J'ai une liste d'objets en Java avec deux timeStamp, comme:
Obj (TimeStamp ts, TimeStamp generationTs, int value).
À la fin, je ne veux pas de deux éléments dans la liste avec les mêmes ts. S'il y en a, je veux ne garder que celui avec la génération la plus récente.
En fait, j'ai ce code, ça marche, mais j'aimerais savoir si avec les flux, je ne peux pas faire quelque chose de mieux ?
{("2019-05-02T09:00:00Z", "2019-05-02T21:00:00Z", 1), ("2019-05-02T09:30:00Z", "2019-05-02T22:00:00Z", 5), ("2019-05-02T10:00:00Z", "2019-05-02T22:00:00Z", 6) ("2019-05-02T10:30:00Z", "2019-05-02T21:00:00Z", 4) }
Si la liste est:
{("2019-05-02T09:00:00Z", "2019-05-02T21:00:00Z", 1), ("2019-05-02T09:30:00Z", "2019-05-02T21:00:00Z", 2), ("2019-05-02T10:00:00Z", "2019-05-02T21:00:00Z", 3), ("2019-05-02T10:30:00Z", "2019-05-02T21:00:00Z", 4), ("2019-05-02T09:30:00Z", "2019-05-02T22:00:00Z", 5), ("2019-05-02T10:00:00Z", "2019-05-02T22:00:00Z", 6) }
Elle doit renvoyer:
list.sort(Collections.reverseOrder()); List<Obj> returnedList = Lists.newArrayList(); if (!list.isEmpty()) { returnedList.add(list.get(0)); Iterator<Obj> i = list.iterator(); while (i.hasNext()) { Obj lastObj = returnedList.get(returnedList.size() - 1); Obj nextObj = i.next(); if (!lastObj.getTs().isEqual(nextObj.getTs())) { returnedList.add(nextObj); } else { if (lastObj.getGenerationTs().isBefore(nextObj.getGenerationTs())) { returnedList.remove(lastObj); returnedList.add(nextObj); } } } }
4 Réponses :
Vous pouvez certainement le faire en utilisant Stream
en utilisant un collecteur de cartes, puis en obtenant les valeurs
List<Obj> result = list.stream() .collect(Collectors.collectingAndThen( Collectors.toMap(Obj::getTimeStamp, Function.identity(), (o1, o2) -> o1.getGenerationTs().isBefore(o2.getGenerationTs()) ? o2 : o1), m -> new ArrayList<>(m.values())));
Ou encore plus court:
Collection<Obj> objects = list.stream() .collect(Collectors.toMap(Obj::getTimeStamp, Function.identity(), (o1, o2) -> o1.getGenerationTs().isBefore(o2.getGenerationTs()) ? o2 : o1)) .values(); List<Obj> listOfObjects = new ArrayList<>(objects);
peut vouloir envelopper la sortie du pipeline de flux avec new ArrayList <> ()
Peut faire cela directement dans le collecteur: List
cette solution est presque comme ce que je commençais à obtenir, en mieux, merci! J'ai seulement ajouté .sorted (Comparator.comparing (LoadCurvePointBO :: getTs))
pour le trier par ts asc
vous pouvez essayer comme ceci:
list.stream().collect(Collectors.groupingBy( Obj::getTs, Collectors.maxBy(Comparator.comparing(Obj::getGenerationTs)) )).values().stream() .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList());
Des options plus complètes comme @Naman l'a indiqué dans le commentaire:
Map<TimeStamp, Optional<Obj>> result = list.stream().collect(Collectors.groupingBy( Obj::getTs, Collectors.maxBy(Comparator.comparing(Obj::getGenerationTs)) ));
Ce que OP pourrait rechercher pourrait être quelque chose comme return list.stream () .collect (Collectors.groupingBy (Obj :: getTs, Collectors.maxBy (Comparator.comparing (Obj :: getGeneratedTs)))). Values ( ) .stream () .filter (Facultatif :: isPresent) .map (Facultatif :: get) .collect (Collectors.toList ());
Voici une façon de le faire.
Regrouper l'un du premier horodatage puis utiliser maxBy pour trouver l'objet avec l'horodatage de dernière génération. Enfin, triez le premier horodatage et imprimez-le.
Le fait que maxBy produise un Optional est un peu moche, mais je n'ai pas trouvé de moyen de l'éviter.
import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.maxBy; import java.time.Instant; import java.util.Optional; import java.util.stream.Stream; import org.junit.jupiter.api.Test; public class SortTest { @Test public void t() { final Stream<Obj> s = Stream.of(new Obj("2019-05-02T09:00:00Z", "2019-05-02T21:00:00Z", 1), new Obj("2019-05-02T09:30:00Z", "2019-05-02T21:00:00Z", 2), new Obj("2019-05-02T10:00:00Z", "2019-05-02T21:00:00Z", 3), new Obj("2019-05-02T10:30:00Z", "2019-05-02T21:00:00Z", 4), new Obj("2019-05-02T09:30:00Z", "2019-05-02T22:00:00Z", 5), new Obj("2019-05-02T10:00:00Z", "2019-05-02T22:00:00Z", 6)); s.collect(groupingBy(o -> o.ts, maxBy((o1, o2) -> o1.generationTs.compareTo(o2.generationTs)))) .values() .stream() .map(Optional::get) .sorted((o1, o2) -> o1.ts.compareTo(o2.ts)) .forEach(System.out::println); } private class Obj { Instant ts; Instant generationTs; int i; Obj(final String ts, final String generationTs, final int i) { this.ts = Instant.parse(ts); this.generationTs = Instant.parse(generationTs); this.i = i; } @Override public String toString() { return String.format("%s %s %d", ts, generationTs, i); } } }
Si vous avez déjà une liste triée (décroissante par generationTs
), comme vous l'avez fait dans votre exemple de code, vous pouvez utiliser un HashSet
et Collection.removeIf ()
pour supprimer tous les horodatages en double de cette liste:
Obj[ts=2019-05-02T09:00:00Z, generationTs=2019-05-02T21:00:00Z, value=1] Obj[ts=2019-05-02T09:30:00Z, generationTs=2019-05-02T22:00:00Z, value=5] Obj[ts=2019-05-02T10:00:00Z, generationTs=2019-05-02T22:00:00Z, value=6] Obj[ts=2019-05-02T10:30:00Z, generationTs=2019-05-02T21:00:00Z, value=4]
Avec cette solution, vous n'avez pas à créer une nouvelle liste, il vous suffit de modifier la liste que vous avez. L'ensemble stocke toutes les clés que vous souhaitez conserver dans la liste. Comme la liste est triée, les objets les plus récents sont conservés dans la liste, tandis que les autres valeurs sont supprimées.
Le résultat avec les données que vous avez partagées sera:
list.sort(Comparator.comparing(Obj::getTs) .thenComparing(Comparator.comparing(Obj::getGenerationTs) .reversed())); Set<Timestamp> keys = new HashSet<>(); list.removeIf(o -> !keys.add(o.getTs()));
Si vous disposez déjà d'une liste triée, cette solution devrait être l'une des plus rapides.
Essayez de rechercher
List.removeIf
, mais vous devez vous assurer que vous ne supprimez pas l'élément pour sa propre existence.@Naman: removeIf () fonctionne quand vous avez un prédicat que vous connaissez .. Dans mon cas, je veux supprimer l'élément x si: `list (x) .getTs () = = list (y) .getTs () && list ( x) .getGenerationTs (). isBefore (list (y) .getGenerationTs () ) `
Je préférerais cependant ne pas emprunter la route du flux ici. Pourtant, un moyen possible est de collecter toMap, avec
timestamp
comme clé etObj
comme valeur, puis dans lamergeFunction
vous assurer que vous remplissez votre < condition code> generatedTs . Enfin, récupérez les valeurs de cette carte qui vous intéresseraient.Mieux
peut être très subjectif. Si c'est purement académique, très bien. Mais si votre code fonctionne, ne le «corrigez» pas.vous vous demandez simplement: vous ne faites rien qui pourrait être mieux fait en utilisant SQL, non?
@WJS Je suis d'accord, mais ce n'est absolument pas académique. Il s'agit simplement de performances! Je pensais qu'avec stream, j'obtiendrais une exécution plus rapide. Et je ne pense pas que la solution actuelle soit la meilleure. Mais si la meilleure solution n'est pas avec stream, je ne l'utiliserai pas! Peut-être que ma question n'a pas été posée à juste titre ...
Les données @Roland proviennent de CassandraDB, et cql n'est pas aussi permissif que sql.
@yahal Rien de mal avec votre question. Mais la plupart du temps, les optimisations de code improvisées n'améliorent pas vraiment beaucoup ou n'améliorent pas ce qui doit être amélioré. Si quelqu'un est vraiment concerné, une fois que c'est fait, lancez un profileur dessus.