2
votes

Comment diffuser une liste dans ce cas?

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


8 commentaires

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é et Obj comme valeur, puis dans la mergeFunction 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.


4 Réponses :


1
votes

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


3 commentaires

peut vouloir envelopper la sortie du pipeline de flux avec new ArrayList <> ()


Peut faire cela directement dans le collecteur: List result = list.stream (). Collect (Collectors.collectingAndThen (Collectors.toMap (…), m -> new ArrayList <> (m.values ​​()) );


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



1
votes

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


1 commentaires

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



0
votes

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


0 commentaires

0
votes

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.


0 commentaires