2017-01-16 109 views
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://stackoverflow.com/q/322715/2711488)。簡單地說,你幾乎從不想使用'LinkedList' ... – Holger

+0

重複項目是否必須連續?你可以在「d」之後加入另一個「a」嗎?如果,它是否應該被刪除? – Mureinik

+2

@Mureinik聲明*「這是刪除相同的事件重複多次的順序*」已經涵蓋了IMO的這種情況。 – CKing

回答

5

您可以使用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://stackoverflow.com/q/513832/2711488)」 – Holger

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

    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

1

您可以使用自定義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

0

編輯:由@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,這種行爲是一樣什麼不同()不會。所以是的,它會過濾,但主要的問題是不同的。 –