在这种情况下,如何流式排序列表?

时间:2019-05-07 14:21:00

标签: java java-8 java-stream

我有一个带有两个timeStamp的Java对象列表,例如:

Obj(TimeStamp ts,TimeStamp generationTs,int值)。

最后,我不希望列表中的两个项目具有相同的ts。如果有的话,我只想保留最新一代的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 个答案:

答案 0 :(得分:1)

您当然可以使用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())));

答案 1 :(得分:1)

您可以尝试这样:

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

答案 2 :(得分:0)

以下是一种实现方法。

将第一个时间戳分组,然后使用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);
    }
}
}

答案 3 :(得分:0)

如果您已经有一个排序列表(以generationTs降序),就像示例代码中的列表一样,则可以使用HashSetCollection.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]

如果您已经有一个排序列表,则此解决方案应该是最快的解决方案之一。