我继承了一大堆代码,这些代码广泛使用并行数组来存储键/值对。实际上这样做是有意义的,但是编写循环遍历这些值有点尴尬。我非常喜欢新的Java foreach构造,但似乎没有办法使用它来迭代并行列表。
使用正常for
循环,我可以轻松完成此操作:
for (int i = 0; i < list1.length; ++i) {
doStuff(list1[i]);
doStuff(list2[i]);
}
但在我看来,这不是语义上的纯粹,因为我们在迭代期间没有检查list2
的界限。是否有一些类似于for-each的聪明语法可用于并行列表?
答案 0 :(得分:22)
我会自己使用Map
。但是,如果您认为一对数组在您的情况下是有意义的,那么采用两个数组并返回Iterable
包装器的实用程序方法怎么样?
概念:
for (Pair<K,V> p : wrap(list1, list2)) {
doStuff(p.getKey());
doStuff(p.getValue());
}
Iterable<Pair<K,V>>
包装器会隐藏边界检查。
答案 1 :(得分:11)
来自增强型for循环的官方Oracle页面:
最后,它不适用于循环 必须迭代多次 收集并行。这些 缺点是众所周知的 有意识的设计师 决定干净,简单 构建将覆盖伟大的 大多数情况。
基本上,你最好使用普通的for循环。
如果您正在使用这些数组对来模拟Map,那么您总是可以编写一个实现具有两个数组的Map接口的类;这可以让你抽象掉大部分循环。
不看你的代码,我不能告诉你这个选项是否是最好的前进方式,但这是你可以考虑的事情。
答案 2 :(得分:9)
这是一项有趣的练习。我创建了一个名为ParallelList的对象,它接受可变数量的类型化列表,并且可以遍历每个索引处的值(作为值列表返回):
public class ParallelList<T> implements Iterable<List<T>> {
private final List<List<T>> lists;
public ParallelList(List<T>... lists) {
this.lists = new ArrayList<List<T>>(lists.length);
this.lists.addAll(Arrays.asList(lists));
}
public Iterator<List<T>> iterator() {
return new Iterator<List<T>>() {
private int loc = 0;
public boolean hasNext() {
boolean hasNext = false;
for (List<T> list : lists) {
hasNext |= (loc < list.size());
}
return hasNext;
}
public List<T> next() {
List<T> vals = new ArrayList<T>(lists.size());
for (int i=0; i<lists.size(); i++) {
vals.add(loc < lists.get(i).size() ? lists.get(i).get(loc) : null);
}
loc++;
return vals;
}
public void remove() {
for (List<T> list : lists) {
if (loc < list.size()) {
list.remove(loc);
}
}
}
};
}
}
使用示例:
List<Integer> list1 = Arrays.asList(new Integer[] {1, 2, 3, 4, 5});
List<Integer> list2 = Arrays.asList(new Integer[] {6, 7, 8});
ParallelList<Integer> list = new ParallelList<Integer>(list1, list2);
for (List<Integer> ints : list) {
System.out.println(String.format("%s, %s", ints.get(0), ints.get(1)));
}
哪个会打印出来:
1, 6
2, 7
3, 8
4, null
5, null
此对象支持变量长度列表,但显然可以将其修改为更严格。
不幸的是我无法摆脱ParallelList构造函数上的一个编译器警告:A generic array of List<Integer> is created for varargs parameters
,所以如果有人知道如何摆脱它,请告诉我:)
答案 3 :(得分:6)
您可以在for循环中使用第二个约束:
for (int i = 0; i < list1.length && i < list2.length; ++i)
{
doStuff(list1[i]);
doStuff(list2[i]);
}//for
我遍历集合的首选方法之一是for-each循环,但正如oracle教程提到的那样,在处理并行集合时使用iterator rather than the for-each。
以下是Martin v. Löwis在类似post中的回答:
it1 = list1.iterator();
it2 = list2.iterator();
while(it1.hasNext() && it2.hasNext())
{
value1 = it1.next();
value2 = it2.next();
doStuff(value1);
doStuff(value2);
}//while
迭代器的优点是它是通用的,所以如果你不知道正在使用什么集合,请使用迭代器,否则如果你知道你的集合是什么,那么你就知道长度/大小函数,所以常规这里可以使用带有附加约束的for循环。 (注意我在这篇文章中非常复数,因为一个有趣的可能性是收集所使用的不同,例如一个可能是List,另一个可能是数组)
希望这会有所帮助。
答案 4 :(得分:1)
简单回答:否。
你想要性感的迭代和Java字节码吗?查看Scala: Scala for loop over two lists simultaneously
免责声明:这确实是“使用其他语言”的答案。相信我,我希望Java有性感的并行迭代,但没有人开始用Java开发,因为他们想要性感的代码。
答案 5 :(得分:1)
使用Java 8,我使用它们以性感的方式循环:
data.frame(other_data = rep(ds[,1], rowSums(ds[-1]!=0)),
flag = sub(".*_", "", names(ds)[-1][t(ds[-1]*col(ds[-1]))]))
# other_data flag
#1 1 a
#2 1 c
#3 2 b
#4 3 c
一些例子,有这两个列表:
//parallel loop
public static <A, B> void loop(Collection<A> a, Collection<B> b, IntPredicate intPredicate, BiConsumer<A, B> biConsumer) {
Iterator<A> ait = a.iterator();
Iterator<B> bit = b.iterator();
if (ait.hasNext() && bit.hasNext()) {
for (int i = 0; intPredicate.test(i); i++) {
if (!ait.hasNext()) {
ait = a.iterator();
}
if (!bit.hasNext()) {
bit = b.iterator();
}
biConsumer.accept(ait.next(), bit.next());
}
}
}
//nest loop
public static <A, B> void loopNest(Collection<A> a, Collection<B> b, BiConsumer<A, B> biConsumer) {
for (A ai : a) {
for (B bi : b) {
biConsumer.accept(ai, bi);
}
}
}
在 a 和 b 的最小尺寸内循环:
List<Integer> a = Arrays.asList(1, 2, 3);
List<String> b = Arrays.asList("a", "b", "c", "d");
输出:
loop(a, b, i -> i < Math.min(a.size(), b.size()), (x, y) -> {
System.out.println(x + " -> " + y);
});
在 a 和 b 的最大尺寸内循环(较短列表中的元素将循环播放):
1 -> a
2 -> b
3 -> c
输出:
loop(a, b, i -> i < Math.max(a.size(), b.size()), (x, y) -> {
System.out.println(x + " -> " + y);
});
循环 n 次((如果n大于列表大小,则元素将循环播放)):
1 -> a
2 -> b
3 -> c
1 -> d
输出:
loop(a, b, i -> i < 5, (x, y) -> {
System.out.println(x + " -> " + y);
});
永远循环:
1 -> a
2 -> b
3 -> c
1 -> d
2 -> a
适用于您的情况:
loop(a, b, i -> true, (x, y) -> {
System.out.println(x + " -> " + y);
});
答案 6 :(得分:0)
ArrayIterator可以避免编制索引,但是如果不编写单独的类或至少函数,就不能使用for-each
循环。正如@Alexei Blue所言,官方建议(The Collection Interface)是:“当您需要时,使用Iterator
而不是for-each
构造:...并行迭代多个集合。”:< / p>
import static com.google.common.base.Preconditions.checkArgument;
import org.apache.commons.collections.iterators.ArrayIterator;
// …
checkArgument(array1.length == array2.length);
Iterator it1 = ArrayIterator(array1);
Iterator it2 = ArrayIterator(array2);
while (it1.hasNext()) {
doStuff(it1.next());
doOtherStuff(it2.next());
}
然而:
Map
,正如@Isaac Truett所言,所以最干净的是为所有并行数组创建映射(因此这个循环只能在创建映射的工厂函数中) ,如果你只是想迭代它们,这将是低效的。 (如果您需要支持重复项,请使用Multimap。)ParallelArrayMap<>
(即由并行数组支持的地图),或者ParallelArrayHashMap<>
(如果添加HashMap
您希望按键进行有效查找),并使用它,允许按原始顺序进行迭代。这可能有点矫枉过正,但可以给出一个性感的回答。那是:
Map<T, U> map = new ParallelArrayMap<>(array1, array2);
for (Map.Entry<T, U> entry : map.entrySet()) {
doStuff(entry.getKey());
doOtherStuff(entry.getValue());
}
哲学上,Java风格是由类实现的显式的,命名的类型。所以,当你说“[我有]并行数组[那]存储键/值对。”时,Java回复“编写一个ParallelArrayMap
类来实现Map
(键/值对)并且有一个采用并行数组的构造函数,然后您可以使用entrySet
返回可以迭代的Set
,因为Set
实现了Collection
。“ - 使结构类型中的显式,由类实现。
对于迭代两个并行集合或数组,您希望迭代Iterable<Pair<T, U>>
,不太明确的语言允许您使用zip
创建(@Isaac Truett调用wrap
) 。然而,这不是惯用的Java - 这对中的元素是什么?有关如何使用Java编写此内容以及为何不鼓励这样做的详细讨论,请参阅Java: How to write a zip
function? What should be the return type?。
这正是Java所做的风格权衡:你确切知道一切都是什么类型,而你有来指定和实现它。
答案 7 :(得分:-1)
//Do you think I'm sexy?
if(list1.length == list2.length){
for (int i = 0; i < list1.length; ++i) {
doStuff(list1[i]);
doStuff(list2[i]);
}
}