2011-04-04 58 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); 
});