使用foreach在Java中迭代并行数组的漂亮方法

时间:2011-04-04 23:42:41

标签: java foreach iteration

我继承了一大堆代码,这些代码广泛使用并行数组来存储键/值对。实际上这样做是有意义的,但是编写循环遍历这些值有点尴尬。我非常喜欢新的Java foreach构造,但似乎没有办法使用它来迭代并行列表。

使用正常for循环,我可以轻松完成此操作:

for (int i = 0; i < list1.length; ++i) {
    doStuff(list1[i]);
    doStuff(list2[i]);
}

但在我看来,这不是语义上的纯粹,因为我们在迭代期间没有检查list2的界限。是否有一些类似于for-each的聪明语法可用于并行列表?

8 个答案:

答案 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());
  }

然而:

  • 索引对于数组来说是很自然的 - 数组是按照定义你索引的东西,而数字for循环就像在原始代码中一样,非常自然而且更直接。
  • 键值对自然形成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]);
    }
}