40 votes

Comment mieux comparer deux collections en Java et agir sur elles ?

J'ai deux collections du même objet, Collection<Foo> oldSet y Collection<Foo> newSet . La logique requise est la suivante :

  • si foo est dans(*) oldSet mais pas newSet appel doRemove(foo)
  • sinon si foo n'est pas dans oldSet mais en newSet appel doAdd(foo)
  • sinon si foo est dans les deux collections mais modifié, appelez doUpdate(oldFoo, newFoo)
  • sinon si !foo.activated && foo.startDate >= now appel doStart(foo)
  • sinon si foo.activated && foo.endDate <= now appel doEnd(foo)

(*) "dans" signifie que l'identifiant unique correspond, pas nécessairement le contenu.

Le code actuel (hérité) effectue de nombreuses comparaisons pour déterminer removeSet , addSet , updateSet , startSet y endSet puis boucle pour agir sur chaque élément.

Le code est assez désordonné (en partie parce que j'ai déjà laissé de côté une partie de la logique spaghetti) et j'essaie de le remanier. Quelques informations supplémentaires :

  • Pour autant que je sache, le oldSet y newSet sont en fait soutenus par ArrayList
  • Chaque ensemble contient moins de 100 éléments, probablement 20 au maximum.
  • Ce code est appelé fréquemment (mesuré en millions/jour), bien que les ensembles diffèrent rarement.

Mes questions :

  • Si je convertis oldSet y newSet en HashMap<Foo> (l'ordre n'a pas d'importance ici), avec les ID comme clés, le code serait-il plus facile à lire et à comparer ? Quelle est la perte de temps et de performance mémoire lors de la conversion ?
  • Le fait d'itérer les deux ensembles et d'effectuer l'opération appropriée serait-il plus efficace et plus concis ?

34voto

user143081 Points 289

La bibliothèque commons.collections d'Apache possède une classe CollectionUtils qui fournit des méthodes faciles à utiliser pour la manipulation et la vérification des collections, comme l'intersection, la différence et l'union.

Les documents de l'API org.apache.commons.collections.CollectionUtils sont les suivants aquí .

20voto

Vitalii Fedorenko Points 17469

Vous pouvez essayer le Sets classe de Goyave :

Set<String> intersection = Sets.intersection(set1, set2);
Set<String> difference = Sets.difference(set1, set2);
Set<String> symmetricDifference = Sets.symmetricDifference(set1, set2);
Set<String> union = Sets.union(set1, set2);

10voto

martinatime Points 1863

J'ai créé une approximation de ce que je pense que vous recherchez en utilisant simplement le Collections Framework en Java. Franchement, je pense que c'est probablement exagéré, comme le souligne @Mike Deck. Pour un si petit ensemble d'éléments à comparer et à traiter, je pense que les tableaux seraient un meilleur choix d'un point de vue procédural, mais voici ma solution pseudo-codée (parce que je suis paresseux). Je suppose que la classe Foo est comparable sur la base de son identifiant unique et non de toutes les données qu'elle contient :

Collection<Foo> oldSet = ...;
Collection<Foo> newSet = ...;

private Collection difference(Collection a, Collection b) {
Collection result = a.clone();
result.removeAll(b)
return result;
}

private Collection intersection(Collection a, Collection b) {
Collection result = a.clone();
result.retainAll(b)
return result;
}

public doWork() {
    // if foo is in(*) oldSet but not newSet, call doRemove(foo)
    Collection removed = difference(oldSet, newSet);
    if (!removed.isEmpty()) {
        loop removed {
            Foo foo = removedIter.next();
            doRemove(foo);
        }
    }
//else if foo is not in oldSet but in newSet, call doAdd(foo)
Collection added = difference(newSet, oldSet);
if (!added.isEmpty()) {
    loop added  {
        Foo foo = addedIter.next();
        doAdd(foo);
    }
}

// else if foo is in both collections but modified, call doUpdate(oldFoo, newFoo)
Collection matched = intersection(oldSet, newSet);
Comparator comp = new Comparator() {
    int compare(Object o1, Object o2) {
        Foo f1, f2;
        if (o1 instanceof Foo) f1 = (Foo)o1;
        if (o2 instanceof Foo) f2 = (Foo)o2;
        return f1.activated == f2.activated ? f1.startdate.compareTo(f2.startdate) == 0 ? ... : f1.startdate.compareTo(f2.startdate) : f1.activated ? 1 : 0;
    }

    boolean equals(Object o) {
        // equal to this Comparator..not used
    }
}
loop matched {
    Foo foo = matchedIter.next();
    Foo oldFoo = oldSet.get(foo);
    Foo newFoo = newSet.get(foo);
    if (comp.compareTo(oldFoo, newFoo ) != 0) {
        doUpdate(oldFoo, newFoo);
    } else {
        //else if !foo.activated && foo.startDate >= now, call doStart(foo)
        if (!foo.activated && foo.startDate >= now) doStart(foo);

        // else if foo.activated && foo.endDate <= now, call doEnd(foo)
        if (foo.activated && foo.endDate <= now) doEnd(foo);
    }
}
}

Pour ce qui est de vos questions : Si je convertis oldSet et newSet en HashMap (l'ordre n'est pas un problème ici), avec les ID comme clés, cela rendrait-il le code plus facile à lire et plus facile à comparer ? Quelle est la perte de temps et de performance mémoire liée à la conversion ? Je pense que vous rendriez probablement le code plus lisible en utilisant une Map MAIS... vous utiliseriez probablement plus de mémoire et de temps pendant la conversion.

Est-ce que itérer les deux ensembles et effectuer l'opération appropriée serait plus efficace et concis ? Oui, ce serait le meilleur des deux mondes, surtout si vous suivez le conseil de @Mike Sharek de créer votre propre liste avec les méthodes spécialisées ou de suivre quelque chose comme le modèle de conception Visitor pour parcourir votre collection et traiter chaque élément.

2voto

bibix Points 1915

Je passerais aux listes et je résoudrais le problème de cette façon :

  1. Trier les deux listes par id en ordre ascendant en utilisant la fonction personnalisée Comparateur si les objets dans les listes ne sont pas Comparable
  2. Itérer sur les éléments des deux listes comme dans la phase de fusion de l'application algorithme de tri par fusion mais au lieu de fusionner des listes, vous vérifiez votre logique.

Le code serait plus ou moins comme ceci :

/* Main method */
private void execute(Collection<Foo> oldSet, Collection<Foo> newSet) {
  List<Foo> oldList = asSortedList(oldSet);
  List<Foo> newList = asSortedList(newSet);

  int oldIndex = 0;
  int newIndex = 0;
  // Iterate over both collections but not always in the same pace
  while( oldIndex < oldList.size() 
      && newIndex < newIndex.size())  {
    Foo oldObject = oldList.get(oldIndex);
    Foo newObject = newList.get(newIndex);

    // Your logic here
    if(oldObject.getId() < newObject.getId()) {
      doRemove(oldObject);
      oldIndex++;
    } else if( oldObject.getId() > newObject.getId() ) {
      doAdd(newObject);
      newIndex++;
    } else if( oldObject.getId() == newObject.getId() 
            && isModified(oldObject, newObject) ) {
      doUpdate(oldObject, newObject);
      oldIndex++;
      newIndex++;
    } else {
      ... 
    }
  }// while

  // Check if there are any objects left in *oldList* or *newList*

  for(; oldIndex < oldList.size(); oldIndex++ ) {
    doRemove( oldList.get(oldIndex) );  
  }// for( oldIndex )

  for(; newIndex < newList.size(); newIndex++ ) {
    doAdd( newList.get(newIndex) );
  }// for( newIndex ) 
}// execute( oldSet, newSet )

/** Create sorted list from collection 
    If you actually perform any actions on input collections than you should 
    always return new instance of list to keep algorithm simple.
*/
private List<Foo> asSortedList(Collection<Foo> data) {
  List<Foo> resultList;
  if(data instanceof List) {
     resultList = (List<Foo>)data;
  } else {
     resultList = new ArrayList<Foo>(data);
  }
  Collections.sort(resultList)
  return resultList;
}

2voto

Sharan Rajendran Points 512

Je pense que la façon la plus simple de le faire est d'utiliser l'api apache collections - CollectionUtils.subtract(list1,list2) tant que les listes sont du même type.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X