webentwicklung-frage-antwort-db.com.de

Vergleich zweier Listen und Unterschiede

Ich habe zwei Listen. Sie enthalten Objekte verschiedener Typen, aber beide Typen enthalten ID und Name. Ich vergleiche sie mit ID. Liste eins wird von DB abgerufen und Liste zwei wird vom Frontend gesendet. 

Ich muss sie durchlaufen und herausfinden, welches Listenelement neu hinzugefügt wurde und welches gelöscht wurde.

Ich konnte es schaffen, aber das Problem ist, dass es hässlich aussieht.

Angenommen, ich habe ein Objekt namens NameDTO, das eine ID und einen Namen haben kann. Liste zwei ist mit dieser Art von Objekten gefüllt.

So habe ich es gemacht:

final ArrayList<NamedDTO> added = new ArrayList<>();
final ArrayList<NamedDTO> removed = new ArrayList<>();

for(NamedDTO listTwoObject : listTwo) {
   boolean contained = false;
   for(SomeObject listOneObject : listOne) {
       if(listTwoObject.getId().equals(listOneObject.getId()) {
           contained = true;
       }
   }
   if(!contained) {
      added.add(listTwoObject);
   }
}

for(SomeObject listOneObject : listOne) {
   boolean contained = false;
   for(NamedDTO listTwoObject : listTwo) {
       if(listTwoObject.getId().equals(listOneObject.getId()) {
           contained = true;
       }
   }
   if(!contained) {
      removed.add(new NamedDTO(listOneObject.getId(), listOneObject.getName()));
  }
}

Das funktioniert, ich habe es getestet. Gibt es bessere Lösungen? Ich habe überlegt, Sets zu verwenden, um sie vergleichen zu können. Gibt es einen Nachteil?

10
mirzak

Wenn ich es richtig verstanden habe, ist dies das Beispielszenario:

  • listOne [datab] Elemente: [A, B, C, D]
  • listTwo [front] Artikel: [B, C, D, E, F]

und was Sie als Effekt erhalten müssen, ist:

  • hinzugefügt: [E, F]
  • gelöscht: [A]

Als Erstes würde ich einen Typadapter verwenden oder die verschiedenen Typen von einer gemeinsamen Klasse und override der equals-Methode erweitern, sodass Sie sie mit id und name abgleichen können.

Zweitens sind dies sehr einfache Operationen an Sets (Sie können Sets verwenden, aber die Liste ist auch in Ordnung). Ich empfehle die Verwendung einer Bibliothek: https://commons.Apache.org/proper/commons-collections/apidocs/org/Apache/commctions/collections4/CollectionUtils.html

Und jetzt im Grunde:

  • hinzugefügt ist listTwo - listOne
  • gelöscht wird listOne - listTwo

und Verwendung von Java-Code:

  • hinzugefügt: CollectionUtils.removeAll(listTwo, listOne)
  • gelöscht: CollectionUtils.removeAll(listOne, listTwo)

Ansonsten haben alle Sammlungen, die Collection ( Java Docs ) implementieren, auch die removeAll-Methode, die Sie verwenden können.

12
Atais

Diese Verarbeitung verschachtelter Listen ist nicht nur hässlich, sie ist ineffizient. Speichern Sie die IDs einer Liste immer besser in einer Set, um eine effiziente Suche zu ermöglichen. Verarbeiten Sie dann die andere Liste mit der Set. Auf diese Weise führen Sie keine list1.size() times list2.size()-Vorgänge aus, sondern list1.size() plus list2.size()-Vorgänge, was bei größeren Listen einen erheblichen Unterschied darstellt. Da beide Operationen grundsätzlich gleich sind, lohnt es sich, sie in eine Methode zu abstrahieren:

public static <A,B,R,ID> List<R> extract(
    List<A> l1, List<B> l2, Function<A,ID> aID, Function<B,ID> bID, Function<A,R> r) {

    Set<ID> b=l2.stream().map(bID).collect(Collectors.toSet());
    return l1.stream().filter(a -> !b.contains(aID.apply(a)))
             .map(r).collect(Collectors.toList());
}

Diese Methode kann als verwendet werden

List<NamedDTO> added   = extract(listTwo, listOne, NamedDTO::getId, SomeObject::getId,
                                 Function.identity());
List<NamedDTO> removed = extract(listOne, listTwo, SomeObject::getId, NamedDTO::getId,
                                 so -> new NamedDTO(so.getId(), so.getName()));

Da für das Vertauschen der beiden Listen die Hilfsmethode unabhängig von den Elementtypen sein muss, werden Funktionen für den Zugriff auf die id -Eigenschaft erwartet, die über Methodenreferenzen angegeben werden können. Dann ist eine Funktion erforderlich, die das Ergebniselement beschreibt. In einem Fall handelt es sich um eine Identitätsfunktion (es wird lediglich die Variable NamedDTO abgerufen) und in dem anderen Fall wird ein Lambda-Ausdruck verwendet, der eine Variable NamedDTO aus SomeObject erstellt.

Die Operation selbst ist so unkompliziert wie oben beschrieben, iteriert über eine Liste, ordnet die ID zu und sammelt sie in eine Set, führt dann die andere Liste durch, behält nur Elemente bei, deren ID nicht in der Menge enthalten ist, ordnet sie dem Ergebnis zu tippen und in eine List sammeln.

6
Holger

Ich schlage eine Lösung mit Java 8-Streams vor:

    ArrayList<ObjOne> list = new ArrayList<>(Arrays.asList(new ObjOne("1","1"),new ObjOne("3","3"),new ObjOne("2","2")));
    ArrayList<ObjTwo> list2 = new ArrayList<>(Arrays.asList(new ObjTwo("1","1"),new ObjTwo("3","3"),new ObjTwo("4","4")));

    List<ObjOne> removed = list.stream().filter(o1 -> list2.stream().noneMatch(o2 -> o2.getId().equals(o1.getId())))
            .collect(Collectors.toList());
    System.out.print("added ");
    removed.forEach(System.out::println);

    List<ObjTwo> added = list2.stream().filter(o1 -> list.stream().noneMatch(o2 -> o2.getId().equals(o1.getId())))
             .collect(Collectors.toList());

    System.out.print("removed ");
    added.forEach(System.out::println);

Dies ist im Grunde Ihre Lösung, die jedoch mithilfe von Streams implementiert wird, wodurch der Code kürzer und einfacher zu lesen ist

5

Wenn diese IDs eindeutig sind, können Sie sie in ein HashSet einfügen und somit die IDs finden, an denen Sie interessiert sind:

    Set<Integer> uiList = Stream.of(new FromUI(1, "db-one"), new FromUI(2, "db-two"), new FromUI(3, "db-three"))
            .map(FromUI::getId)
            .collect(Collectors.toCollection(HashSet::new));
    Set<Integer> dbList = Stream.of(new FromDB(3, "ui-one"), new FromDB(5, "ui-five"))
            .map(FromDB::getId)
            .collect(Collectors.toCollection(HashSet::new));

    uiList.removeIf(dbList::remove);

added/uiSet :   [1,2]
removed/dbSet : [5]

Ich habe FromUI- und FromDB-Klassen mit einem Konstruktor erstellt, der die ID und den Namen als Eingabe verwendet.

Ich gehe auch davon aus, dass wenn ein Element in der uiSet enthalten ist, aber nicht in der dbSet ist, und umgekehrt. 

2
Eugene