2016-09-04 55 views
3

我有一個項目列表(!):Java:如何將集合劃分爲等價類?

  • 一個
  • Ç
  • d
  • Ë
  • ...

,我想將它們分組:

  • [A,C,d]
  • [B,E]
  • ...

組由被定義:根據

  • 組中的所有項相等到自定義函數F(A,b) - >布爾
  • F(A,b)= F(b,A)

問題:有沒有準備好API?

<T> List<List<T>> group(Collection<T> collection, BiFunction<T, T, Boolean> eqF); 

UPDATE。這個問題完全不適用於你可以定義一些質量來分組!在這種情況下,Java 8 Collectors.groupingBy是最簡單的答案。

我正在與多維矢量和平等功能工作的樣子:

  • 指標(A,B)<閾

對於這種情況定義的散列等於解決初始任務: )

+0

如果用於分組元素的自定義函數返回一個布爾值,那麼如何擁有兩個以上的組? – Tunaki

+3

@Tunaki - 這就是所謂的劃分爲等價類。假設對象是整數並且等式(真/假)以模3計算(即,如果它們具有相同的餘數則它們相等)。然後,從1到100的整數將以三個桶結束,即使它是一個二進制相等性測試。 –

+0

例如有* F *元素,它不等於任何其他元素。它使一組元素。 –

回答

1

您可以使用散列法在線性時間內執行此操作。

爲此,您需要首先在對象中實現hashCode()函數,以便爲相等元素返回相同的散列值(例如,通過異或其實例屬性的散列代碼)。然後你可以使用集合的散列表來對你的元素進行分組。

Map<Integer, Set<T>> hashMap = new HashMap<>(); 
for (T element : collection) { 
    if (!hashMap.containsKey(element.hashCode()) 
     hashMap.put(element.hashCode(), new HashSet<T>()); 
    hashMap.get(element.hashCode()).add(element); 
} 

由於等號生成相同的散列,它們將被插入到相同的等價類中。現在

,您可以通過使用hashMap.values();

+1

寫一個合適的哈希代碼並不容易。 (你的建議是行不通的,因爲具有不同字段的項目需要使用相同的散列碼,如果自定義相等函數這樣說的話)。另外,如果OP需要處理相同的項目列表,那麼這不起作用不同的平等測試。 –

+0

強制依賴特定的'hashCode'函數是不好的,真的很糟糕。 equals/hashCode應該是外部化的。 –

0

我還建議實施散列機制獲得的所有等價類(如套)的集合。你可以做番石榴FluentIterable類似的東西:

FluentIterable.from(collection) 
    .index(new Function<T, K>() { 
     K apply(T input) { 
      //transform T to K hash 
     } 
    })//that would return ImmutableListMultimap<K, T> 
    .asMap()//that would return Map<K, Collection<T>> 
    .values();//Collection<Collection<T>> 
1

我敢肯定,沒有什麼標準的API在此。您可以嘗試第三方收集課程,如Trove的TCustomHashSet。 (有趣的是,根據this related thread中的評論,番石榴集團(現在)拒絕了類似的類別。參見討論here。)

另一種方法是推出自己的解決方案。如果你沒有太多項目,我會建議採用強力方法:保留項目列表的列表,並且對於每個新項目,通過列表列表並查看它是否等於第一個元素列表。如果是這樣,請將新項目添加到匹配列表中,如果沒有,則將新項目列表添加到該項目列表作爲唯一成員。計算複雜度不是很好,這就是爲什麼我只會在項目數量很少或者執行時間性能完全不成問題時才推薦這一點。

第二種方法是修改您的項目類以實現自定義相等功能。但是要將其與基於散列的集合類一起使用,您還需要覆蓋hashcode()。 (如果你不使用基於散列的集合,那麼你可以使用暴力方法。)如果你不想(或不能)修改項目類(例如,你想使用各種平等測試),我會建議創建一個包裝類,可以使用相等(和散列碼)戰略參數化使用。 (這是修改你的物品類和使用Trove類之間的一種方式。)

+0

我認爲你應該深入Guava:它具有'Equivalence'類/接口,在這種情況下可能真的有幫助。僅僅因爲Guava不接受它(迄今爲止),他們的API不能用來滿足OP的用例。 –

+0

@OlivierGrégoire - 是的,我不太瞭解番石榴。你應該寫下你的想法作爲答案。但基於OP對這個問題的最新編輯,我認爲這些方法都不會奏效。 (請參閱我對問題的第二條評論。)我懷疑這是[XY問題](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem)。 –

+0

是的,它嗅到XY問題,但我認爲解決方案可以很好地工作。此外,番石榴解決方案基本上是喬恩的答案。我正在寫他寫的東西,貼在他後面,並且因爲他的情況好轉而刪除了我的回答。 –

1

下面是一個簡單的分組字符串示例。如果要分組的對象更加複雜,則需要提供除identity()以外的其他功能。

public class StreamGroupingBy 
{ 

    public static void main(String[] args) 
    { 
     List<String> items = Arrays.asList( 
       "a", "b", "c", "d", 
       "a", "b", "c", 
       "a", "b", 
       "a", "x"); 

     Map<String,List<String>> result = items.stream().collect(
       Collectors.groupingBy(Function.identity())); 
     System.out.println(result); 
    } 
} 

輸出:

{a=[a, a, a, a], b=[b, b, b], c=[c, c], d=[d], x=[x]} 
2

你的情況聽起來是個不錯的用例爲groupingBy收集器。通常,不提供相等函數,而是提供一個提取限定符的函數。元素然後映射到列表中的這些限定符。

Map<Qualifier, List<T>> map = list.stream() 
    .collect(Collectors.groupingBy(T::getQualifier)); 

Collection<List<T>> result = map.values(); 

在案件的T的身份是你的預選賽中,你可以使用Function.identity()作爲參數。

但是當您的限定符超過1個字段T時,這會成爲問題。您可以使用元組類型來爲T創建一個替代標識,但是這僅僅到目前爲止,因爲每個字段數都需要單獨的元組類。


如果你想使用groupingBy你真的需要爲T創建溫帶替補身份,所以你不必改變TequalshashCode方法。

要創建一個合適的身份,您需要實現equalshashCode(或總是返回0作爲散列碼,性能下降)。沒有API類這一點,我知道的,但我做了一個簡單的實現:

interface AlternateIdentity<T> {  
    public static <T> Function<T, AlternateIdentity<T>> mapper(
      BiPredicate<? super T, Object> equality, ToIntFunction<? super T> hasher) { 
     return t -> new AlternateIdentity<T>() { 
      @Override 
      public boolean equals(Object other) { 
       return equality.test(t, other); 
      } 

      @Override 
      public int hashCode() { 
       return hasher.applyAsInt(t); 
      } 
     }; 
    } 
} 

,你可以使用這樣的:

Collection<List<T>> result 
    = list.stream() 
     .collect(Collectors.groupingBy(
      AlternateIdentity.mapper(eqF, hashF) 
     )) 
     .values(); 

哪裏eqF是你的函數,並hashF是散列碼功能,散列與eqF測試相同的字段。 (同樣,你也可以在hashF中返回0,但是正確的實現會加快速度。)