Java 8 - 从列表中删除重复的元素序列

Java 8 - 从列表中删除重复的元素序列

问题描述:

我有一个要求,我希望使用Java Stream Api来处理来自系统的事件流,并应用数据清理过程来删除重复的事件。 这是按顺序重复多次删除相同的事件,而不是创建不同事件的列表。大多数可在线提供的Java Stream api示例旨在创建来自给定输入的独特输出。Java 8 - 从列表中删除重复的元素序列

实施例,对输入流

[A,B,C,A,A,A,A,d,d,d,C,C,E,E,E,E,E, E,F,F,F]

输出列表或流应是

[A,b,C,A,d,C,E,F]

我的cu rrent执行(不使用流API)看起来像

public class Test { 
    public static void main(String[] args) { 
     String fileName = "src/main/resources/test.log"; 
     try { 
      List<String> list = Files.readAllLines(Paths.get(fileName)); 
      LinkedList<String> acc = new LinkedList<>(); 

      for (String line: list) { 
       if (acc.isEmpty()) 
        acc.add(line); 
       else if (! line.equals(acc.getLast())) 
        acc.add(line); 
      } 

      System.out.println(list); 
      System.out.println(acc); 

     } catch (IOException ioe) { 
      ioe.printStackTrace(); 
     } 
    } 
} 

输出,

[a, b, c, a, a, a, a, d, d, d, c, c, e, e, e, e, e, e, f, f, f] 
[a, b, c, a, d, c, e, f] 

我试着减少,groupingBy等各种例子,但没有成功。如果存在这样的可能性,我似乎无法找到一种方法来比较流与我的累加器中的最后一个元素。

+5

作为一个提示,请考虑阅读[“何时使用ArrayList上的LinkedList?”](http://*.com/q/322715/2711488)。简单地说,你几乎从不想使用'LinkedList' ... – Holger

+0

重复项目是否必须连续?你可以在“d”之后加入另一个“a”吗?如果,它是否应该被删除? – Mureinik

+2

@Mureinik声明*“这是删除相同的事件重复多次的顺序*”已经涵盖了IMO的这种情况。 – CKing

您可以使用IntStream获得索引位置的保持在List,并使用你的优势如下:

List<String> acc = IntStream 
      .range(0, list.size()) 
      .filter(i -> ((i < list.size() - 1 && !list.get(i).equals(list 
        .get(i + 1))) || i == list.size() - 1)) 
      .mapToObj(i -> list.get(i)).collect(Collectors.toList()); 
System.out.println(acc); 

说明

  1. IntStream.range(0,list.size()):返回的原始序列将用作访问列表的索引位置的int值元素。
  2. filter(i -> ((i < list.size() - 1 && !list.get(i).equals(list.get(i + 1) || i == list.size() - 1)):仅继续如果在当前索引位置的元素不是在下一索引位置等于所述元件,或者如果最后一个索引位置达到
  3. mapToObj(i -> list.get(i):流转换为Stream<String>
  4. collect(Collectors.toList()):将结果收集到列表中。
+0

嗨@CKing,谢谢你的快速回复。我只是尝试了你的解决方案,这似乎是逻辑上正确的,但我没有得到所需的输出。请检查https://gist.github.com/amitoj/6b1705cd127e282cf87921ebe9e5d82e输出与输入相同。 – Amitoj

+0

@Amitoj我在Ideone测试了它,并按预期工作。请参阅[stdout](http://ideone.com/8ghrld)了解我的运行情况。您是否按原样复制了我的解决方案,并确定您的代码中没有其他错误? – CKing

+1

那么,显而易见的问题是,此代码只能用于测试数据,即字符串文字,但不能在从文件中读取字符串时使用。原因在“[我如何比较Java中的字符串?](http://*.com/q/513832/2711488)” – Holger

请再试此解决方案:

public class TestDuplicatePreviousEvent { 

public static void main(String[] args) { 
    List<Integer> inputData = new ArrayList<>(); 
    List<Integer> outputData = new ArrayList<>(); 

    inputData.add(1); 
    inputData.add(2); 
    inputData.add(2); 
    inputData.add(3); 
    inputData.add(3); 
    inputData.add(3); 
    inputData.add(4); 
    inputData.add(4); 
    inputData.add(4); 
    inputData.add(4); 
    inputData.add(1); 

    AtomicInteger index = new AtomicInteger(); 
    Map<Integer, Integer> valueByIndex = inputData.stream().collect(Collectors.toMap(i -> index.incrementAndGet(), i -> i)); 

    outputData = valueByIndex.entrySet().stream().filter(i -> !i.getValue().equals(valueByIndex.get(i.getKey() - 1))).map(x -> x.getValue()).collect(Collectors.toList()); 
    System.out.println(outputData); 
} 

}

输出: [1,2,3,4,1]

解决方案没有地图:

public class TestDuplicatePreviousEvent { 

public static void main(String[] args) { 
    List<Integer> inputData = new ArrayList<>(); 
    List<Integer> outputData = new ArrayList<>(); 

    inputData.add(1); 
    inputData.add(2); 
    inputData.add(2); 
    inputData.add(3); 
    inputData.add(3); 
    inputData.add(3); 
    inputData.add(4); 
    inputData.add(4); 
    inputData.add(4); 
    inputData.add(4); 
    inputData.add(1); 
    inputData.add(1); 
    inputData.add(1); 
    inputData.add(4); 
    inputData.add(4); 

    AtomicInteger index = new AtomicInteger(); 
    outputData = inputData.stream().filter(i -> filterInputEvents(i, index, inputData)).collect(Collectors.toList()); 
    System.out.println(outputData); 
} 

private static boolean filterInputEvents(Integer i, AtomicInteger index, List<Integer> inputData) { 

    if (index.get() == 0) { 
     index.incrementAndGet(); 
     return true; 
    } 
    return !(i.equals(inputData.get(index.getAndIncrement() - 1))); 
} 

}

+1

当输入数据来自文件时,此解决方案需要一个额外的步骤来将输入'List'转换为'Map'。 – CKing

您可以使用自定义Collector来实现您的目标。请看以下细节:

Stream<String> lines = Files.lines(Paths.get("distinct.txt")); 
LinkedList<String> values = lines.collect(Collector.of(
      LinkedList::new, 
      (list, string) -> { 
       if (list.isEmpty()) 
        list.add(string); 
       else if (!string.equals(list.getLast())) 
        list.add(string); 
      }, 
      (left, right) -> { 
       left.addAll(right); 
       return left; 
      } 
    )); 

values.forEach(System.out::println); 

但是它可能有一些问题,当使用parallel流。

+2

并行执行的问题是组合器不检查'left'的最后一个元素是否与'right'的第一个元素匹配。在这种情况下,不能添加第一个元素。一个正确的组合器将是'if(left.isEmpty())返回正确的;否则,如果left.addAll(left.getLast()等于(right.getFirst())right.subList(1 right.size())。?:右)(right.isEmpty()!);向左返回;' – Holger

编辑:由@Bolzano评论,这种方法不符合要求。

如果t是输入流然后

Map<String,Boolean> s = new HashMap<>(); 
Stream<String> u = t.filter(e -> s.put(e, Boolean.TRUE)==null); 

将产生独特的元素的流,而不创建列表。

然后一个普通的

List<String> m = u.collect(Collectors.toList()); 

可以创造独特的元素的列表。

我不明白为什么像@CKing和@Anton提出的这样冗长的解决方案会被要求?我错过了什么吗?

+0

是的,你错过了一些东西,再次比较输入数组和输出数组。他不想要独特的元素,他想要将重复的元素序列转换为单个元素。如果你想收集独特的元素,你的解决方案也不短,你可以使用distinct()方法的流然后收集。 - > list.stream()不同的()收集(... –

+0

是@Bolzano你是正确的,但随后一个非常类似的方法'地图 S =新的HashMap (); 流。! U = t.filter(E - > e.equals(s.put(Boolean.TRUE,E))!);?'应该做的过滤是等于先前的那些的作业没有它 –

+0

考虑stream在第一个元素上,它的值是“a”,所以在你的hashmap中“a”被标记为true,然后stream在3个不同的元素后面找到第二个“a”,在这种情况下,第二个“a”它已经位于你的HashMap,这种行为是一样什么不同()不会。所以是的,它会过滤,但主要的问题是不同的。 –