2016-01-11 61 views
3

如果我有以下列表:等效使用自定義比較以流不同

List<String> list = Arrays.asList("hello", "world", "hello"); 

我申請以下(Java8):

list.stream().distinct().collect(Collectors.toString()); 

然後我會得到一個「Hello列表「和」世界「。但是,就我而言,我有一個類型(來自外部api)的列表,我想繞過equals方法,理想情況下使用比較器,因爲它不包含我需要的東西。

假設這個類看起來像這樣:

public class Point { 
    float x; 
    float y; 
    //getters and setters omitted 
} 

在這種情況下,我想的是覆蓋一定的標準的兩點被定義爲相等的,例如(30,20)和(30.0001,19.999 )。

一個自定義的比較器可以做到這一點,但我沒有找到API來做Java8 Stream中的distinct(),但是使用比較器(或類似模式)。

有什麼想法?我知道我可以編寫這樣一個函數,但我寧願喜歡使用現有API的優雅方式......我對外部庫(番石榴,apache-commons等)沒有限制,只要它們有一個舒適的方式我需要的)。

+0

這是一個類似的討論,但試圖用Set實現:http://stackoverflow.com/questions/14880450/java-hashset-with-a-custom-equality-criteria。我認爲我所遵循的方法朝着正確的方向發展。有沒有可以做這種事情的API? – Martin

+0

來自doc:distinct() 返回由此流的不同元素(根據Object.equals(Object))組成的流。您可以在您的特定情況下覆蓋Point類中的equals方法。另外,不同的是有狀態的管道操作。您可以嘗試使用過濾器代替:[示例](http://stackoverflow.com/questions/23699371/java-8-distinct-by-property) – algor

+0

一個比較器(或一個equals()方法),將執行相等你所描述的會打破Comparator(或等於)的合約。事實上,如果兩個點如果彼此接近(假設在兩個方向上最多爲0.1),那麼它表示等於'(0.9,0.9)',並且'(0.9,0.9)'等於'(0.8,0.8)'。但是,通過傳遞性(由比較合同規定),'(1,1)'也必須等於'(0.8,0.8)',這不是你想要的。 –

回答

3

HashingStrategy是您正在尋找的概念。這是一個策略接口,允許您定義equals和hashcode的自定義實現。

public interface HashingStrategy<E> 
{ 
    int computeHashCode(E object); 
    boolean equals(E object1, E object2); 
} 

流不支持散列策略,但Eclipse Collections呢。它具有支持散列策略的設置和映射,以及採用哈希策略的方法(如distinct())的重載。

這對於Strings很適用。例如,我們可以如何讓所有不同的字符串忽略大小寫。

MutableList<String> strings = Lists.mutable.with("Hello", "world", "HELLO", "World"); 
assertThat(
    strings.distinct(HashingStrategies.fromFunction(String::toLowerCase)), 
    is(equalTo(Lists.immutable.with("Hello", "world")))); 

或者你可以手工編寫散列策略來避免垃圾創建。

HashingStrategy<String> caseInsensitive = new HashingStrategy<String>() 
{ 
    @Override 
    public int computeHashCode(String string) 
    { 
     int hashCode = 0; 
     for (int i = 0; i < string.length(); i++) 
     { 
      hashCode = 31 * hashCode + Character.toLowerCase(string.charAt(i)); 
     } 
     return hashCode; 
    } 

    @Override 
    public boolean equals(String string1, String string2) 
    { 
     return string1.equalsIgnoreCase(string2); 
    } 
}; 

assertThat(
    strings.distinct(caseInsensitive), 
    is(equalTo(Lists.immutable.with("Hello", "world")))); 

這也可以適用於Points,但前提是您可以將非重疊區域內的所有點組合爲具有相同的哈希碼。如果您使用定義爲在兩個點足夠接近時返回0的Comparator,則可能會遇到傳遞性問題。例如,點A,B和C可以沿A和C線落下,A和C都靠近B但彼此遠離。不過,如果這對你來說是一個有用的概念,我們歡迎向API添加ListIterable.distinct(Comparator)的拉取請求。

注意:我是Eclipse集合的提交者。

+2

我會給這個嘗試......我確實可以給所有點相同的哈希碼因爲我現在能夠識別它們。我做了一個嵌套的竅門,如果運行該集合並應用我的自定義比較器並刪除所有檢測到的相同元素...計算散列碼應該是可能的,我認爲這只是一個四捨五入的問題... – Martin