2013-08-17 31 views
43

說我有一個Map<? extends Object, List<String>>平展收集

我可以得到地圖的價值很輕鬆了,和遍歷它來產生一個List<String>

for (List<String> list : someMap.values()) { 
     someList.addAll(list); 
    } 

有沒有辦法將它壓扁一次?

List<String> someList = SomeMap.values().flatten(); 
+0

使用循環有什麼問題? –

+1

@JoshM一無所有。但是,如果我可以使用內置的東西,我應該。我通常知道這些類型的問題的答案,但這次我不知道,所以我想我會問。 –

回答

45

如果您使用的是Java 8,你可以做這樣的事情:

someMap.values().forEach(someList::addAll); 
+3

如果我沒有錯,這實際上不建議 - https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html請參閱side-effect的部分。 >一般來說,行爲參數對流操作的副作用是不鼓勵的,因爲它們通常會導致無意中違反無狀態要求以及其他線程安全危害。因此,在這種情況下,最好使用'Collector.toList()' –

5

不,沒有更短的方法。你必須使用循環。

2014年4月更新: Java 8終於問世了。在新版本中,您可以使用Iterable.forEach方法遍歷集合而不使用顯式循環。

更新2017年11月:找到一個現代的解決方案時偶然發現這個問題。與reduce導致去:

someMap.values().stream().reduce(new ArrayList(), (accum, list) -> { 
    accum.addAll(list); 
    return accum; 
}): 

這避免了視forEach(someList::addAll)flatMap(List::stream)開銷可變外部狀態。

0

如果你只是想通過值迭代,你能避免所有這些方法的addAll。

所有您需要做的是寫一個封裝你的地圖類,並實現了Iterator:

public class ListMap<K,V> implements Iterator<V> 
{ 
    private final Map<K,List<V>> _map; 
    private Iterator<Map.Entry<K,List<V>>> _it1 = null; 
    private Iterator<V> _it2 = null; 

    public ListMap(Map<K,List<V>> map) 
    { 
    _map = map; 
    _it1 = map.entrySet().iterator(); 
    nextList(); 
    } 

    public boolean hasNext() 
    { 
    return _it2!=null && _it2.hasNext(); 
    } 

    public V next() 
    { 
    if(_it2!=null && _it2.hasNext()) 
    { 
     return _it2.next(); 
    } 
    else 
    { 
     throw new NoSuchElementException(); 
    } 
    nextList(); 
    } 

    public void remove() 
    { 
    throw new NotImplementedException(); 
    } 

    private void nextList() 
    { 
    while(_it1.hasNext() && !_it2.hasNext()) 
    { 
     _it2 = _it1.next().value(); 
    } 
    } 
} 
6

如果您使用Eclipse Collections,您可以使用Iterate.flatten()

MutableMap<String, MutableList<String>> map = Maps.mutable.empty(); 
map.put("Even", Lists.mutable.with("0", "2", "4")); 
map.put("Odd", Lists.mutable.with("1", "3", "5")); 
MutableList<String> flattened = Iterate.flatten(map, Lists.mutable.empty()); 
Assert.assertEquals(
    Lists.immutable.with("0", "1", "2", "3", "4", "5"), 
    flattened.toSortedList()); 

flatten()是更一般的RichIterable.flatCollect()一個特例。

MutableList<String> flattened = 
    map.flatCollect(x -> x, Lists.mutable.empty()); 

注意:我是Eclipse集合的提交者。

51

使用Java 8,如果你不喜歡自己的建議(並接受)的解決方案,以實例化一個List例如,像

someMap.values().forEach(someList::addAll); 

您也可以用這個說法流做這一切:

List<String> someList = map.values().stream().flatMap(c -> c.stream()).collect(Collectors.toList()); 

順便說一句,有趣的是,在Java 8上,接受的版本似乎確實是最快的。它與

for (List<String> item : someMap.values()) ... 

大約相同,並且是一種比純流解決方案更快的方式。這是我的小測試代碼。我明確地不把它命名爲基準,以避免由此導致的基準缺陷的討論。 ;)我做了兩次測試,希望得到完整的編譯版本。

Map<String, List<String>> map = new HashMap<>(); 
    long millis; 

    map.put("test", Arrays.asList("1", "2", "3", "4")); 
    map.put("test2", Arrays.asList("10", "20", "30", "40")); 
    map.put("test3", Arrays.asList("100", "200", "300", "400")); 

    int maxcounter = 1000000; 

    System.out.println("1 stream flatmap"); 
    millis = System.currentTimeMillis(); 
    for (int i = 0; i < maxcounter; i++) { 
     List<String> someList = map.values().stream().flatMap(c -> c.stream()).collect(Collectors.toList()); 
    } 
    System.out.println(System.currentTimeMillis() - millis); 

    System.out.println("1 parallel stream flatmap"); 
    millis = System.currentTimeMillis(); 
    for (int i = 0; i < maxcounter; i++) { 
     List<String> someList = map.values().parallelStream().flatMap(c -> c.stream()).collect(Collectors.toList()); 
    } 
    System.out.println(System.currentTimeMillis() - millis); 

    System.out.println("1 foreach"); 
    millis = System.currentTimeMillis(); 
    for (int i = 0; i < maxcounter; i++) { 
     List<String> mylist = new ArrayList<String>(); 
     map.values().forEach(mylist::addAll); 
    } 
    System.out.println(System.currentTimeMillis() - millis);   

    System.out.println("1 for"); 
    millis = System.currentTimeMillis(); 
    for (int i = 0; i < maxcounter; i++) { 
     List<String> mylist = new ArrayList<String>(); 
     for (List<String> item : map.values()) { 
      mylist.addAll(item); 
     } 
    } 
    System.out.println(System.currentTimeMillis() - millis); 


    System.out.println("2 stream flatmap"); 
    millis = System.currentTimeMillis(); 
    for (int i = 0; i < maxcounter; i++) { 
     List<String> someList = map.values().stream().flatMap(c -> c.stream()).collect(Collectors.toList()); 
    } 
    System.out.println(System.currentTimeMillis() - millis); 

    System.out.println("2 parallel stream flatmap"); 
    millis = System.currentTimeMillis(); 
    for (int i = 0; i < maxcounter; i++) { 
     List<String> someList = map.values().parallelStream().flatMap(c -> c.stream()).collect(Collectors.toList()); 
    } 
    System.out.println(System.currentTimeMillis() - millis); 

    System.out.println("2 foreach"); 
    millis = System.currentTimeMillis(); 
    for (int i = 0; i < maxcounter; i++) { 
     List<String> mylist = new ArrayList<String>(); 
     map.values().forEach(mylist::addAll); 
    } 
    System.out.println(System.currentTimeMillis() - millis);   

    System.out.println("2 for"); 
    millis = System.currentTimeMillis(); 
    for (int i = 0; i < maxcounter; i++) { 
     List<String> mylist = new ArrayList<String>(); 
     for (List<String> item : map.values()) { 
      mylist.addAll(item); 
     } 
    } 
    System.out.println(System.currentTimeMillis() - millis); 

而且這裏的結果:

1 stream flatmap 
468 
1 parallel stream flatmap 
1529 
1 foreach 
140 
1 for 
172 
2 stream flatmap 
296 
2 parallel stream flatmap 
1482 
2 foreach 
156 
2 for 
141 

編輯2016年5月24日(兩年後):

用實際的Java版本8運行在相同的測試(U92 )在同一臺機器上:

1 stream flatmap 
313 
1 parallel stream flatmap 
3257 
1 foreach 
109 
1 for 
141 
2 stream flatmap 
219 
2 parallel stream flatmap 
3830 
2 foreach 
125 
2 for 
140 

看來有一個spee用於流的順序處理以及並行流的更大開銷。

+7

雖然實際上要延長几個字符,但在flatMap(c - > c.stream ())'。 –

+4

它是'Collection :: stream',使用'Collections'不會在我的測試中做。 – BAER

+0

哪一個更快可能還取決於您的輸入數據。如果在輸入很多小列表時流版本更快,我不會感到驚訝。如果它足夠聰明,它有機會一次爲整個結果分配一個內存,而forEach版本將不得不重新分配它幾次。 – danadam

25

當搜索「java 8 flatten」時,這是唯一提及的。而且它也不是關於扁平化。因此,對於偉大的好,我剛剛離開這裏

.flatMap(Collection::stream) 

我也感到驚訝沒有人給Java併發8回答原來的問題是

.collect(ArrayList::new, ArrayList::addAll, ArrayList::addAll); 
+2

我相信'.collect(ArrayList :: new,ArrayList :: addAll,ArrayList :: addAll);'是正確的答案。 'flatMap()'在這種情況下沒有用。如果你需要在獲得一個流之前調用另一個參數方法(即調用'stream()'方法),'flatMap()'會很有用。然而,在這裏,我們已經有了一個對象,我們可以直接檢索一個流。 –

0

爲Map的子情況一個很好的解決方案的地圖是如果可能的話,將數據存儲在Guava的Table中。

https://github.com/google/guava/wiki/NewCollectionTypesExplained#table

因此,例如,一個Map<String,Map<String,String>>Table<String,String,String>這已經是flattend取代。事實上,文檔說HashBasedTableTable的哈希實現,基本上是由一個HashMap<R, HashMap<C, V>>

6

一個同事建議的支持:

listOfLists.stream().flatMap(e -> e.stream()).collect(Lists.toList()) 

我喜歡它比的forEach更好的()。

+1

您可以使用List :: stream的方法引用替換e - > e.stream()。應該快一點。 –