流复制/减少重复的条目

Kev*_*inO 7 java java-8 java-stream

我试图过滤/减少其中包含一些重复条目的数据流.

从本质上讲,我试图找到一个比我实现的更好的过滤一组数据的解决方案.我们的数据在其基础上是这样的:

Action | Date         | Detail
15     | 2016-03-15   | 
5      | 2016-03-15   | D1
5      | 2016-09-25   | D2      <--
5      | 2016-09-25   | D3      <-- same day, different detail
4      | 2017-02-08   | D4
4      | 2017-02-08   | D5
5      | 2017-03-01   | D6      <--
5      | 2017-03-05   | D6      <-- different day, same detail; need earliest
5      | 2017-03-08   | D7
5      | 2017-03-10   | D8
...
Run Code Online (Sandbox Code Playgroud)

我需要提取细节,以便:

  • 仅选择动作5
  • 如果细节相同(例如,D6在不同日期出现两次),则选择最早的日期

这些数据被加载到对象中(每个"记录"一个实例),并且对象上还有其他字段,但它们与此过滤无关.细节存储为String,Date作为ZonedDateTime存储,Action是一个int(实际上是一个enum,但在这里显示为int).对象List<Entry>按时间顺序给出.

我能够得到一个工作,但我认为是次优的,通过做的解决方案:

  List<Entry> entries = getEntries(); // retrieved from a server

  final Set<String> update = new HashSet<>();
  List<Entry> updates =
  entries.stream()
    .filter(e -> e.getType() == 5)
    .filter(e -> pass(e, update))
    .collect(Collectors.toList());


private boolean pass(Entry ehe, Set<String> update)
   {
     final String val =  ehe.getDetail();
     if (update.contains(val)) { return false; }
     update.add(val);
     return true;
   }
Run Code Online (Sandbox Code Playgroud)

但问题是我必须使用这种pass()方法并在其中检查a Set<String>以维护给定的细节是否已经处理完毕.虽然这种方法有效,但似乎应该可以避免外部引用.

我尝试groupingBy在详细信息上使用a ,它将允许从列表中提取最早的条目,问题是我不再有日期排序,我不得不处理结果Map<String,List<Entry>>.

似乎有些减少操作(如果我正确地使用了这个术语)这里没有使用该pass()方法应该是可能的,但我正在努力获得更好的实现.

什么是更好的方法,以便.filter(e -> pass(e, update))可以删除?

谢谢!

Rob*_*per 8

在这个答案的两个解决方案,其中第二个明显更快.

解决方案1

改编答案奥莱VV上的另一个问题:

Collection<Entry> result = 
 entries.stream().filter(e -> e.getAction() == 5)
  .collect(Collectors.groupingBy(Entry::getDetail, Collectors.collectingAndThen(Collectors.minBy(Comparator.comparing(Entry::getDate)), Optional::get)))
  .values();
Run Code Online (Sandbox Code Playgroud)

使用您的示例数据集(我选择GMT + 0作为时区):

Entry [action=5, date=2017-03-01T00:00Z[GMT], detail=D6]
Entry [action=5, date=2017-03-08T00:00Z[GMT], detail=D7]
Entry [action=5, date=2017-03-10T00:00Z[GMT], detail=D8]
Entry [action=5, date=2016-03-15T00:00Z[GMT], detail=D1]
Entry [action=5, date=2016-09-25T00:00Z[GMT], detail=D2]
Entry [action=5, date=2016-09-25T00:00Z[GMT], detail=D3]
Run Code Online (Sandbox Code Playgroud)

如果你坚持要List回来:

List<Entry> result = new ArrayList<>(entries.stream() ..... .values());
Run Code Online (Sandbox Code Playgroud)

如果您想获得原始订单,请使用3参数groupingBy:

...groupingBy(Entry::getDetail, LinkedHashMap::new, Collectors.collectingAndThen(...))
Run Code Online (Sandbox Code Playgroud)

解决方案2

使用toMap,更容易阅读,更快(请参阅holi-java对此答案的评论,以及下一个'部分'):

List<Entry> col = new ArrayList<>(
  entries.stream().filter(e -> e.getAction() == 5)
  .collect(Collectors.toMap(Entry::getDetail, Function.identity(), (a,b) -> a.getDate().compareTo(b.getDate()) >= 0 ? b : a))
  .values());
Run Code Online (Sandbox Code Playgroud)

哪里(a,b) -> a.getDate().compareTo(b.getDate()) >= 0 ? b : a可以替代:

BinaryOperator.minBy(Comparator.comparing(Entry::getDate))
Run Code Online (Sandbox Code Playgroud)

如果您想在此解决方案中获得原始订单,请使用4参数toMap:

...toMap(Entry::getDetail, Function.identity(), (a,b) -> a.getDate().compareTo(b.getDate()) >= 0 ? b : a, LinkedHashMap::new)
Run Code Online (Sandbox Code Playgroud)

性能

使用我为测试我的解决方案而创建的testdata,我检查了两个解决方案的运行时间.第一个解决方案平均需要67毫秒(仅运行20次,所以不要相信数字!),第二个解决方案平均需要2毫秒.如果有人想进行适当的性能比较,请将结果放在评论中,我会在此处添加.

  • 这里没有讽刺,我很认真.我喜欢当我写的东西似乎对某人有帮助时,这可能是我在这里的主要原因.我认为我的贡献与你的贡献相结合,价值更高. (5认同)
  • 真棒!然后我的回答首先收集到"地图",你不依赖于订单(可能不是).一加 (2认同)