Как стрим сортировать список в таком случае? - PullRequest
2 голосов
/ 07 мая 2019

У меня есть список объектов в Java с двумя timeStamp, например:

Obj (TimeStamp ts, TimeStamp generationTs, int value).

В конце я не хочудва пункта в списке с одинаковым тс.Если да, я хочу оставить только тот, у которого последнее поколение T.

На самом деле, у меня есть этот код, он работает, но я хотел бы знать, если с потоками я не могу что-то сделатьлучше ?

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

Если список:

{("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) }

Он должен вернуть:

{("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) }

Ответы [ 4 ]

1 голос
/ 07 мая 2019

Вы можете попробовать вот так:

Map<TimeStamp, Optional<Obj>> result = 
         list.stream().collect(Collectors.groupingBy(
                                Obj::getTs,
                                Collectors.maxBy(Comparator.comparing(Obj::getGenerationTs))
         ));

Более полные параметры, как указано в комментарии @Naman:

list.stream().collect(Collectors.groupingBy(
                       Obj::getTs,
                       Collectors.maxBy(Comparator.comparing(Obj::getGenerationTs))
              )).values().stream()
                .filter(Optional::isPresent) 
                .map(Optional::get)
                .collect(Collectors.toList());
1 голос
/ 07 мая 2019

Вы, конечно, можете сделать это, используя Stream, используя сборщик карт, а затем получить значения

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

Или даже короче:

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())));
0 голосов
/ 07 мая 2019

Если у вас уже есть отсортированный список (по убыванию generationTs), как в примере кода, вы можете использовать HashSet и Collection.removeIf(), чтобы удалить все дублированные метки времени из этого списка:

list.sort(Comparator.comparing(Obj::getTs)
        .thenComparing(Comparator.comparing(Obj::getGenerationTs)
                .reversed()));

Set<Timestamp> keys = new HashSet<>();
list.removeIf(o -> !keys.add(o.getTs()));

С этим решением вам не нужно создавать новый список, вы просто изменяете свой список.Набор хранит все ключи, которые вы хотите сохранить в списке.Поскольку список отсортирован, самые новые объекты сохраняются в списке, а остальные значения удаляются.

Результат с данными, которыми вы поделились, будет:

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]

Если у вас уже естьотсортированный список, это решение должно быть одним из самых быстрых.

0 голосов
/ 07 мая 2019

Ниже приведен один из способов сделать это.

Группировка одной первой временной метки и последующее использование maxBy для поиска объекта с временной меткой последнего поколения. Наконец, отсортируйте первую временную метку и распечатайте ее.

Тот факт, что maxBy будет производить Optional, немного уродлив, но я не мог найти способ избежать этого.

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