2011-04-04 88 views
31

我已经继承了大量使用并行数组来存储键/值对的代码。这样做确实有道理,但编写循环遍历这些值是有点尴尬的。我真的很喜欢新的Java foreach结构,但似乎没有办法使用它来迭代并行列表。使用foreach在Java中迭代并行数组的巧妙方法

与正常for环,我可以很容易地做到这一点:

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

但在我看来,这不是纯粹的语义,因为我们没有迭代过程中检查list2界限。是否有一些类似于for-each的巧妙语法,可以用于平行列表?

+4

回复:语义纯度 - 你可以迭代过程中检查列表2边界('(I'list1.length)&&(I'list2.length)'),或者如果您知道迭代期间列表将不会被修改,您可以在循环之前检查是否有'list1'和'list2'具有相同的长度,在这种情况下,您可以避免在迭代期间用明确的良心检查两者的边界。 – QuantumMechanic 2011-04-04 23:47:24

+0

如果一个线程修改'list2'而不是'list1',那么我就搞定了。 – 2011-04-04 23:53:38

+1

就像我说的 - “如果你知道列表在迭代过程中将不会被修改”。而且,那样会让你比现在更糟糕。如果你不得不担心多个线程触及这些列表,那么你就有更多的担忧,而不仅仅是这个循环。 – QuantumMechanic 2011-04-05 00:03:44

回答

21

我会自己使用Map。但是,如果您认为一对数组对您的情况有意义,那么使用这两个数组并返回Iterable包装的实用方法会如何?

概念:

for (Pair<K,V> p : wrap(list1, list2)) { 
    doStuff(p.getKey()); 
    doStuff(p.getValue()); 
} 

Iterable<Pair<K,V>>包装将隐藏边界检查。

+0

旧的'对'解决一切。没有想过那个,很好。是不是有一个JSR的地方乞求将其纳入该语言? – 2011-04-04 23:52:37

+0

请小心这个答案。如果第一个列表具有重复值,会发生什么?生成的Map将覆盖这些键的值。所以**不要使用这种方法,如果你的列表可能包含重复的值** – jfcorugedo 2015-04-09 16:10:19

+0

@jfcorugedo你是正确的,一个地图不能包含重复的键。我的答案实际上并不需要使用地图。 – 2015-04-09 16:56:27

-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]); 
    } 
} 
+1

当list1.length!= list2.length时,一切都会神秘地破坏'。 – 2011-04-04 23:51:55

+0

请参阅我对@QuantumMechanic的回应 – 2011-04-04 23:53:59

+0

@Isaac Truett,@ Travis Webb,错误的标记,对不起。 – 2011-04-04 23:54:27

10

从Oracle官方页面上增强的for循环:

最后,它不是循环 必须多次迭代 集并行使用。这些 的缺点被 设计师所了解,他们意识到 决定采用一种干净,简单的 构造,它将涵盖大多数情况下的优秀 构造。

基本上,你最好使用正常的循环。

如果你使用这些数组对来模拟一个Map,你总是可以写一个类来实现Map接口和两个数组;这可以让你抽象出大部分循环。

不看你的代码,我不能告诉你这个选项是否是最好的前进方向,但这是你可以考虑的。

8

这是一个有趣的练习。我创建称为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,因此,如果有人知道如何摆脱这一点,让我知道:)

+6

'@ SuppressWarnings' ;-) – 2011-04-05 01:16:45

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

以下类似的post是一个答案通过Martin v. Löwis

it1 = list1.iterator(); 
it2 = list2.iterator(); 
while(it1.hasNext() && it2.hasNext()) 
{ 
    value1 = it1.next(); 
    value2 = it2.next(); 

    doStuff(value1); 
    doStuff(value2); 
}//while 

迭代器的优点是,它是通用的,因此,如果您不知道正在使用什么样的集合,使用迭代器,否则,如果你知道你的集合是什么,那么你知道长度/大小函数,所以在这里可以使用带额外约束的常规for循环。 (注意我在这篇文章中是非常复数的,因为一个有趣的可能性是所使用的集合是不同的,例如一个可能是List而另一个是数组)。

1

答案很简单:

你想要性感迭代和Java字节码号?退房斯卡拉: Scala for loop over two lists simultaneously

免责声明:这确实是一个“用另一种语言”的答案。相信我,我希望Java有性感的并行迭代,但没有人开始用Java开发,因为他们需要性感的代码。

+1

嘿乔!我们继续碰面:)当然“使用不同的语言”是解决问题的方法,但不是对包含介词短语“in Java”的问题的回答。 – 2014-11-12 21:57:55

+0

@TravisWebb哦,没有注意到它是你!实际答案是“否”。其他语言是性感迭代的建议。 :)希望他们很快会成为一个不错的Java 8 lambda表达式答案。 – 2014-11-12 22:47:33

+0

是的,这个问题在这一点上已经很老了。可能需要一些更现代的答案。 – 2014-11-13 23:35:25

0

ArrayIterator可以避免建立索引,但不能使用for-each循环,而无需编写单独的类或至少是函数。作为@Alexei蓝的言论,官方推荐(在The Collection Interface)是:“当你需要使用Iterator代替for-each结构:...遍历多个集合并行”:

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特鲁特称wrap)。这不是惯用的Java,但是 - 这对的元素是什么?请参阅Java: How to write a zip function? What should be the return type?了解如何使用Java编写此代码以及为何不鼓励它的广泛讨论。

这正是Java所做的风格权衡:你确切地知道什么类型的东西都是,而你来指定和实现它。

1

在Java 8,I在性感方式使用这些循环:

//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); 
     } 
    } 
} 

的一些示例,这些2所列出:

List<Integer> a = Arrays.asList(1, 2, 3); 
List<String> b = Arrays.asList("a", "b", "c", "d"); 

环路内的一个分钟大小和b

loop(a, b, i -> i < Math.min(a.size(), b.size()), (x, y) -> { 
    System.out.println(x + " -> " + y); 
}); 

输出:

一个b最大尺寸内环路(在较短的列表中的元素将被循环):

loop(a, b, i -> i < Math.max(a.size(), b.size()), (x, y) -> { 
    System.out.println(x + " -> " + y); 
}); 

输出:

1 -> a 
2 -> b 
3 -> c 
1 -> d 

环路Ñ倍((如果n大于列表的大小)大的元件将被循环):

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); 
}); 

适用于您的情况:

loop(list1, list2, i -> i < Math.min(a.size(), b.size()), (e1, e2) -> { 
    doStuff(e1); 
    doStuff(e2); 
});