Java 8, Streams에서 중복 요소 찾기


87

정수 목록에 중복 요소를 나열하려고합니다. 예를 들어,

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

jdk 8의 스트림을 사용하고 있습니다. 중복을 제거하기 위해 distinct () API를 사용할 수 있습니다. 하지만 중복 된 요소를 찾는 것은 어떻습니까? 아무도 나를 도울 수 있습니까?



스트림을 수집하지 않으려면 본질적으로 "스트림에서 한 번에 두 개 이상의 항목을 볼 수있는 방법"으로 요약됩니다.
Thorbjørn Ravn Andersen

Set <Integer> 항목 = new HashSet (); numbers.stream (). filter (n-> i! tems.add (n)). collect (Collectors.toSet ());
Saroj Kumar Sahoo

답변:


127

다음을 사용할 수 있습니다 Collections.frequency.

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

11
@OussamaZoghlami answer 에서와 동일한 O (n ^ 2) 성능은 아마도 더 간단합니다. 그럼에도 불구하고 여기에 찬성표가 있습니다. StackOverflow에 오신 것을 환영합니다!
Tagir Valeev

6
언급했듯이 이것은 사소한 선형 솔루션이 존재하는 ^ 2 솔루션입니다. 나는 이것을 CR에서 받아들이지 않을 것입니다.
jwilner

3
@Dave 옵션보다 느릴 수 있지만 더 예뻐서 성능 저하를 가져갈 것입니다.
jDub9

@jwilner는 필터에서 Collections.frequency 사용을 참조하는 n ^ 2 솔루션에 대한 귀하의 요점입니까?
mancocapac 19

5
@mancocapac 예, 주파수 호출은 숫자의 모든 요소를 ​​방문해야하고 모든 요소에서 호출되기 때문에 2 차입니다. 따라서 각 요소에 대해 모든 요소를 ​​방문합니다. n ^ 2이고 불필요하게 비효율적입니다.
jwilner

71

기본 예. 전반부는 주파수 맵을 만들고 후반부는 필터링 된 목록으로 축소합니다. 아마도 Dave의 대답만큼 효율적이지는 않지만 더 다양합니다 (정확히 두 개를 감지하려는 경우 등).

     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
이 답변은 선형적이고 "상태 비 저장 술어"규칙을 위반하지 않기 때문에 올바른 답변입니다.
jwilner

53

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 :이 솔루션이 예상치 못한 동작을 일으킬 가능성이 있음을 의미합니까, 아니면 나쁜 습관입니까?
IcedDante

7
@IcedDante 스트림이라는 것을 확실히 알고있는 현지화 사례에서는 아마도 안전 sequential()것입니다 . 스트림이 일 수있는보다 일반적인 경우에는 parallel()이상한 방식으로 중단되는 것이 거의 보장됩니다.
Matt McHenry

5
일부 상황에서 예상치 못한 동작을 생성하는 것 외에도 Bloch가 Effective Java의 제 3 판에 참여해서는 안된다고 주장하는 것처럼 패러다임을 혼합합니다. 이것을 작성하고 있다면 for 루프를 사용하십시오.
jwilner

6
Hibernate Validator UniqueElements 제약에 의해 사용되는 야생에서 이것을 발견했습니다 .
Dave

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());

이 접근 방식에서는 공간 복잡성이 두 배가 될 것이지만 그 공간은 낭비가 아닙니다. 사실, 우리는 이제 모든 복제물도 제거 된 다른 세트뿐만 아니라 세트로만 복제 된 것을 가지고 있습니다.


13

Java 8 스트림을 향상시키는 My StreamEx 라이브러리 distinct(atLeast)는 지정된 횟수 이상 나타나는 요소 만 유지할 수 있는 특수 작업 을 제공합니다 . 따라서 다음과 같이 문제를 해결할 수 있습니다.

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

내부적으로는 @Dave 솔루션과 유사하며, 원하는 수량을 지원하기 위해 객체를 계산하고 병렬 친화적입니다 ( ConcurrentHashMap병렬 스트림에 사용되지만 HashMap순차에 사용됨). 많은 양의 데이터의 경우를 사용하여 속도를 높일 수 있습니다 .parallel().distinct(2).


26
문제는 타사 라이브러리가 아닌 Java Streams에 관한 것입니다.
ᄂ ᄀ

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

4
사용하려고numbers = Arrays.asList(400, 400, 500, 500);
Tagir Valeev 보낸 사람에게

1
2 깊이 루프를 만드는 것과 비슷합니까? 대한 (..) 단지의 작동 방법을 내부적으로 골동품 {(..)에 대한}
redigaffi

좋은 접근 방식이지만 stream내부를 갖는 stream것은 비용이 많이 듭니다.
Vishwa Ratna

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

나는이 같은 문제를 해결하는 방법이 있다고 생각합니다-List => 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대해 동일한 값을 생성하면 a, b, c 및 d가 동일한 지 실제로 확인하지 않고 동일한 것으로 간주되어 중복으로 제거됩니다. 이는 허용 가능한 위험이거나 도메인 전체에서 고유 한 결과를 생성하도록 보장되는 기능 이외의 기능을 사용할 수 있습니다. (v.a_1, v.b_1, v.c_1, v.d_1)(v.a_2, v.b_2, v.c_2, v.d_2)Objects.hash
Marty Neal

0

자바 8 관용구 (스팀)를 사용해야합니까? Perphaps의 간단한 해결책은 숫자를 키로 (반복하지 않고) 보유하고 값으로 발생하는 시간을 포함하는 맵과 유사한 데이터 구조로 복잡성을 이동하는 것입니다. 당신은 그지도를 반복해서 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) 성능도 여기에 있습니다.
Florian Albrecht
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.