2016-09-04 16 views
3

私が項目のリストを持っている(!):Java:コレクションを等価クラスに分割する方法は?

  • B
  • C
  • D
  • E
  • ...

は、私がしたいですそれらをグループ化する:

  • [A、C、D]
  • [B、E]
  • ...

グループはによって定義される:グループ内のすべての項目が記載等しい

  • >ブール
  • F(A、B)= F(B、A)
- カスタム関数 F(A、B)に

質問:は準備ができていますか?

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

UPDATE。この質問は、あなたがグループ化するためにいくつかの品質を定義することができるシナリオでは全くありません!この場合、Java 8 Collectors.groupingByが最も簡単な答えです。私は、多次元ベクトルと平等の機能で働いています

は、次のようになります。

  • メトリックハッシュを定義することは、最初のタスクを解決するに等しい。このケースでは(a、b)は<しきい値

: )

+0

要素をグループ化するカスタム関数が真偽を返す場合、3つ以上のグループを持つことはできますか? – Tunaki

+3

@ Tunaki - これは等価クラスへの分割と呼ばれています。オブジェクトが整数であり、等価(真/偽)が3を法として計算される(すなわち、それらが同じ残余を有する場合、それらは等しい)と仮定する。バイナリの等価性テストであっても、1から100までの整数は3つのバケットになります。 –

+0

など。 * F *要素があり、他の要素と等しくはありません。それは1つの要素のグループを作ります。 –

答えて

1

ハッシュを使用して線形時間でこれを行うことができます。

これを行うには、最初にhashCode()関数をオブジェクトに実装する必要があります。したがって、等しい要素に対して等しいハッシュ値を返します(たとえば、インスタンスプロパティのハッシュコードをXORするなど)。次に、セットのハッシュテーブルを使用して要素をグループ化することができます。

同じ要素が同じハッシュを生成するので、それらは同じ同値クラスに挿入されます。

さて、あなたは私もハッシュメカニズムを実装することをお勧めしますhashMap.values();

+1

適切なハッシュコードを書くのは簡単ではありません。 (カスタム等価関数であれば、異なるフィールドを持つアイテムに同じハッシュコードが必要なので、あなたの提案はうまくいかないでしょう)OPが同じアイテムリストを処理する必要がある場合、これは機能しません異なる平等テスト。 –

+0

特定の 'hashCode'関数に依存するように強制するのは悪いです。本当に悪いです。 equals/hashCodeは外部化する必要があります。 –

0

を使用して(セットなど)は、すべての等価クラスのコレクションを取得することができます。

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でありませんかなり確信して:あなたは、グアバFluentIterableと似た何かを行うことができます。 TroveのTCustomHashSetのようなサードパーティのコレクションクラスをお試しください。 (this related threadのコメントによれば、グアバグループは(今のところ)類似のクラスを拒否していることに興味があります。

代わりに、独自のソリューションをロールバックすることです。あなたがあまりにも多くのアイテムを持っていない場合は、ブルートフォースアプローチを提案します:アイテムリストのリストを保持し、新しいアイテムごとにリストのリストを調べ、それが最初の要素と等しいかどうかを確認します。リスト。そうであれば、一致するリストに新しい項目を追加し、そうでない場合は、その項目を唯一のメンバーとして持つリストのリストに新しいリストを追加します。計算の複雑さはそれほど良くありません。なぜなら、アイテムの数が少ないか、実行時間のパフォーマンスがまったく問題にならない場合にのみこれをお勧めします。

第2の方法は、カスタム等価機能を実装するようにアイテムクラスを変更することです。しかし、ハッシュベースのコレクションクラスでそれを使用するには、hashcode()もオーバーライドする必要があります。ハッシュベースのコレクションを使用していない場合は、ブルートフォースアプローチを使用することもできます。アイテムクラスを変更したくない(またはできない)場合(たとえば、平等テスト)、私は等価(とハッシュコード)戦略を使用するパラメータ化することができますラッパークラスを作成することをお勧めします。 (これは、アイテムクラスの変更とTroveクラスの使用の中間の一種です。)

+0

私はあなたがグアバでもっと深く進むべきだと思う:それは 'Equivalence'クラス/インターフェースを持っており、このシナリオで本当に助けになるかもしれない。 Guavaがそれを受け入れなかったからといって、そのAPIをOPのユースケースを実現するために使うことはできません。 –

+0

@OlivierGrégoire - うん、私はグアバをよく知りません。アイデアを答えとして書くべきです。しかしOPの最新の質問に対する編集に基づいて、私はこれらのアプローチのいずれもうまくいくとは思わない。 (私の2番目のコメントを参照してください)これは[XY問題](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem)であると思われます。 –

+0

はい、それはXY問題の匂いがしますが、解決策はうまくいくと思います。さらに、Guavaのソリューションは基本的にJonの答えです。私は彼が書いたものを書いていて、彼の直後に投稿し、彼の答えがうまくいけば削除しました。 –

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()使用することができ、あなたの修飾子です。

これは、修飾子がTの1つ以上のフィールドである場合に問題になります。タプル型を使用して、Tの代替IDを作成することもできますが、フィールドの数ごとに別々のタプルクラスが必要になるため、これまでのところしかありません。


あなたがgroupingByあなたを使用したい場合は、本当にTための温帯別のIDを作成する必要があるので、あなたはTequalshashCode方法を変更する必要はありません。

適切なIDを作成するには、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と同じフィールドをハッシュするハッシュコード関数。また0hashFに返すこともできますが、適切な実装をすると処理が速くなります)

関連する問題