web-dev-qa-db-fra.com

Comparer deux listes et obtenir des différences

J'ai deux listes. Ils contiennent des objets de types différents, mais les deux types contiennent l'identifiant et le nom, et l'identifiant est ce sur quoi je compare. La première liste est extraite de la base de données et la deuxième liste est envoyée depuis le frontend. 

Ce que je dois faire, c'est les parcourir en boucle et trouver quel élément de la liste a été ajouté et lequel a été supprimé.

J'étais capable de le faire, mais le problème est que ça a l'air moche.

Disons que j'ai un objet appelé NameDTO qui peut avoir un identifiant et un nom. La liste deux est remplie avec ce type d’objets.

Voici comment je l'ai fait:

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

Cela fonctionne, je l'ai testé. Y a-t-il de meilleures solutions? Je pensais utiliser des sets pour pouvoir les comparer, y a-t-il un inconvénient à cela?

10
mirzak

Si je comprends bien, voici l'exemple de scénario:

  • éléments listOne [datab]: [A, B, C, D]
  • listTwo [front] items: [B, C, D, E, F]

et ce que vous devez obtenir comme effet est:

  • ajouté: [E, F]
  • supprimé: [A]

Tout d’abord, j’utiliserais un adaptateur de type ou étendrais les différents types d’une classe commune et de la méthode override à la méthode equals afin que vous puissiez les faire correspondre par id et name

Deuxièmement, il s’agit d’opérations très faciles sur les ensembles (vous pouvez utiliser les ensembles mais la liste convient également). Je recommande d'utiliser une bibliothèque: https://commons.Apache.org/proper/commons-collections/apidocs/org/Apache/commons/collections4/CollectionUtils.html

Et maintenant, fondamentalement:

  • ajouté est listTwo - listOne
  • supprimé est listOne - listTwo

et en utilisant le code Java:

  • ajouté: CollectionUtils.removeAll(listTwo, listOne)
  • supprimé: CollectionUtils.removeAll(listOne, listTwo)

Sinon, toutes les collections implémentant Collection ( Java Docs ) ont également la méthode removeAll, que vous pouvez utiliser.

12
Atais

Ce traitement de liste imbriquée n’est pas seulement vilain, il est aussi inefficace. Il est toujours préférable de stocker les identifiants d'une liste dans une variable Set permettant une recherche efficace, puis de traiter l'autre liste à l'aide de la variable Set. Ainsi, vous n’effectuez pas les opérations list1.size() fois list2.size(), mais les opérations list1.size() plus list2.size(), ce qui constitue une différence significative pour les listes plus volumineuses. Ensuite, comme les deux opérations sont fondamentalement identiques, il convient de les résumer en une méthode:

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

Cette méthode peut être utilisée comme

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

Etant donné que l'échange des deux listes nécessite que la méthode d'assistance soit indépendante des types d'élément, elle attend des fonctions permettant d'accéder à la propriété id, qui peuvent être spécifiées via des références de méthode. Ensuite, une fonction décrivant l'élément result est requise, qui est une fonction d'identité dans un cas (obtenant simplement la variable NamedDTO) et une expression lambda construisant une variable NamedDTO à partir de SomeObject dans l'autre.

L’opération elle-même est aussi simple que décrit ci-dessus, parcourez une liste, mappez l’identifiant et collectez-la dans une variable Set, puis parcourez l’autre liste, conservez uniquement les éléments dont l’identifiant n’est pas défini, associez le résultat. tapez et collectez dans une List.

6
Holger

Je propose une solution utilisant les flux Java 8:

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

Ceci est fondamentalement votre solution mais implémentée en utilisant des flux, ce qui raccourcira votre code et facilitera sa lecture

5
Kamil Banaszczyk

Si ces identifiants sont uniques, vous pouvez les mettre dans un hachage et trouver ainsi les identifiants qui vous intéressent:

    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]

J'ai créé les classes FromUI et FromDB avec un constructeur qui prend l'identifiant et le nom en entrée.

Je suppose également que si un élément est contenu dans uiSet, mais pas dans dbSet, il a été ajouté et inversement. 

2
Eugene