Java 8, Yinelenen öğeleri bulmak için Akışlar


87

Tamsayı listesindeki yinelenen öğeleri listelemeye çalışıyorum, örneğin örneğin,

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

jdk 8 Akışlarını kullanarak. Kimse denedi mi. Yinelenenleri kaldırmak için, farklı () api kullanabiliriz. Peki yinelenen öğeleri bulmaya ne dersiniz? Bana yardım edebilecek biri var mı?



Akışı toplamak istemiyorsanız, bu özünde "bir akışta aynı anda birden fazla öğeye nasıl bakabilirim?"
Thorbjørn Ravn Andersen

<Integer> öğeleri = new HashSet (); numbers.stream (). filter (n -> i! tems.add (n)). Collect (Collectors.toSet ());
Saroj Kumar Sahoo

Yanıtlar:


127

Şunları kullanabilirsiniz Collections.frequency:

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

11
@OussamaZoghlami yanıtındakiyle aynı O (n ^ 2) performansı , ancak muhtemelen daha basit. Yine de burada bir olumlu oy var. StackOverflow'a hoş geldiniz!
Tagir Valeev

6
Belirtildiği gibi, bu önemsiz bir doğrusal çözümün var olduğu bir ^ 2 çözümüdür. Bunu CR'de kabul etmem.
jwilner

3
@Dave seçeneğinden daha yavaş olabilir, ancak daha güzel, bu yüzden performans vuruşunu alacağım.
jDub9

@jwilner, bir filtrede Collections.frekans kullanımına atıfta bulunan n ^ 2 çözümüyle ilgili noktanız mı?
mancocapac

5
@mancocapac evet, bu kuadratik çünkü frekans çağrısının sayılardaki her öğeyi ziyaret etmesi gerekiyor ve her öğede çağrılıyor. Böylece, her bir element için, her elementi ziyaret ediyoruz - n ^ 2 ve gereksiz yere verimsiz.
jwilner

72

Temel örnek. İlk yarı frekans haritasını oluşturur, ikinci yarı onu filtrelenmiş bir listeye indirger. Muhtemelen Dave'in cevabı kadar verimli değil, ancak daha çok yönlü (tam olarak iki tane tespit etmek istiyorsanız gibi)

     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
Bu cevap doğru bir imo çünkü doğrusaldır ve "durumsuz yüklem" kuralını ihlal etmez.
jwilner

55

allItemsTüm dizi içeriğini tutmak için bir kümeye ( aşağıda) ihtiyacınız var , ancak bu 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()vatansız bir yüklem gerektirir. "Çözümünüz", javadoc'ta
Matt McHenry

1
@MattMcHenry: Bu, çözümün beklenmedik davranışlar üretme potansiyeline sahip olduğu anlamına mı geliyor yoksa sadece kötü bir uygulama mı?
IcedDante

7
@IcedDante Akışın olduğundan emin olduğunuz yerelleştirilmiş bir durumda sequential(), muhtemelen güvenlidir. Akışın olabileceği daha genel durumda parallel(), tuhaf şekillerde kırılması neredeyse garantidir.
Matt McHenry

5
Bazı durumlarda beklenmedik davranışlar üretmenin yanı sıra, bu, Bloch'un Etkili Java'nın üçüncü baskısında yapmamanız gerektiğini iddia ettiği gibi paradigmaları karıştırır. Kendinizi bunu yazarken bulursanız, sadece bir for döngüsü kullanın.
jwilner

6
Bunu, Hibernate Validator UniqueElements kısıtlaması tarafından kullanılan vahşi ortamda bulundu .
Dave

14

Bir O (n) yolu aşağıdaki gibi olacaktır:

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

Bu yaklaşımda uzay karmaşıklığı iki katına çıkar, ancak bu alan bir israf değildir; aslında, artık sadece bir Set olarak tek başına kopyalananların yanı sıra tüm kopyaların kaldırıldığı başka bir Set olarak da var.


13

Java 8 akışlarını geliştiren My StreamEx kitaplığı, distinct(atLeast)yalnızca en az belirtilen sayıda görünen öğeleri tutabilen özel bir işlem sağlar . Yani probleminiz şu şekilde çözülebilir:

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

Dahili olarak @Dave çözümüne benzer, diğer istenen miktarları desteklemek için nesneleri sayar ve paralel dostudur ( ConcurrentHashMapparalelleştirilmiş akış için kullanır , ancak HashMapsıralı için). Büyük miktarda veri için kullanarak hızlandırabilirsiniz .parallel().distinct(2).


26
Soru, üçüncü taraf kitaplıkları değil, Java Akışları hakkındadır.
ᄂ ᄀ

9

Kopyalanmış olanı şu şekilde elde edebilirsiniz:

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
Bu bir O (n ^ 2) işlemi değil mi?
Trejkaz

4
numbers = Arrays.asList(400, 400, 500, 500);
Kullanmayı

1
Bu 2 derinlikli bir döngü oluşturmaya benzer mi? for (..) {for (..)} Dahili olarak nasıl çalıştığını merak
redigaffi

Güzel bir yaklaşım olsa da streamiçeriye sahip streamolmak maliyetli.
Vishwa Ratna

4

Sorunun temel çözümlerinin aşağıdaki gibi olması gerektiğini düşünüyorum:

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

Eh, bir filtre işlemi yapmanız tavsiye edilmez, ancak daha iyi anlamak için kullandım, ayrıca gelecekteki sürümlerde bazı özel filtreleme olması gerekir.


3

Çoklu set, her bir elemanın oluşum sayısını koruyan bir yapıdır. Guava uygulamasını kullanma:

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

2

Ek bir haritanın veya akışın oluşturulması zaman ve yer kaplar…

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]


… Ve hangisinin [kopya] olduğu iddia edilen soru için

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

Yalnızca yinelenenlerin varlığını tespit etmeniz gerekiyorsa (bunları listelemek yerine, ki bu OP'nin istediği şeydir), onları hem Liste hem de Küme'ye dönüştürün, ardından boyutları karşılaştırın:

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

Bu yaklaşımı seviyorum çünkü daha az hata yeri var.


0

Sanırım böyle bir sorunu nasıl çözeceğime dair iyi bir çözüme sahip olduğumu düşünüyorum - Liste => Bir Şeye göre gruplama ile Liste. Genişletilmiş tanım var:

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

}

sınıf A, liste1 sadece gelen veri - sihir Objects.hash (...) :)


1
Uyarı: ve Objects.hashiçin aynı değeri üretirlerse, eşit kabul edilecekler ve a, b, c ve d'lerin aynı olup olmadığı kontrol edilmeden kopya olarak kaldırılacaklar. Bu kabul edilebilir bir risk olabilir veya etki alanınızda benzersiz bir sonuç üretmesi garanti edilenden farklı bir işlev kullanmak isteyebilirsiniz . (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

Java 8 deyimlerini (steams) kullanmak zorunda mısınız? Basit bir çözüm, karmaşıklığı, sayıları anahtar olarak (tekrar etmeden) ve bir değer olarak ortaya çıktığı zamanları tutan veri yapısına benzer bir haritaya taşımak olabilir. Bu haritayı yineleyebilir ve yalnızca> 1 olan sayılarla bir şeyler yapabilirsin.

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

Bu çözümü deneyin:

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

Dizinlerin kontrol edilmesine ne dersiniz?

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

1
İyi çalışmalı, aynı zamanda burada diğer bazı çözümler olarak O (n ^ 2) performansı.
Florian Albrecht
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.