Java 8 สตรีมเพื่อค้นหาองค์ประกอบที่ซ้ำกัน


87

ฉันกำลังพยายามแสดงรายการองค์ประกอบที่ซ้ำกันในรายการจำนวนเต็มพูดเช่น

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

ใช้ Streams ของ jdk 8 มีใครลองบ้าง ในการลบรายการที่ซ้ำกันเราสามารถใช้ API ที่แตกต่างกัน () แต่การค้นหาองค์ประกอบที่ซ้ำกันล่ะ? ใครสามารถช่วยฉันออก?



หากคุณไม่ต้องการรวบรวมสตรีมสิ่งนี้จะกลายเป็น "ฉันจะดูมากกว่าหนึ่งรายการพร้อมกันในสตรีมได้อย่างไร"
Thorbjørn Ravn Andersen

ตั้งค่า <Integer> รายการ = HashSet ใหม่ (); numbers.stream (). กรอง (n -> i! tems.add (n)) รวบรวม (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
ประสิทธิภาพ O (n ^ 2) เดียวกันกับในคำตอบของ @OussamaZoghlami แม้ว่าอาจจะง่ายกว่า อย่างไรก็ตามนี่คือการโหวตเพิ่ม ยินดีต้อนรับสู่ StackOverflow!
Tagir Valeev

6
ดังที่ได้กล่าวไปแล้วนี่คือวิธีแก้ปัญหา ^ 2 ที่มีโซลูชันเชิงเส้นเล็กน้อย ฉันจะไม่ยอมรับสิ่งนี้ใน CR
jwilner

3
อาจจะช้ากว่าตัวเลือก @Dave แต่สวยกว่าดังนั้นฉันจะตีประสิทธิภาพ
jDub9

@jwilner เป็นประเด็นของคุณเกี่ยวกับโซลูชัน n ^ 2 ที่อ้างถึงการใช้ Collections.frequency ในตัวกรองหรือไม่?
mancocapac

5
@mancocapac ใช่มันกำลังสองเพราะการโทรความถี่ต้องไปที่ทุกองค์ประกอบเป็นตัวเลขและมันถูกเรียกใช้ในทุกองค์ประกอบ ดังนั้นสำหรับแต่ละองค์ประกอบเราเยี่ยมชมทุกองค์ประกอบ - n ^ 2 และไม่มีประสิทธิภาพโดยไม่จำเป็น
jwilner

72

ตัวอย่างพื้นฐาน ครึ่งแรกสร้างแผนที่ความถี่ครึ่งหลังลดเป็นรายการที่กรองแล้ว อาจไม่ได้มีประสิทธิภาพเท่ากับคำตอบของ 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
คำตอบนี้เป็น 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()ต้องการเพรดิเคตไร้สัญชาติ "วิธีการแก้ปัญหา" ของคุณคล้ายคลึงอย่างมากกับตัวอย่างเพรดิเคต stateful ที่ระบุใน 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 รุ่นที่สาม หากคุณพบว่าตัวเองกำลังเขียนสิ่งนี้ให้ใช้ for loop
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

ไลบรารีStreamExของฉันซึ่งปรับปรุงสตรีม Java 8 ให้การดำเนินการพิเศษ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 deep loop หรือไม่? สำหรับ (.. ) {for (.. )} แค่อยากรู้ว่ามันทำงานอย่างไรภายใน
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สร้างค่าเดียวกันสำหรับ(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's, b's, c's และ d เหมือนกันหรือไม่ นี่อาจเป็นความเสี่ยงที่ยอมรับได้หรือคุณอาจต้องการใช้ฟังก์ชันอื่นนอกเหนือจากObjects.hashที่รับประกันว่าจะให้ผลลัพธ์ที่ไม่ซ้ำใครในโดเมนของคุณ
Marty Neal

0

คุณต้องใช้สำนวน java 8 (steams) หรือไม่? Perphaps วิธีแก้ปัญหาง่ายๆคือการย้ายความซับซ้อนไปยังโครงสร้างข้อมูลที่เหมือนกันซึ่งถือตัวเลขเป็นคีย์ (โดยไม่ต้องทำซ้ำ) และจำนวนครั้งที่ ocurrs เป็นค่า คุณสามารถทำซ้ำแผนที่นั้นและทำบางอย่างกับตัวเลขที่เป็น 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) เป็นโซลูชันอื่น ๆ ที่นี่
Florian Albrecht
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.