2013-08-17 18 views
43

Say Abflachen habe ich ein Map<? extends Object, List<String>>eine Sammlung

ich die Werte der Karte leicht genug bekommen können, und iterieren sie einen einzigen List<String> zu produzieren.

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

Gibt es eine Möglichkeit, es in einem Schuss zu glätten?

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

Was ist mit der Verwendung einer Schleife falsch? –

+1

@JoshM Nichts. Aber wenn ich etwas eingebautes verwenden kann, sollte ich. Normalerweise kenne ich die Antworten auf diese Art von Fragen, aber dieses Mal nicht, also dachte ich, ich würde fragen. –

Antwort

45

Wenn Sie Java 8 verwenden, könnten Sie so etwas tun:

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

Wenn ich nicht falsch liege, ist dies eigentlich nicht zu empfehlen - https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html Siehe Seite Nebenwirkungen. > Im Allgemeinen werden Nebenwirkungen von Verhaltensparametern auf streaming-Operationen vermieden, da sie oft zu unwissentlichen Verstößen gegen das Erfordernis der Staatenlosigkeit sowie zu anderen Risiken der Thread-Sicherheit führen können. In diesem Fall ist es besser, 'Collector.toList()' –

5

Nein, es gibt keine kürzere Methode. Sie müssen eine Schleife verwenden.

Update Apr 2014: Java 8 ist endlich herausgekommen. In der neuen Version können Sie die Methode Iterable.forEach verwenden, um eine Sammlung ohne explizite Schleife zu durchlaufen.

Update Nov 2017: Diese Frage wurde zufällig bei der Suche nach einer modernen Lösung gefunden. Am Ende gehen mit reduce:

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

Dies vermeidet je nach wandelbar externen Zustand forEach(someList::addAll) der Aufwand für flatMap(List::stream).

0

Wenn Sie nur durch Werte zu durchlaufen möchten, können Sie all diese addAll Methoden vermeiden.

Alles, was Sie tun müssen, ist eine Klasse schreiben, die Ihre Karte kapselt, und das implementiert das 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

Wenn Sie Eclipse Collections verwenden, können Sie Iterate.flatten() verwenden.

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() ist ein Spezialfall des allgemeineren RichIterable.flatCollect().

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

Hinweis: Ich bin ein Committer für Eclipse Collections.

51

Verwendung von Java 8 und wenn Sie nicht lieber eine List Instanz selbst, wie in der vorgeschlagenen (und akzeptiert) Lösung

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

Man könnte es instanziiert tun alle von Streaming mit dieser Aussage:

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

Übrigens sollte es interessant sein zu wissen, dass auf Java 8 die akzeptierte Version in der Tat die schnellste ist. Es hat ungefähr das gleiche Timing wie ein

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

und ist viel schneller als die reine Streaming-Lösung. Hier ist mein kleiner Testcode. Ich benenne es ausdrücklich nicht als Benchmark, um die daraus resultierende Diskussion von Benchmark-Fehlern zu vermeiden. ;) Ich mache jeden Test zweimal, um hoffentlich eine vollständige kompilierte Version zu bekommen.

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

Und hier sind die Ergebnisse:

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 

bearbeiten 2016.05.24 (zwei Jahre nach):

den gleichen Test Ausführen einer tatsächlichen Java 8 Version mit (U92) auf der gleichen Maschine:

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 

Es scheint, dass es eine spee ist dup für die sequentielle Verarbeitung von Streams und einen noch größeren Overhead für parallele Streams.

+7

zu verwenden. Tatsächlich ist das Schreiben von 'flatMap (Collections :: stream)' im Vergleich zu 'flatMap (c -> c.stream()) '. –

+4

Es ist 'Collection :: stream', das' Collections' verwendet in meinem Test nicht. – BAER

+0

Welcher schneller ist, hängt wahrscheinlich auch von Ihren Eingabedaten ab. Ich wäre nicht überrascht, wenn die Stream-Version schneller wäre, wenn die Eingabe viele kleine Listen wären. Wenn es clever genug ist, kann es einen Speicher für das gesamte Ergebnis auf einmal zuweisen, während die forEach-Version es einige Male neu zuweisen muss. – danadam

25

Bei der Suche nach "Java 8 flatten" ist dies die einzige Erwähnung. Und es geht auch nicht darum, den Stream zu verflachen. Also für große gut ich es gerade hier lasse

.flatMap(Collection::stream) 

Ich bin überrascht, auch niemand gleichzeitige Java 8 Antwort auf ursprüngliche Frage, die

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

Ich glaube, dass '.collect (ArrayList :: neu, ArrayList :: addAll, ArrayList :: addAll);' ist die richtige Antwort. 'flatMap()' ist in dieser Situation nicht nützlich. 'flatMap()' kann nützlich sein, wenn Sie eine andere Methode für das Argument aufrufen müssen, bevor Sie einen Stream erhalten (d. h. die 'stream()' Methode aufrufen). Hier haben wir jedoch bereits einen Verweis auf ein Objekt, für das wir einen Stream direkt abrufen können. –

0

schöne Lösung A für den Unterfall einer Karte gegeben hat von Maps ist, wenn möglich, die Daten in Guava Table zu speichern.

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

So zum Beispiel ein Map<String,Map<String,String>> durch Table<String,String,String> ersetzt, die bereits werden abgeflacht. In der Tat, sagen die docs, dass HashBasedTable, Table ‚s Hash Implementierung, im Wesentlichen durch ein HashMap<R, HashMap<C, V>> unterstützt wird

6

von einem Kollegen Empfehlung:

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

Ich mag es besser als foreach().

+1

Sie können e -> e.stream() durch die Methodenreferenz von List :: stream ersetzen. Sollte etwas schneller sein. –