Improving list initialization for n-elements using nested lists - java

I'd like to initialize a nested list without using a for-loop, the root list which is: cakeList will contain another ones eg.(100).
My code:
1. ArrayList<ArrayList<Integer>> cakeList = new ArrayList<>();
2. for (int i=0;i<100;i++) cakeList.add(new ArrayList<>());
I've tried:
import java.util.ArrayList;
public class Solution {
public static void main(String[] args) {
ArrayList<ArrayList<Integer>> cakeList = new ArrayList<>(100);
cakeList.forEach(e-> new ArrayList<>());
System.out.println(cakeList.size());
cakeList.get(0);
}
}
but as you maybe know, it complies, but throws an error al line #7 because cakeList is empty whe I try to cakeList.get(0).
IndexOutOfBoundsException: Index: 0, Size: 0
Thank you in advance!

It is convenient to use Stream::generate and Stream::limit to create predefined number of objects, so for the 2D list this would look as follows:
List<ArrayList<Integer>> list2D = Stream.generate(ArrayList<Integer>::new)
.limit(100) // Stream<ArrayList<Integer>>
.collect(Collectors.toList());
list2D.get(0).add(111); // 111 added to one entry
Also, IntStream with a fixed range may be used:
List<ArrayList<Integer>> list2D = IntStream.range(0, 100)
.mapToObj(i -> new ArrayList<Integer>())
.collect(Collectors.toList());
If it is important to have specific ArrayList<ArrayList> as a result, a collector Collectors.toCollection may be used:
ArrayList<ArrayList<Integer>> list2D = IntStream.range(0, 100)
.mapToObj(i -> new ArrayList<Integer>())
.collect(Collectors.toCollection(ArrayList::new));
There is also a method Collections.nCopies but it creates N copies of the same object which may have a side effect of applying a change in one element to all other "copy" elements in the list.
List<ArrayList<Integer>> cakeList = Collections.nCopies(100, new ArrayList<>());
cakeList.get(0).add(111); // 111 is added to "all" entries in cakeList

Your initialization is fine, but post initialization, your lists are all empty, as you would expect docs (see Constructors) .
Per the methods section of the docs,
cakeList.get(0); // "Returns the element at the specified position in this list."
Since your list is empty, there is no element at the specified position.
The only way to create an ArrayList that is non-empty is to use the constructor (see docs) which takes in a Collection.
Example:
ArrayList<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
numbers.get(0); //will return 1
Your goal of not wanting to use a for-loop for initialization is understandable, but see this post for the speed comparison of the two methods. Note the method call
cakeList.forEach(e-> new ArrayList<>());
is equivalent to
Iterator<T> iter = cakeList.iterator();
while (iter.hasNext()) {
iter.next().add(new ArrayList<Integer>();
}
A conclusion you will reach is that there is no shortcut to filling arrays, it will take O(n) where n is your array size.

Related

Sorting multiple ArrayList synchronously

I have multiple parallel ArrayList, I am sorting one of them (i.e. indexes).
ArrayList<Integer> indexes = {2,3,1};
ArrayList<String> names = {"two","three","one"};
ArrayList<String> upper = {"TWO","THREE","ONE"};
I want to synchronise the sorting of the ArrayList 'indexes' with the others ArrayList.
I am wondering in the Collections.sort(list) would give me a clue?
It sounds like you want to sort one array by its values, and then rearrange two other arrays so that the arrangement of their values matches the sort order of the first array.
An easy way to do this is to sort an array of indexes into the ordering that you want, and then use this to rearrange the other arrays into the same order. Since one of your arrays is already called "indexes" I'll call this new array the "permutation".
First, create the permutation array by generating index values from zero to size-1 and then sorting them. They end up being sorted not according to their own values, but by the values in your index array:
List<Integer> indexes = List.of(2,3,1);
List<String> names = List.of("two","three","one");
List<String> upper = List.of("TWO","THREE","ONE");
List<Integer> permutation = IntStream.range(0, indexes.size())
.boxed()
.sorted(comparing(indexes::get))
.collect(toCollection(ArrayList::new));
(So far, this is similar to the technique from Eritrean's answer.)
Now, we need to rearrange some data array according to the arrangement from the permutation array. Since we're doing this multiple times, here's a function that does that:
static <T> List<T> permute(List<Integer> permutation, List<T> list) {
return IntStream.range(0, permutation.size())
.mapToObj(i -> list.get(permutation.get(i)))
.toList();
}
Now it's a simple matter to apply this to each of the data arrays:
System.out.println(permute(permutation, indexes));
System.out.println(permute(permutation, names));
System.out.println(permute(permutation, upper));
The result is
[1, 2, 3]
[one, two, three]
[ONE, TWO, THREE]
Note that this creates new lists in the desired arrangement. It's possible to permute the data arrays in-place, but it's somewhat more work, though not intractable. (Search for "[java] permute array in place" for ideas.)
Create a priority list from your indexes and use the index of the elemnts as a sorting criteria:
public static void main(String args[]) {
List<Integer> indexes = new ArrayList<>(List.of(2,3,1));
List<String> names = new ArrayList<>(List.of("two","three","one"));
List<String> upper = new ArrayList<>(List.of("TWO","THREE","ONE"));
List<Integer> priority = IntStream.range(0, indexes.size())
.boxed()
.sorted(Comparator.comparingInt(indexes::get))
.collect(Collectors.toList());
names.sort(Comparator.comparingInt(i -> priority.indexOf(names.indexOf(i))));
upper.sort(Comparator.comparingInt(i -> priority.indexOf(upper.indexOf(i))));
indexes.sort(Comparator.comparingInt(i -> priority.indexOf(indexes.indexOf(i))));
System.out.println(indexes);
System.out.println(names);
System.out.println(upper);
}
You can use a container as an intermediary step (though, it may be better to modify your code to simply use the container instead of 3 separate lists):
public class Container {
final int id;
final String name;
final String upper;
... // constructor + getters (or create a record if you're using j14+)
}
public static void main(String args[]) {
List<Container> values = indexes.stream()
.sorted()
.map(index -> new Container(index, names.get(index), upper.get(index)))
.collect(Collectors.toList());
List<String> sortedNames = values.stream()
.map(value -> value.getName())
.collect(Collectors.toList());
List<String> sortedUpper = values.stream()
.map(value -> value.getUpper())
.collect(Collectors.toList());
}

remove duplicate list from an arrayList using Set

I have a List that contains duplicate ArrayList.
I'm looking for a solution to remove them.
Here is an example:
listOne = [[1, 0], [0, 1], [3, 2], [2, 3]]
This set contains duplicate List. Normally i want to get :
theListAfterTransformation = [[1, 0],[3, 2]]
Here is my tiny example, i tried to use the Set but it didn't work well.
public class Example {
public static void main( String[] args ) {
ArrayList<ArrayList<Integer>> lists = new ArrayList<>();
ArrayList<Integer> list1 = new ArrayList<>(); list1.add(1); list1.add(0);
ArrayList<Integer> list2 = new ArrayList<>(); list2.add(0); list2.add(1);
ArrayList<Integer> list3 = new ArrayList<>(); list3.add(3); list3.add(2);
ArrayList<Integer> list4 = new ArrayList<>(); list4.add(2); list4.add(3);
lists.add(list1);lists.add(list2);lists.add(list3);lists.add(list4);
System.out.println(getUnduplicateList(lists));
}
public static ArrayList<ArrayList<Integer>> getUnduplicateList( ArrayList<ArrayList<Integer>> lists) {
Iterator iterator = lists.iterator();
Set<ArrayList<Integer>> set = new HashSet<>();
while (iterator.hasNext()){
ArrayList<Integer> list = (ArrayList<Integer>) iterator.next();
set.add(list);
}
return new ArrayList<>(set);
}
}
Note that is a tiny example from my project and it will be very hard to use a solution that change many thing in this implementation.
So take into account that the getUnduplicateList should keep the same signature. the good idea will be to change only the implementation.
This program print the same list as the input. any idea please.
A couple notes on terminology—Set is a distinct data structure from List, where the former is unordered and does not allow duplicates, while the latter is a basic, linear collection, that's generally ordered, and allows duplicates. You seem to be using the terms interchangeably, which may be part of the issue you're having: Set is probably the appropriate data structure here.
That said, it seems that your code is relying on the List API, so we can follow that along. Note that you should, in general, code to the interface (List), rather than the specific class (ArrayList).
Additionally, consider using the Arrays.asList shorthand method for initializing a list (note that this returns an immutable list).
Finally, note that a HashSet eliminates duplicates by checking if both objects have the same hashCode. Lists containing the same elements are still not considered to be the same list unless the elements appear in the same order, and will typically not be treated as duplicates. Sets, however, implement equals and hashCode in such a way that two sets containing exactly the same elements are considered equal (order doesn't matter).
Using your original starting collection, you can convert each inner-list to a set. Then, eliminate duplicates from the outer collection. Finally, convert the inner-collections back to lists, to maintain compatibility with the rest of your code (if needed). This approach will work regardless of the size of the inner-lists.
You can simulate these steps using a Stream, and using method references to convert to and from the Set, as below.
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.stream.Collectors;
public class Example {
public static void main( String[] args ) {
List<Integer> list1 = Arrays.asList(1, 0);
List<Integer> list2 = Arrays.asList(0, 1);
List<Integer> list3 = Arrays.asList(3, 2);
List<Integer> list4 = Arrays.asList(2, 3);
List<List<Integer>> lists = Arrays.asList(list1, list2, list3, list4);
System.out.println(getUnduplicateList(lists));
}
public static List<List<Integer>> getUnduplicateList(List<List<Integer>> lists) {
return lists
.stream()
.map(HashSet::new)
.distinct()
.map(ArrayList::new)
.collect(Collectors.toList());
}
}
You need to convert the inner lists to sets as well.
Another solution is to sort your lists and then run them through distinct Although this is not very efficient and you will also obtain a set of sorted lists:
Set<List<Integer>> collect = set.stream()
.map(list -> {
list.sort(Comparator.comparingInt(Integer::intValue));
return list;
})
.distinct()
.collect(Collectors.toSet());

Add a `List<Integer>` to `List<List<Integer>>`, why "new" is necessary in this situation?

The question is came up when I was looking at one interview question:
Given a set of distinct integers, nums, return all possible subsets (the power set). And one solution is:
class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
if(nums==null || nums.length==0) return res;
dfs(res, new ArrayList<>(), 0, nums);
return res;
}
private void dfs(List<List<Integer>> res, List<Integer> list, int pos, int[] nums) {
res.add(new ArrayList<Integer>(list));
if(pos==nums.length+1) return;
for(int i=pos; i<nums.length; i++) {
list.add(nums[i]);
dfs(res, list, i+1, nums);
list.remove(list.size()-1);
}
}
}
Here in the helper function dfs, we have to add a copy of the list each time. Otherwise, the empty list will be added to the res multiple times(if doing res.add(list) instead of what in the solution). Why "new" is necessary?
Your code is a fairly standard recursive depth-first search (DFS) for all possible subsets of the set of numbers in the nums array.
When I run your code as it stands, it works nicely. For example I call it like this:
Solution app = new Solution();
int[] nums = { 5, 2 };
List<List<Integer>> subsets = app.subsets(nums);
subsets.forEach(System.out::println);
Output:
[]
[5]
[5, 2]
[2]
And you are quite correct: if in the helper function dfs we don’t make a copy of the list with new, the result will appear as a list of empty lists. I tried changing the first line of dfs to just:
res.add(list);
Now the output from the same call as above is:
[]
[]
[]
[]
And I’ll tell you what: this is not just 4 empty lists. This is the same empty list being printed 4 times.
Follow this link to see the difference live.
Why is this? After all, as you said, the helper method does add elements to the list within its loop:
list.add(nums[i]);
Without new the same list is passed to each recursive invocation of dfs. While elements are added to this list, they are also removed from it again:
list.remove(list.size()-1);
So for most of the time during running your code, the result list of lists, res will contain a list with elements in it (the same list multiple times), at the end all the elements will be removed from the contained list.
What new does, as you know, is taking a copy of the list. In this way the state of the list, which elements it contains, is preserved: no elements will be removed from the copy when elements are removed from the original list. So your result ends up holding all different lists and all with the elements in them that were there when the list was added to the result.
First, we should know the "list" used in the for loop contains an address which points to a List type.
So, when use res.add(list), we store the address into the res, instead of the value. Then, every time we run list.add(nums[i]) , we can find the change through address stored in the list or the address stored in res (actually they store the same address).
With the backtrack goes deeper, we add the same address(stored in variable list) repeatedly into the res, that's why you can find copy of the list, since they are all the same address.
So the "new" is necessary to make the res store different addresses which point to different temporary list.
No, you don't need new.
I've just checked:
import java.util.*;
public class Main
{
public static void addToList(List<List<Integer>> res, List<Integer> toAdd){
res.add(toAdd);
}
public static void main(String[] args)
{
List<Integer> a = new ArrayList<>();
a.add(5); a.add(2); // list a size is expected to be 2
List<Integer> b = new ArrayList<>();
b.add(3); // list b size is expected to be 1
List<List<Integer>> containerOfLists = new ArrayList<>();
addToList(containerOfLists, a);
addToList(containerOfLists, b);
// Should be 2
System.out.println("The list size is "+ containerOfLists.size());
// Should be 2
System.out.println("The nested list 0 size is "+ containerOfLists.get(0).size());
// Should be 1
System.out.println("The nested list 1 size is "+ containerOfLists.get(1).size());
// Should be 5
System.out.println("The nested list 0 element 0 is "+containerOfLists.get(0).get(0));
}
}
The result is as expected, no new was used in the helper and the container was not empty:
The list size is 2
The nested list 0 size is 2
The nested list 1 size is 1
The nested list 0 element 0 is 5
I don't know at what point exactly you get an empty list, but, I wonder if the problem in your case is about you creating an empty list as follows
List<List<Integer>> res = new ArrayList<>();
in your subsets method. There you may return an empty list with the res object, and you might mistakenly think the empty list is returned by dfs. I can only suppose.

Create sub List from List in Java that's not a single continuous range

Suppose I have the code:
List<String> list = new ArrayList<String>();
list.add("A"); //want
list.add("B");
list.add("C");
list.add("D"); //want
list.add("E"); //want
and I'd like to create a new list that only has the elements "A", "D", E".
The List class has a method subList that will work if you only have 1 single continuous range, but what if you want multiple ranges, or multiple discrete elements?
Is there any method in java or library that allows me to do something like:
List<String> subList = NeatListUtilities.subList(list, 0, 3, 4);
None that I know of, however it is fairly simple to implement.
(No error / range checking, assumed use of ArrayList)
public static <T> List<T> subList(List<T> src, int... indices) {
List<T> result = new ArrayList<>(indices.length);
for(int i : indices) {
result.add(src.get(i));
}
return result;
}
A concise way to do what you want with Stream:
List<String> subList = IntStream.of(0, 3, 4)
.mapToObj(i -> list.get(i))
.collect(Collectors.toList());
So, probably you don't need a library for this.

Exception with ListIterator in Java [duplicate]

Is it possible to add elements to a collection while iterating over it?
More specifically, I would like to iterate over a collection, and if an element satisfies a certain condition I want to add some other elements to the collection, and make sure that these added elements are iterated over as well. (I realise that this could lead to an unterminating loop, but I'm pretty sure it won't in my case.)
The Java Tutorial from Sun suggests this is not possible: "Note that Iterator.remove is the only safe way to modify a collection during iteration; the behavior is unspecified if the underlying collection is modified in any other way while the iteration is in progress."
So if I can't do what I want to do using iterators, what do you suggest I do?
How about building a Queue with the elements you want to iterate over; when you want to add elements, enqueue them at the end of the queue, and keep removing elements until the queue is empty. This is how a breadth-first search usually works.
There are two issues here:
The first issue is, adding to an Collection after an Iterator is returned. As mentioned, there is no defined behavior when the underlying Collection is modified, as noted in the documentation for Iterator.remove:
... The behavior of an iterator is
unspecified if the underlying
collection is modified while the
iteration is in progress in any way
other than by calling this method.
The second issue is, even if an Iterator could be obtained, and then return to the same element the Iterator was at, there is no guarantee about the order of the iteratation, as noted in the Collection.iterator method documentation:
... There are no guarantees concerning the
order in which the elements are
returned (unless this collection is an
instance of some class that provides a
guarantee).
For example, let's say we have the list [1, 2, 3, 4].
Let's say 5 was added when the Iterator was at 3, and somehow, we get an Iterator that can resume the iteration from 4. However, there is no guarentee that 5 will come after 4. The iteration order may be [5, 1, 2, 3, 4] -- then the iterator will still miss the element 5.
As there is no guarantee to the behavior, one cannot assume that things will happen in a certain way.
One alternative could be to have a separate Collection to which the newly created elements can be added to, and then iterating over those elements:
Collection<String> list = Arrays.asList(new String[]{"Hello", "World!"});
Collection<String> additionalList = new ArrayList<String>();
for (String s : list) {
// Found a need to add a new element to iterate over,
// so add it to another list that will be iterated later:
additionalList.add(s);
}
for (String s : additionalList) {
// Iterate over the elements that needs to be iterated over:
System.out.println(s);
}
Edit
Elaborating on Avi's answer, it is possible to queue up the elements that we want to iterate over into a queue, and remove the elements while the queue has elements. This will allow the "iteration" over the new elements in addition to the original elements.
Let's look at how it would work.
Conceptually, if we have the following elements in the queue:
[1, 2, 3, 4]
And, when we remove 1, we decide to add 42, the queue will be as the following:
[2, 3, 4, 42]
As the queue is a FIFO (first-in, first-out) data structure, this ordering is typical. (As noted in the documentation for the Queue interface, this is not a necessity of a Queue. Take the case of PriorityQueue which orders the elements by their natural ordering, so that's not FIFO.)
The following is an example using a LinkedList (which is a Queue) in order to go through all the elements along with additional elements added during the dequeing. Similar to the example above, the element 42 is added when the element 2 is removed:
Queue<Integer> queue = new LinkedList<Integer>();
queue.add(1);
queue.add(2);
queue.add(3);
queue.add(4);
while (!queue.isEmpty()) {
Integer i = queue.remove();
if (i == 2)
queue.add(42);
System.out.println(i);
}
The result is the following:
1
2
3
4
42
As hoped, the element 42 which was added when we hit 2 appeared.
You may also want to look at some of the more specialised types, like ListIterator, NavigableSet and (if you're interested in maps) NavigableMap.
Actually it is rather easy. Just think for the optimal way.
I beleive the optimal way is:
for (int i=0; i<list.size(); i++) {
Level obj = list.get(i);
//Here execute yr code that may add / or may not add new element(s)
//...
i=list.indexOf(obj);
}
The following example works perfectly in the most logical case - when you dont need to iterate the added new elements before the iteration element. About the added elements after the iteration element - there you might want not to iterate them either. In this case you should simply add/or extend yr object with a flag that will mark them not to iterate them.
Use ListIterator as follows:
List<String> l = new ArrayList<>();
l.add("Foo");
ListIterator<String> iter = l.listIterator(l.size());
while(iter.hasPrevious()){
String prev=iter.previous();
if(true /*You condition here*/){
iter.add("Bah");
iter.add("Etc");
}
}
The key is to iterate in reverse order - then the added elements appear on the next iteration.
I know its been quite old. But thought of its of any use to anyone else. Recently I came across this similar problem where I need a queue that is modifiable during iteration. I used listIterator to implement the same much in the same lines as of what Avi suggested -> Avi's Answer. See if this would suit for your need.
ModifyWhileIterateQueue.java
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class ModifyWhileIterateQueue<T> {
ListIterator<T> listIterator;
int frontIndex;
List<T> list;
public ModifyWhileIterateQueue() {
frontIndex = 0;
list = new ArrayList<T>();
listIterator = list.listIterator();
}
public boolean hasUnservicedItems () {
return frontIndex < list.size();
}
public T deQueue() {
if (frontIndex >= list.size()) {
return null;
}
return list.get(frontIndex++);
}
public void enQueue(T t) {
listIterator.add(t);
}
public List<T> getUnservicedItems() {
return list.subList(frontIndex, list.size());
}
public List<T> getAllItems() {
return list;
}
}
ModifyWhileIterateQueueTest.java
#Test
public final void testModifyWhileIterate() {
ModifyWhileIterateQueue<String> queue = new ModifyWhileIterateQueue<String>();
queue.enQueue("one");
queue.enQueue("two");
queue.enQueue("three");
for (int i=0; i< queue.getAllItems().size(); i++) {
if (i==1) {
queue.enQueue("four");
}
}
assertEquals(true, queue.hasUnservicedItems());
assertEquals ("[one, two, three, four]", ""+ queue.getUnservicedItems());
assertEquals ("[one, two, three, four]", ""+queue.getAllItems());
assertEquals("one", queue.deQueue());
}
Using iterators...no, I don't think so. You'll have to hack together something like this:
Collection< String > collection = new ArrayList< String >( Arrays.asList( "foo", "bar", "baz" ) );
int i = 0;
while ( i < collection.size() ) {
String curItem = collection.toArray( new String[ collection.size() ] )[ i ];
if ( curItem.equals( "foo" ) ) {
collection.add( "added-item-1" );
}
if ( curItem.equals( "added-item-1" ) ) {
collection.add( "added-item-2" );
}
i++;
}
System.out.println( collection );
Which yeilds:
[foo, bar, baz, added-item-1, added-item-2]
Besides the solution of using an additional list and calling addAll to insert the new items after the iteration (as e.g. the solution by user Nat), you can also use concurrent collections like the CopyOnWriteArrayList.
The "snapshot" style iterator method uses a reference to the state of the array at the point that the iterator was created. This array never changes during the lifetime of the iterator, so interference is impossible and the iterator is guaranteed not to throw ConcurrentModificationException.
With this special collection (usually used for concurrent access) it is possible to manipulate the underlying list while iterating over it. However, the iterator will not reflect the changes.
Is this better than the other solution? Probably not, I don't know the overhead introduced by the Copy-On-Write approach.
public static void main(String[] args)
{
// This array list simulates source of your candidates for processing
ArrayList<String> source = new ArrayList<String>();
// This is the list where you actually keep all unprocessed candidates
LinkedList<String> list = new LinkedList<String>();
// Here we add few elements into our simulated source of candidates
// just to have something to work with
source.add("first element");
source.add("second element");
source.add("third element");
source.add("fourth element");
source.add("The Fifth Element"); // aka Milla Jovovich
// Add first candidate for processing into our main list
list.addLast(source.get(0));
// This is just here so we don't have to have helper index variable
// to go through source elements
source.remove(0);
// We will do this until there are no more candidates for processing
while(!list.isEmpty())
{
// This is how we get next element for processing from our list
// of candidates. Here our candidate is String, in your case it
// will be whatever you work with.
String element = list.pollFirst();
// This is where we process the element, just print it out in this case
System.out.println(element);
// This is simulation of process of adding new candidates for processing
// into our list during this iteration.
if(source.size() > 0) // When simulated source of candidates dries out, we stop
{
// Here you will somehow get your new candidate for processing
// In this case we just get it from our simulation source of candidates.
String newCandidate = source.get(0);
// This is the way to add new elements to your list of candidates for processing
list.addLast(newCandidate);
// In this example we add one candidate per while loop iteration and
// zero candidates when source list dries out. In real life you may happen
// to add more than one candidate here:
// list.addLast(newCandidate2);
// list.addLast(newCandidate3);
// etc.
// This is here so we don't have to use helper index variable for iteration
// through source.
source.remove(0);
}
}
}
For examle we have two lists:
public static void main(String[] args) {
ArrayList a = new ArrayList(Arrays.asList(new String[]{"a1", "a2", "a3","a4", "a5"}));
ArrayList b = new ArrayList(Arrays.asList(new String[]{"b1", "b2", "b3","b4", "b5"}));
merge(a, b);
a.stream().map( x -> x + " ").forEach(System.out::print);
}
public static void merge(List a, List b){
for (Iterator itb = b.iterator(); itb.hasNext(); ){
for (ListIterator it = a.listIterator() ; it.hasNext() ; ){
it.next();
it.add(itb.next());
}
}
}
a1 b1 a2 b2 a3 b3 a4 b4 a5 b5
I prefer to process collections functionally rather than mutate them in place. That avoids this kind of problem altogether, as well as aliasing issues and other tricky sources of bugs.
So, I would implement it like:
List<Thing> expand(List<Thing> inputs) {
List<Thing> expanded = new ArrayList<Thing>();
for (Thing thing : inputs) {
expanded.add(thing);
if (needsSomeMoreThings(thing)) {
addMoreThingsTo(expanded);
}
}
return expanded;
}
IMHO the safer way would be to create a new collection, to iterate over your given collection, adding each element in the new collection, and adding extra elements as needed in the new collection as well, finally returning the new collection.
Given a list List<Object> which you want to iterate over, the easy-peasy way is:
while (!list.isEmpty()){
Object obj = list.get(0);
// do whatever you need to
// possibly list.add(new Object obj1);
list.remove(0);
}
So, you iterate through a list, always taking the first element and then removing it. This way you can append new elements to the list while iterating.
Forget about iterators, they don't work for adding, only for removing. My answer applies to lists only, so don't punish me for not solving the problem for collections. Stick to the basics:
List<ZeObj> myList = new ArrayList<ZeObj>();
// populate the list with whatever
........
int noItems = myList.size();
for (int i = 0; i < noItems; i++) {
ZeObj currItem = myList.get(i);
// when you want to add, simply add the new item at last and
// increment the stop condition
if (currItem.asksForMore()) {
myList.add(new ZeObj());
noItems++;
}
}
I tired ListIterator but it didn't help my case, where you have to use the list while adding to it. Here's what works for me:
Use LinkedList.
LinkedList<String> l = new LinkedList<String>();
l.addLast("A");
while(!l.isEmpty()){
String str = l.removeFirst();
if(/* Condition for adding new element*/)
l.addLast("<New Element>");
else
System.out.println(str);
}
This could give an exception or run into infinite loops. However, as you have mentioned
I'm pretty sure it won't in my case
checking corner cases in such code is your responsibility.
This is what I usually do, with collections like sets:
Set<T> adds = new HashSet<T>, dels = new HashSet<T>;
for ( T e: target )
if ( <has to be removed> ) dels.add ( e );
else if ( <has to be added> ) adds.add ( <new element> )
target.removeAll ( dels );
target.addAll ( adds );
This creates some extra-memory (the pointers for intermediate sets, but no duplicated elements happen) and extra-steps (iterating again over changes), however usually that's not a big deal and it might be better than working with an initial collection copy.
Even though we cannot add items to the same list during iteration, we can use Java 8's flatMap, to add new elements to a stream. This can be done on a condition. After this the added item can be processed.
Here is a Java example which shows how to add to the ongoing stream an object depending on a condition which is then processed with a condition:
List<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
intList.add(3);
intList = intList.stream().flatMap(i -> {
if (i == 2) return Stream.of(i, i * 10); // condition for adding the extra items
return Stream.of(i);
}).map(i -> i + 1)
.collect(Collectors.toList());
System.out.println(intList);
The output of the toy example is:
[2, 3, 21, 4]
In general, it's not safe, though for some collections it may be. The obvious alternative is to use some kind of for loop. But you didn't say what collection you're using, so that may or may not be possible.

Categories

Resources