2017-09-04 48 views
2

我膚淺洞察到Java 8集流媒體功能,所以我不知道,如果以下甚至有可能:我想filter基於整數比較的收集和再利用的映射該值。如何在沒有冗餘計算的情況下過濾和映射Java 8流中的值?

具體我有一個Collection<String> strings並希望其每個值的Levenshtein距離如果Levenshtein距離小於值levenshteinLimit映射到固定String x

String x = "some string"; 
Collection<String> strings = new LinkedList<>(Arrays.asList("not some string", 
     "some other string")); 
int levenshteinLimit = 10; 
Map<Integer, String> stringsLevenshteinMap = strings.stream() 
     .filter(string -> LevenshteinDistance.getDefaultInstance().apply(x, string) < levenshteinLimit) 
     .collect(Collectors.toMap(string -> LevenshteinDistance.getDefaultInstance().apply(x, string), Function.identity())); 
System.out.println(stringsLevenshteinMap); 

效果很好,並表達我正在尋找的結果,但需要冗餘計算距離。到目前爲止這不是問題。沒有流的解決方案也是可行的。我試圖學習新的東西。

我假定它是更有效的過濾,然後再映射,因爲對象的數量是可能更小,這意味着較少的工作。上述

該代碼使用Apache公地文本1.1。示例項目可在https://github.com/krichter722/java-filter-and-map-without-redundancy找到。

回答

1

如果你想避免臨時對象持有的鍵和值,你需要一個定製的收藏家,追溯一下內置收藏家,但納入過濾器直接。順便說一句,我不認爲Collectors.toMap是合適的在這裏,因爲我們無法保證會有一個只爲每個距離的字符串。因此,我用Collectors.groupingBy作爲模板來代替:

public static <T> Collector<T,?,Map<Integer,List<T>>> 
        grouping(ToIntFunction<T> f, int limit) { 
    return Collector.of(HashMap::new, 
     (m,t) -> { 
      int v = f.applyAsInt(t); 
      if(v < limit) m.computeIfAbsent(v, x -> new ArrayList<>()).add(t); 
     }, 
     (m1,m2) -> { 
      m2.forEach((k,v) -> m1.merge(k, v, (l1,l2)->{ l1.addAll(l2); return l1; })); 
      return m1; 
     }); 
} 

這基本上做什麼Collectors.groupingBy做,但限制其使用一鍵功能評估到int數量只有處理單元映射到低於指定限度的數字。也可以概括爲使用FunctionPredicate代替。

這可以用於像

Map<Integer, List<String>> stringsLevenshteinMap 
    = Stream.of("not some string", "some other string") 
      .collect(grouping(
       string -> LevenshteinDistance.getDefaultInstance().apply(x, string), 
       levenshteinLimit)); 

但必須強調的是,我們無法保證,這比執行剛剛創建臨時對象容納兩個值更好;這取決於很多環境因素。一般來說,創建臨時對象並不昂貴。使用持有者對象的方法要靈活得多,尤其是當您想要稍後改變流操作時。

-1

走出我的頭,我認爲這應該工作:

Map<Integer, String> stringsLevenshteinMap = strings.stream() 
    .map(string -> LevenshteinDistance.getDefaultInstance().apply(x, string)) 
    .filter(val -> val < levenshteinLimit) 
    .collect(Collectors.toMap(val -> val, Function.identity())); 

但正如我說:這是我的頭,所以我不能保證功能或compilability。但這個想法應該很明顯。我們不是計算兩次,而是計算出計算值,並從那裏繼續進行過濾和收集。

+0

'val - > val = Function.identity()'以及 – Andrew

+2

我打算回答這樣的問題,但是在第一個映射中丟失了字符串。 – daniu

+0

@daniu就我所瞭解的代碼而言,該字符串除了'apply'的重複調用之外沒有別的用途,所以這裏沒有真正的損失。 – Lothar

0

首先,你可以使代碼更快通過避免創建無用的LinkedList。

現在,關於你的問題,如果你想使用流來做到這一點,保持,那麼解決辦法是每個串映射到包含字符串,其距離的物體,然後過濾這些對象,然後收集到地圖:

String x = "some string"; 
int levenshteinLimit = 10; 

List<String> strings = Arrays.asList("not some string", "some other string")); 
Map<Integer, String> stringsLevenshteinMap = 
    strings.stream() 
      .map(string -> new StringWithDistance(string, LevenshteinDistance.getDefaultInstance().apply(x, string)) 
      .filter(o -> o.getDistance() < levenshteinLimit) 
      .collect(Collectors.toMap(StringWithDistance::getDistance, StringWithDistance.getString)); 

System.out.println(stringsLevenshteinMap); 
5

東西有一個元組的中間目標應該工作:

Map<Integer, String> stringsLevenshteinMap = strings.stream() 
    .map(s -> new Tuple<>(LD.getInstance().apply(x, s), s) 
    .filter(t -> t.getFirst() < maxDistance) 
    .collect(Collectors.toMap(Tuple::getFirst, Tuple::getSecond)); 
+0

那麼,沒有辦法使用昂貴的'新'?它會幫助使用'Map.Entry',因爲將它們添加到'Map'會更便宜嗎? –

+2

爲什麼將它們添加到地圖會更便宜?是的,有一種方法:使用for循環而不是使用流。但是你正在預先優化,而且它是所有邪惡的根源。在Java中創建短期對象很便宜,幾乎沒有機會導致性能問題。 –

相關問題