【问题标题】:How to remove first appearance in stream when searching for duplicates?搜索重复项时如何删除流中的第一次出现?
【发布时间】:2021-02-06 07:49:52
【问题描述】:

我有一个对象流,它根据它们拥有的 id 定义等号和哈希码。所以我知道使用stream.distinct() 我会得到一个没有任何重复的流,但它会保留第一个出现,而不是最后一个。

这在文档中有明确说明:

对于有序流,不同元素的选择是稳定的 *(对于重复的元素,在遭遇中最先出现的元素 * 订单被保留。)

那么我怎样才能有一个没有重复但保留最后一次出现的流呢?

例如,如果我有此项目列表1,2,3,1,4,5

如果我申请distinct(),我将得到1,2,3,4,5,我期待:2,3,1,4,5。我怎样才能做到这一点?

【问题讨论】:

  • 您是否尝试颠倒顺序,然后删除重复项并再次颠倒顺序?
  • 我严重怀疑您是否可以使用流实现此目的。这将要求流函数“向前看”以查看流中稍后是否出现重复项,或者返回并追溯删除已允许通过输出的项目。这些都不是流的“特征”的一部分。
  • 您为什么还要关心“第一个”或“最后一个”元素是否保留在流中?如果它们相等,那真的没关系!
  • @Seelenvirtuose 您对 OP 的用例了解得不够多,无法说出它是否重要。
  • @TimothyTruckle 也许你没有仔细阅读。他说项目的顺序很重要,这意味着List(或数组)绝对是正确的数据结构,因为列表是有序的。

标签: java java-8 java-stream


【解决方案1】:

具有访问顺序LinkedHashMap 的自定义toMap 收集器是一个好的开始,例如:

Stream.of( 1, 2, 3, 1, 4, 5 )
        .collect( Collectors.toMap(
                key -> key,
                value -> value,
                ( oldValue, newValue ) -> newValue,
                () -> new LinkedHashMap<>( 16, 0.75f, true )
        ) )
        .values()
        .stream()
        .forEach( System.out::println );

保留项目最后一次出现的任务意味着在生成新流的第一个元素之前,无论如何您都必须完全使用原始流,因此收集和创建新流并不是那么糟糕。如 cmets 中所建议的,反转、应用 distinct 和再次反转,将完全消耗原始流和“distinct”流,因此使用专门的收集器应该会更好。

【讨论】:

    【解决方案2】:

    Oleg's answer 是绝对正确的,在这里我根据他使用 access-ordered LinkedHashMap 的想法发布做同样事情的其他方法。

    没有流:

    Map<Integer, Integer> map = new LinkedHashMap<>(16, 0.75f, true); // access order
    Arrays.asList(1, 2, 3, 1, 4, 5)
        .forEach(e -> map.put(e, e)); // just put
    List<Integer> list1 = new ArrayList<>(map.values()); // discard the map
    
    System.out.println(list1); // [2, 3, 1, 4, 5]
    

    使用流,使用自定义收集器:

    List<Integer> list2 = Stream.of(1, 2, 3, 1, 4, 5)
        .collect(Collector.of(
            () -> new LinkedHashMap<Integer, Integer>(16, 0.75f, true),
            (m, e) -> m.put(e, e),
            (m1, m2) -> { m1.putAll(m2); return m1; },
            m -> new ArrayList<>(m.values())));
    
    System.out.println(list2); // [2, 3, 1, 4, 5]
    

    注意:正如 Holger 在 cmets 中指出的,使用 Map.values() 而不是 Map.keySet() 创建结果列表很重要。这是因为在使用m.put(e, e)时,如果map中已经存在具有相同键的条目,则旧值被替换为新值,但键保持不变,即旧的。

    【讨论】:

    • 与其他答案略有不同。 put 会将现有条目移到前面,但不会替换键实例。对于装箱的小整数值,相等的值始终具有相同的标识,这没有区别,但在对象标识很重要的用例中,结果将是错误的。但这很容易解决;只需使用values() 而不是keySet()。除此之外,将类型参数添加到LinkedHashMap(即() -&gt; new LinkedHashMap&lt;Integer,Integer&gt;(16, 0.75f, true),而不是Collector.of)更简单。
    • @Holger 好的,那么这是 IntelliJ 的类型推断机制的错误。我举报了,已经举报了。非常感谢!
    【解决方案3】:

    我解决了这个问题,首先反转列表,删除重复项,然后再次反转它以获得原始列表。

    public void removeDuplicates(){ 
         Collections.reverse(duplicatesList);
         duplicatesList = duplicatesList.stream().distinct().collect(Collectors.toList());
         Collections.reverse(duplicatesList);
         }
    

    【讨论】:

      猜你喜欢
      • 2014-12-14
      • 2022-11-22
      • 1970-01-01
      • 1970-01-01
      • 2016-09-10
      • 1970-01-01
      • 2020-12-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多