Java 8、重複する要素を見つけるためのストリーム


87

整数リスト内の重複する要素をリストアップしようとしています。

List<Integer> numbers = Arrays.asList(new Integer[]{1,2,1,3,4,4});    

jdk8のStreamsを使用します。誰かが試しましたか。重複を削除するには、distinct()APIを使用できます。しかし、重複した要素を見つけるのはどうですか?誰かが私を助けることができますか?



ストリームを収集したくない場合、これは基本的に「ストリーム内で一度に複数のアイテムを表示するにはどうすればよいですか」ということになります。
–ThorbjørnRavn Andersen 2018

Set <Integer> items = new HashSet(); numbers.stream()。filter(n-> i!tems.add(n))。collect(Collectors.toSet());
Saroj KumarSahoo20年

回答:


127

あなたが使用することができますCollections.frequency

numbers.stream().filter(i -> Collections.frequency(numbers, i) >1)
                .collect(Collectors.toSet()).forEach(System.out::println);

11
@OussamaZoghlamiの回答と同じO(n ^ 2)パフォーマンスですが、おそらくより単純です。それにもかかわらず、ここに賛成票があります。StackOverflowへようこそ!
Tagir Valeev 2015

6
前述のように、これは自明な線形解が存在する^ 2解です。私はこれをCRで受け入れません。
jwilner 2018

3
@Daveオプションより遅いかもしれませんが、もっときれいなので、パフォーマンスに影響を与えます。
jDub9

@jwilnerは、フィルターでのCollections.frequencyの使用を参照するn ^ 2ソリューションに関するあなたのポイントですか?
mancocapac

5
@mancocapacはい、頻度呼び出しは数値のすべての要素にアクセスする必要があり、すべての要素で呼び出されるため、2次式です。したがって、各要素について、すべての要素(n ^ 2)にアクセスし、不必要に非効率的です。
jwilner

72

基本的な例。前半は頻度マップを作成し、後半はそれをフィルタリングされたリストに減らします。おそらくデイブの答えほど効率的ではありませんが、より用途が広いです(正確に2つを検出したい場合など)。

     List<Integer> duplicates = IntStream.of( 1, 2, 3, 2, 1, 2, 3, 4, 2, 2, 2 )
       .boxed()
       .collect( Collectors.groupingBy( Function.identity(), Collectors.counting() ) )
       .entrySet()
       .stream()
       .filter( p -> p.getValue() > 1 )
       .map( Map.Entry::getKey )
       .collect( Collectors.toList() );

12
この答えは、線形であり、「ステートレス述語」ルールに違反しないため、正しい1つのimoです。
jwilner

55

allItems配列の内容全体を保持するためのセット(以下)が必要ですが、これはO(n)です。

Integer[] numbers = new Integer[] { 1, 2, 1, 3, 4, 4 };
Set<Integer> allItems = new HashSet<>();
Set<Integer> duplicates = Arrays.stream(numbers)
        .filter(n -> !allItems.add(n)) //Set.add() returns false if the item was already in the set.
        .collect(Collectors.toSet());
System.out.println(duplicates); // [1, 4]

18
filter()ステートレス述語が必要です。あなたの「解決策」は、javadocで与えられたステートフル述語の例と非常に似ています:docs.oracle.com/javase/8/docs/api/java/util/stream/…–
Matt McHenry

1
@MattMcHenry:それは、このソリューションが予期しない動作を引き起こす可能性があることを意味しますか、それとも単に悪い習慣ですか?
IcedD​​ante 2016

7
@IcedD​​anteストリームが確実にあることがわかっているようなローカライズされたケースではsequential()おそらく安全です。ストリームが存在する可能性があるより一般的なケースではparallel()、奇妙な方法で中断することがほぼ保証されています。
Matt McHenry

5
状況によっては予期しない動作を引き起こすことに加えて、BlochがEffective Javaの第3版ではすべきではないと主張しているように、これはパラダイムを混合します。これを書いていることに気付いた場合は、forループを使用してください。
jwilner 2018

6
Hibernateバリで使用される野生ビーイングでこれを見つけたUniqueElementsの制約。
デイブ

14

O(n)の方法は次のようになります。

List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 4, 4);
Set<Integer> duplicatedNumbersRemovedSet = new HashSet<>();
Set<Integer> duplicatedNumbersSet = numbers.stream().filter(n -> !duplicatedNumbersRemovedSet.add(n)).collect(Collectors.toSet());

このアプローチでは、スペースの複雑さが2倍になりますが、そのスペースは無駄ではありません。実際には、セットとしてのみ複製されたものと、すべての複製も削除された別のセットがあります。


13

マイStreamExのJavaの8ストリームを強化するライブラリは、特別な操作を提供しdistinct(atLeast)、少なくとも指定された回数を表示される要素のみを保持することができます。したがって、問題は次のように解決できます。

List<Integer> repeatingNumbers = StreamEx.of(numbers).distinct(2).toList();

内部的には@Daveソリューションに似ており、オブジェクトをカウントして他の必要な量をサポートしConcurrentHashMap、並列対応です(並列化されたストリームに使用しますがHashMap、順次に使用します)。大量のデータの場合は、を使用して高速化できます.parallel().distinct(2)


26
問題は、サードパーティのライブラリではなく、Javaストリームに関するものです。
ᄂᄀ 2017年

9

あなたはこのように複製を得ることができます:

List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 4, 4);
Set<Integer> duplicated = numbers
  .stream()
  .filter(n -> numbers
        .stream()
        .filter(x -> x == n)
        .count() > 1)
   .collect(Collectors.toSet());

11
それはO(n ^ 2)操作ではありませんか?
Trejkaz 2015年

4
使用してみてくださいnumbers = Arrays.asList(400, 400, 500, 500);
Tagir Valeev 2015

1
これは、2深度ループの作成に似ていますか?それがどのように動作するか、内部で(..){のために(..)}だけの骨董品のため
redigaffi

それは素晴らしいアプローチですが、それでもstream内部を持つことstreamはコストがかかります。
VishwaRatna20年

4

この質問に対する基本的な解決策は次のようになります。

Supplier supplier=HashSet::new; 
HashSet has=ls.stream().collect(Collectors.toCollection(supplier));

List lst = (List) ls.stream().filter(e->Collections.frequency(ls,e)>1).distinct().collect(Collectors.toList());

さて、フィルター操作を実行することはお勧めしませんが、理解を深めるために、私はそれを使用しました。さらに、将来のバージョンではいくつかのカスタムフィルターがあるはずです。


3

マルチセットは、各要素の出現回数を維持する構造です。Guava実装の使用:

Set<Integer> duplicated =
        ImmutableMultiset.copyOf(numbers).entrySet().stream()
                .filter(entry -> entry.getCount() > 1)
                .map(Multiset.Entry::getElement)
                .collect(Collectors.toSet());

2

追加のマップまたはストリームの作成には、時間とスペースがかかります…

Set<Integer> duplicates = numbers.stream().collect( Collectors.collectingAndThen(
  Collectors.groupingBy( Function.identity(), Collectors.counting() ),
  map -> {
    map.values().removeIf( cnt -> cnt < 2 );
    return( map.keySet() );
  } ) );  // [1, 4]


…そして、どちらの質問が [重複]であると主張されているかについて

public static int[] getDuplicatesStreamsToArray( int[] input ) {
  return( IntStream.of( input ).boxed().collect( Collectors.collectingAndThen(
      Collectors.groupingBy( Function.identity(), Collectors.counting() ),
      map -> {
        map.values().removeIf( cnt -> cnt < 2 );
        return( map.keySet() );
      } ) ).stream().mapToInt( i -> i ).toArray() );
}

1

重複の存在を検出するだけでよい場合(OPが望んでいたものをリストするのではなく)、それらをリストとセットの両方に変換してから、サイズを比較します。

    List<Integer> list = ...;
    Set<Integer> set = new HashSet<>(list);
    if (list.size() != set.size()) {
      // duplicates detected
    }

間違いの場所が少ないので、私はこのアプローチが好きです。


0

私はこのような問題を修正するための良い解決策があると思います-リスト=> Something.aとSomething.bでグループ化したリスト。拡張された定義があります:

public class Test {

    public static void test() {

        class A {
            private int a;
            private int b;
            private float c;
            private float d;

            public A(int a, int b, float c, float d) {
                this.a = a;
                this.b = b;
                this.c = c;
                this.d = d;
            }
        }


        List<A> list1 = new ArrayList<A>();

        list1.addAll(Arrays.asList(new A(1, 2, 3, 4),
                new A(2, 3, 4, 5),
                new A(1, 2, 3, 4),
                new A(2, 3, 4, 5),
                new A(1, 2, 3, 4)));

        Map<Integer, A> map = list1.stream()
                .collect(HashMap::new, (m, v) -> m.put(
                        Objects.hash(v.a, v.b, v.c, v.d), v),
                        HashMap::putAll);

        list1.clear();
        list1.addAll(map.values());

        System.out.println(list1);
    }

}

クラスA、list1それはただの受信データです-魔法はObjects.hash(...)にあります:)


1
警告:もしがObjects.hash同じ値を生成(v.a_1, v.b_1, v.c_1, v.d_1)し、(v.a_2, v.b_2, v.c_2, v.d_2)その後、彼らは実際には、Aさん、Bさん、Cさん、そしてD'sが同じであることを確認せず、等しいと見なされるために、重複として削除される予定です。これは許容できるリスクである可能性があります。またはObjects.hash、ドメイン全体で一意の結果を生成することが保証されている以外の関数を使用することもできます。
マーティニール

0

Java 8イディオム(スチーム)を使用する必要がありますか?Perphapsの簡単な解決策は、複雑さを、数値をキーとして(繰り返しなしで)保持し、それが値として発生する回数を保持するマップのようなデータ構造に移動することです。あなたは彼らがそのマップを繰り返すことができますが、それはocurrs> 1であるそれらの数でのみ何かをします。

import java.lang.Math;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;

public class RemoveDuplicates
{
  public static void main(String[] args)
  {
   List<Integer> numbers = Arrays.asList(new Integer[]{1,2,1,3,4,4});
   Map<Integer,Integer> countByNumber = new HashMap<Integer,Integer>();
   for(Integer n:numbers)
   {
     Integer count = countByNumber.get(n);
     if (count != null) {
       countByNumber.put(n,count + 1);
     } else {
       countByNumber.put(n,1);
     }
   }
   System.out.println(countByNumber);
   Iterator it = countByNumber.entrySet().iterator();
    while (it.hasNext()) {
        Map.Entry pair = (Map.Entry)it.next();
        System.out.println(pair.getKey() + " = " + pair.getValue());
    }
  }
}

0

このソリューションを試してください:

public class Anagramm {

public static boolean isAnagramLetters(String word, String anagramm) {
    if (anagramm.isEmpty()) {
        return false;
    }

    Map<Character, Integer> mapExistString = CharCountMap(word);
    Map<Character, Integer> mapCheckString = CharCountMap(anagramm);
    return enoughLetters(mapExistString, mapCheckString);
}

private static Map<Character, Integer> CharCountMap(String chars) {
    HashMap<Character, Integer> charCountMap = new HashMap<Character, Integer>();
    for (char c : chars.toCharArray()) {
        if (charCountMap.containsKey(c)) {
            charCountMap.put(c, charCountMap.get(c) + 1);
        } else {
            charCountMap.put(c, 1);
        }
    }
    return charCountMap;
}

static boolean enoughLetters(Map<Character, Integer> mapExistString, Map<Character,Integer> mapCheckString) {
    for( Entry<Character, Integer> e : mapCheckString.entrySet() ) {
        Character letter = e.getKey();
        Integer available = mapExistString.get(letter);
        if (available == null || e.getValue() > available) return false;
    }
    return true;
}

}

0

インデックスのチェックはどうですか?

        numbers.stream()
            .filter(integer -> numbers.indexOf(integer) != numbers.lastIndexOf(integer))
            .collect(Collectors.toSet())
            .forEach(System.out::println);

1
正常に動作するはずですが、他のいくつかのソリューションと同様にO(n ^ 2)のパフォーマンスもここにあります。
フロリアン・アルブレヒト
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.