2009-09-02 10 views
35

Angenommen, ich habe 2 parallele Sammlungen, zB: eine Liste von Personen in einer List<String> und eine Liste ihres Alters in einer List<Int> in der gleichen Reihenfolge (so dass jeder Index in jeder Sammlung auf die gleiche Person verweist).Wie durch parallele Sammlungen am elegantesten iterieren?

Ich möchte beide Sammlungen gleichzeitig durchlaufen und den Namen und das Alter jeder Person holen und etwas damit machen. Bei Arrays ist dies leicht getan mit:

for (int i = 0; i < names.length; i++) { 
    do something with names[i] .... 
    do something with ages[i]..... 
} 

Was wäre die eleganteste Art und Weise (in Bezug auf Lesbarkeit und Geschwindigkeit), dies zu tun mit Sammlungen?

+0

mögliche Duplikat von [Bes t Möglichkeit, über zwei Listen gleichzeitig zu iterieren?] (http://stackoverflow.com/questions/3137944/best-way-to-iterate-over-tow-lists-simultan) –

+0

Diese Frage ist subtil anders: es geht darum, wie Iteriere _Outside_ eine Klasse über zwei Sammlungen _inside_ einer Klasse, die die Schnittstelle ein wenig ändert. –

Antwort

30

Ich würde ein neues Objekt erstellen, das die beiden kapselt. Wirf das im Array und iteriere darüber.

List<Person> 

Wo

public class Person { 
    public string name; 
    public int age; 
} 
+2

+1 - sehr vernünftiger Vorschlag, da es am meisten erweiterbar ist –

+1

Dies ist die offensichtliche Lösung - Objekte und Domäne richtig definieren: Alter und Name sind Attribute einer Person. Ordnen Sie sie zu und arbeiten Sie mit ihnen. – Precipitous

+0

Ich stimme zu, das ist die eleganteste Lösung. –

49
it1 = coll1.iterator(); 
it2 = coll2.iterator(); 
while(it1.hasNext() && it2.hasNext()) { 
    value1 = it1.next(); 
    value2 = it2.next(); 
    do something with it1 and it2; 
} 

Diese Version wird beendet, wenn die kürzere Sammlung erschöpft ist; alternativ könntest du weitermachen, bis der längere erschöpft ist, indem du value1 resp. Wert2 bis Null.

+0

danke für die Antwort, aber ich habe dich nicht verstanden, wenn wir weitermachen wollen, bis der längere erschöpft ist – Youssef

+4

Dies ist in der Tat die offizielle Antwort: "Verwenden Iterator anstelle der für jedes Konstrukt, wenn Sie: ... Iterieren Sie mehrere Sammlungen parallel. " [Die Java ™ Tutorials: Das Collection-Interface] (http://docs.oracle.com/javase/tutorial/collections/interfaces/collection.html) –

+1

Um mit einer Liste fertig zu werden, können Sie 'value1 = it1 verwenden .hasNächste()? it1.next(): null; '(dito für' it2'), um zu überprüfen, bevor das nächste Element abgerufen wird. Alternativ können Sie try-catch mit 'NoSuchElementException' verwenden, das etwas länger ist. –

7
for (int i = 0; i < names.length; ++i) { 
    name = names.get(i); 
    age = ages.get(i); 
    // do your stuff 
} 

Es ist nicht wirklich wichtig. Dein Code erhält keine Punkte für Eleganz. Mach es einfach so, dass es funktioniert. Und bitte nicht aufblasen.

+8

Seien Sie hier gewarnt, dass nicht alle Listen effiziente get (int) Implementierungen haben. Der Doppel-Iterator ist weniger wahrscheinlich ineffizient. – TREE

9

Sie können eine Schnittstelle für die es schaffen:

public interface ZipIterator<T,U> { 
    boolean each(T t, U u); 
} 

public class ZipUtils { 
    public static <T,U> boolean zip(Collection<T> ct, Collection<U> cu, ZipIterator<T,U> each) { 
    Iterator<T> it = ct.iterator(); 
    Iterator<U> iu = cu.iterator(); 
    while (it.hasNext() && iu.hasNext()) { 
     if (!each.each(it.next(), iu.next()) { 
     return false; 
     } 
    } 
    return !it.hasNext() && !iu.hasNext(); 
    } 
} 

Und dann haben Sie:

Collection<String> c1 = ... 
Collection<Long> c2 = ... 
zip(c1, c2, new ZipIterator<String, Long>() { 
    public boolean each(String s, Long l) { 
    ... 
    } 
}); 
+2

Und mit Java 8's lambdas kannst du es sogar so verwenden: 'zip (c1, c2, (s, l) -> ...)'. (Auch Ihr ZipIterator ist im Prinzip der gleiche wie ein BiConsumer.) –

+0

scheint die eleganteste Art mit Java 8 Lambdas zu sein. Beachten Sie, dass Sie den Typ von 'Collection' in' Iterable' ändern, um ihn allgemeiner zu gestalten – Sisyphus

0

Wie jeef3 vorgeschlagen, die Modellierung des wahren Domäne, anstatt sep zu halten Rate, implizit gekoppelte Listen ist der richtige Weg zu gehen ... wenn dies eine Option ist.

Es gibt verschiedene Gründe, warum Sie diesen Ansatz möglicherweise nicht anwenden können. Wenn ja ...

A. Sie können einen Callback-Ansatz verwenden, wie von cletus vorgeschlagen.

B. Sie können weiterhin einen Iterator verfügbar machen, der das Domänenobjektelement für jede zusammengesetzte Instanz verfügbar macht. Dieser Ansatz zwingt Sie nicht dazu, eine parallele Listenstruktur herumzuhalten.

C. Sie können eine Variante davon verwenden und eine RowSet-Stil-Cursor-API verwenden. Nehmen wir an, IPerson ist eine Schnittstelle, die Person beschreibt. Dann können wir tun:

public interface IPerson { 
    String getName(); 
    void setName(String name); 
    ... 
} 

public interface ICursor<T> { 
    boolean next(); 
    T current(); 
} 

private static class PersonCursor implements IPerson, ICursor<IPerson> { 
    private final List<String> _names; 
    ... 
    private int _index = -1; 

    PersonCursor(List<String> names, List<Integer> ages) { 
    _names = names; 
    ... 
    } 

    public boolean next() { 
    return ++_index < _names.size(); 
    } 

    public Person current() { 
    return this; 
    } 

    public String getName() { 
    return _names.get(_index); 
    } 

    public void setName(String name) { 
    _names.set(0, name); 
    } 

    ... 
} 

private List<String> _names = ...; 
private List<Integer> _ages = ...; 

Cursor<Person> allPeople() { 
    return new PersonCursor(_names, _ages); 
} 

Beachten Sie, dass der B-Ansatz auch Aktualisierungen vorgenommen werden, um zu unterstützen, zur Liste durch ein Domain-Schnittstelle Einführung und die Iterator Rückkehr mit Objekten ‚live‘.

0

Ich habe gerade gebucht diese Funktion in diesem similar question (die von Barth @Nils behauptet, ist kein Duplikat;)), aber es ist hier gleichermaßen anwendbar:

public static <L,R,M> List<M> zipLists(
    BiFunction<L,R,M> factory, Iterable<L> left, Iterable<R> right) { 
    Iterator<L> lIter = left.iterator(); 
    Iterator<R> rIter = right.iterator(); 
    ImmutableList.Builder<M> builder = ImmutableList.builder(); 

    while (lIter.hasNext() && rIter.hasNext()) { 
    builder.add(factory.apply(lIter.next(), rIter.next())); 
    } 

    // Most of the existing solutions fail to enforce that the lists are the same 
    // size. That is a *classic* source of bugs. Always enforce your invariants! 
    checkArgument(!lIter.hasNext(), 
     "Unexpected extra left elements: %s", ImmutableList.copyOf(lIter)); 
    checkArgument(!rIter.hasNext(), 
     "Unexpected extra right elements: %s", ImmutableList.copyOf(rIter)); 
    return builder.build(); 
} 

können Sie geben dann einen Fabrikbetrieb für die BiFunction , wie zum Beispiel eines Wertes Typ Konstruktor:

List<Person> people = zipLists(Person::new, names, ages); 

Wenn Sie wirklich nur über die Freunde zu wollen und eine Operation zu tun, anstatt eine neue Sammlung aufzubauen, könnten Sie vertausche die BiFunction für eine BiConsumer und habe die Funktion void zurückgeben.

1

Ich nahm @cletus Kommentar und es abit verbessert und das ist, was ich benutze:

public static <T,U> void zip(Collection<T> ct, Collection<U> cu, BiConsumer<T, U> consumer) { 
    Iterator<T> it = ct.iterator(); 
    Iterator<U> iu = cu.iterator(); 
    while (it.hasNext() && iu.hasNext()) { 
     consumer.accept(it.next(), iu.next()); 
    } 
} 

Verbrauch:

zip(list1, list2, (v1, v2) -> { 
    // Do stuff 
}); 
0

Während die eingereichten Lösungen sind richtig ziehe ich folgendes ein, weil es folgt die Führer von effektivem Java-Punkt 57: Minimieren Sie den Umfang der lokalen Variablen:

for (Iterator<String> i = lst1.iterator(), ii = lst2.iterator(); i.hasNext() && ii.hasNext();) { 
     String e1 = i.next(); 
     String e2 = ii.next(); 
     .... 
    }