2017-09-30 3 views
0

ファイルのストリームと、2つのファイルを引数として取り、同じ内容を持っているかどうかを返すメソッドがあります。Java 8、BiPredicateを使用してストリーム要素をセットにグループ化する方法

このファイルのストリームを、同じ内容のすべてのファイルをグループ化したセットのセット(またはマップ)に縮小したいとします。

これは、1つのファイルを取得するためにcompareメソッドをリファクタリングし、ハッシュを返し、次にコレクタに与えられた関数によって返されたハッシュによってストリームをグループ化することで可能であることが分かります。 しかし、2つのファイルをとりブール値を返すcompareメソッドでこれを達成する最もクリーンな方法は何ですか?明確にするために

は、ここでは一つの引数関数溶液で

file.stream().collect(groupingBy(f -> Utility.getHash(f))

明白な方法の一例ですが、私の場合は私は分割プロセスに活用したい、次のメソッドを持っている

public boolean isFileSame(File f, File f2) { 
    return Files.equal(f, f2) 
} 
+0

の可能性のある重複(https://stackoverflow.com/questions/36678571/how-to-partition-a -list-by-predicate-using-java8) – Oleg

+0

あなたは投稿を読むのに気をつけますか?これは重複することはできませんが、私は明らかに、2つの要素をとりブール値を返す関数でパーティション化が可能かどうかを尋ねています。 –

+0

なぜ人々はこれをdownvotingですか? –

答えて

1

有効なルックアップを可能にする関連するハッシュ関数を持たないBiPredicateがあれば、線形プロービングのみを使用できます。もちろん、あなたが持っているより多くの結果のグループは、より悪いパフォーマンスになります、そこにそれをやって何の組み込みコレクターはありませんが、元groupingByコレクタに近い作業カスタムコレクタは

public static <T> Collector<T,?,Map<T,Set<T>>> groupingBy(BiPredicate<T,T> p) { 
    return Collector.of(HashMap::new, 
     (map,t) -> { 
      for(Map.Entry<T,Set<T>> e: map.entrySet()) 
       if(p.test(t, e.getKey())) { 
        e.getValue().add(t); 
        return; 
       } 
      map.computeIfAbsent(t, x->new HashSet<>()).add(t); 
     }, (m1,m2) -> { 
      if(m1.isEmpty()) return m2; 
      m2.forEach((t,set) -> { 
       for(Map.Entry<T,Set<T>> e: m1.entrySet()) 
        if(p.test(t, e.getKey())) { 
         e.getValue().addAll(set); 
         return; 
        } 
       m1.put(t, set); 
      }); 
      return m1; 
     } 
    ); 

のように実装することができますが。あなたの特定のタスクのために

、それは

public static ByteBuffer readUnchecked(Path p) { 
    try { 
     return ByteBuffer.wrap(Files.readAllBytes(p)); 
    } catch(IOException ex) { 
     throw new UncheckedIOException(ex); 
    } 
} 

Set<Set<Path>> groupsByContents = your stream of Path instances 
    .collect(Collectors.collectingAndThen(
     Collectors.groupingBy(YourClass::readUnchecked, Collectors.toSet()), 
     map -> new HashSet<>(map.values()))); 

内容によってその意思グループファイルと暗黙的にハッシングんを使用することがはるかに効率的になります。等しいハッシュは等しい内容を意味するわけではないが、この解決策はすでにこれを処理していることに注意してください。仕上げ機能map -> new HashSet<>(map.values())は、結果のコレクションが操作後にファイルの内容をメモリに保持しないようにします。

1

ヘルパークラスWrapperによって可能な解決策:

files.stream() 
    .collect(groupingBy(f -> Wrapper.of(f, Utility::getHash, Files::equals))) 
    .keySet().stream().map(Wrapper::value).collect(toList()); 

あなたには、いくつかの理由でUtility.getHashを使用しない場合は、ハッシュ関数のためFile.length()を使用するようにしてください。 Wrapperは、任意のタイプ(配列など)のハッシュ/等価関数をカスタマイズする一般的なソリューションを提供します。ツールキットに入れておくと便利です。ここでWrapperのためのサンプル実装です:[?java8を使用して述語でリストを分割する方法]

public class Wrapper<T> { 
    private final T value; 
    private final ToIntFunction<? super T> hashFunction; 
    private final BiFunction<? super T, ? super T, Boolean> equalsFunction; 
    private int hashCode; 

    private Wrapper(T value, ToIntFunction<? super T> hashFunction, BiFunction<? super T, ? super T, Boolean> equalsFunction) { 
     this.value = value; 
     this.hashFunction = hashFunction; 
     this.equalsFunction = equalsFunction; 
    } 
    public static <T> Wrapper<T> of(T value, ToIntFunction<? super T> hashFunction, BiFunction<? super T, ? super T, Boolean> equalsFunction) { 
     return new Wrapper<>(value, hashFunction, equalsFunction); 
    } 
    public T value() { 
     return value; 
    } 
    @Override 
    public int hashCode() { 
     if (hashCode == 0) { 
      hashCode = value == null ? 0 : hashFunction.applyAsInt(value); 
     } 

     return hashCode; 
    } 
    @Override 
    public boolean equals(Object obj) { 
     return (obj == this) || (obj instanceof Wrapper && equalsFunction.apply(((Wrapper<T>) obj).value, value)); 
    } 
    // TODO ... 
} 
関連する問題