I've inherited a bunch of code that makes extensive use of parallel arrays to store key/value pairs. It actually made sense to do it this way, but it's sort of awkward to write loops that iterate over these values. I really like the new Java foreach construct, but it does not seem like there is a way to iterate over parallel lists using this.
With a normal for
loop, I can do this easily:
for (int i = 0; i < list1.length; ++i) {
doStuff(list1[i]);
doStuff(list2[i]);
}
But in my opinion this is not semantically pure, since we are not checking the bounds of list2
during iteration. Is there some clever syntax similar to the for-each that I can use with parallel lists?
From the official Oracle page on the enhanced for loop:
Basically, you're best off using the normal for loop.
If you're using these pairs of arrays to simulate a Map, you could always write a class that implements the Map interface with the two arrays; this could let you abstract away much of the looping.
Without looking at your code, I cannot tell you whether this option is the best way forward, but it is something you could consider.
With Java 8, I use these to loop in the sexy way:
Some example, with these 2 lists:
Loop within min size of a and b:
Output:
Loop within max size of a and b (elements in shorter list will be cycled):
Output:
Loop n times ((elements will be cycled if n is bigger than sizes of lists)):
Output:
Loop forever:
Apply to your situation:
This was a fun exercise. I created an object called ParallelList that takes a variable number of typed lists, and can iterate over the values at each index (returned as a list of values):
Example usage:
Which would print out:
This object supports lists of variable lengths, but clearly it could be modified to be more strict.
Unfortunately I couldn't get rid of one compiler warning on the ParallelList constructor:
A generic array of List<Integer> is created for varargs parameters
, so if anyone knows how to get rid of that, let me know :)ArrayIterator lets you avoid indexing, but you can’t use a
for-each
loop without writing a separate class or at least function. As @Alexei Blue remarks, official recommendation (at The Collection Interface) is: “UseIterator
instead of thefor-each
construct when you need to: … Iterate over multiple collections in parallel.”:However:
Map
, as @Isaac Truett remarks, so cleanest would be to create maps for all your parallel arrays (so this loop would only be in the factory function that creates the maps), though this would be inefficient if you just want to iterate over them. (Use Multimap if you need to support duplicates.)ParallelArrayMap<>
(i.e., a map backed by parallel arrays), or maybeParallelArrayHashMap<>
(to add aHashMap
if you want efficient lookup by key), and use that, which allows iteration in the original order. This is probably overkill though, but allows a sexy answer.That is:
Philosophically, Java style is to have explicit, named types, implemented by classes. So when you say “[I have] parallel arrays [that] store key/value pairs.”, Java replies “Write a
ParallelArrayMap
class that implementsMap
(key/value pairs) and that has a constructor that takes parallel arrays, and then you can useentrySet
to return aSet
that you can iterate over, sinceSet
implementsCollection
.” – make the structure explicit in a type, implemented by a class.For iterating over two parallel collections or arrays, you want to iterate over a
Iterable<Pair<T, U>>
, which less explicit languages allow you to create withzip
(which @Isaac Truett callswrap
). This is not idiomatic Java, however – what are the elements of the pair? See Java: How to write azip
function? What should be the return type? for an extensive discussion of how to write this in Java and why it’s discouraged.This is exactly the stylistic tradeoff Java makes: you know exactly what type everything is, and you have to specify and implement it.
You can use a second constraint in your for loop:
One of my preferred methods for traversing collections is the for-each loop, but as the oracle tutorial mentions, when dealing with parallel collections to use the iterator rather than the for-each.
The following was an answer by Martin v. Löwis in a similar post:
The advantage to the iterator is that it's generic so if you don't know what the collections are being used, use the iterator, otherwise if you know what your collections are then you know the length/size functions and so the regular for-loop with the additional constraint can be used here. (Note I'm being very plural in this post as an interesting possibility would be where the collections used are different e.g. one could be a List and the other an array for instance)
Hope this helped.