Java 8, Luồng để tìm các phần tử trùng lặp


87

Tôi đang cố gắng liệt kê các phần tử trùng lặp trong danh sách số nguyên, ví dụ:

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

bằng cách sử dụng Streams of jdk 8. Có ai đã thử. Để loại bỏ các bản sao, chúng ta có thể sử dụng api riêng biệt (). Nhưng còn việc tìm các phần tử trùng lặp thì sao? Ai có thể giúp tôi ra ngoài?



Nếu bạn không muốn thu thập luồng, thì điều này về cơ bản chỉ tóm tắt là "làm cách nào để tôi có thể xem nhiều mục cùng lúc trong một luồng"?
Thorbjørn Ravn Andersen

Đặt <Integer> items = new HashSet (); number.stream (). filter (n -> i! tems.add (n)). thu thập (Collectors.toSet ());
Saroj Kumar Sahoo

Câu trả lời:


127

Bạn có thể sử dụng Collections.frequency:

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

11
Hiệu suất O (n ^ 2) tương tự như trong câu trả lời @OussamaZoghlami , mặc dù có lẽ đơn giản hơn. Tuy nhiên, đây là một ủng hộ. Chào mừng bạn đến với StackOverflow!
Tagir Valeev

6
Như đã đề cập, đây là một nghiệm ^ 2 trong đó một nghiệm tuyến tính nhỏ tồn tại. Tôi sẽ không chấp nhận điều này trong CR.
jwilner

3
Nó có thể chậm hơn tùy chọn @Dave, nhưng nó đẹp hơn nên tôi sẽ đánh giá cao hiệu suất.
jDub9

@jwilner là quan điểm của bạn về giải pháp n ^ 2 đề cập đến việc sử dụng Collections.frequency trong bộ lọc?
mancocapac 21-07-19

5
@mancocapac vâng, nó là bậc hai vì cuộc gọi tần suất phải truy cập vào mọi phần tử bằng số và nó được gọi trên mọi phần tử. Do đó, đối với mỗi phần tử, chúng tôi truy cập mọi phần tử - n ^ 2 và không cần thiết phải có hiệu quả.
jwilner 21/07/19

71

Ví dụ cơ bản. Nửa đầu xây dựng bản đồ tần suất, nửa sau giảm nó thành một danh sách được lọc. Có lẽ không hiệu quả như câu trả lời của Dave, nhưng linh hoạt hơn (như nếu bạn muốn phát hiện chính xác hai, v.v.)

     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
Câu trả lời này là câu trả lời đúng trong imo vì nó tuyến tính và không vi phạm quy tắc "vị từ không trạng thái".
jwilner

54

Bạn cần một bộ ( allItemsbên dưới) để chứa toàn bộ nội dung mảng, nhưng đây là 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()yêu cầu một vị ngữ không trạng thái. "Giải pháp" của bạn rất giống với ví dụ về một vị từ trạng thái được đưa ra trong javadoc: docs.oracle.com/javase/8/docs/api/java/util/stream/…
Matt McHenry

1
@MattMcHenry: điều đó có nghĩa là giải pháp này có khả năng tạo ra hành vi không mong muốn, hay nó chỉ là thực hành xấu?
IcedDante

7
@IcedDante Trong một trường hợp được bản địa hóa như ở đó mà bạn biết chắc chắn rằng luồng sequential(), nó có thể an toàn. Trong trường hợp tổng quát hơn, luồng có thể xảy ra parallel(), nó được đảm bảo khá nhiều sẽ bị phá vỡ theo những cách kỳ lạ.
Matt McHenry

5
Ngoài việc tạo ra hành vi không mong muốn trong một số tình huống, điều này còn kết hợp các mô hình như Bloch cho rằng bạn không nên sử dụng phiên bản thứ ba của Java hiệu quả. Nếu bạn thấy mình đang viết điều này, chỉ cần sử dụng vòng lặp for.
jwilner

6
Tìm thấy điều này trong tự nhiên được sử dụng bởi ràng buộc Hibernate Validator UniqueElements .
Dave

14

Một cách O (n) sẽ như sau:

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

Sự phức tạp về không gian sẽ tăng gấp đôi trong cách tiếp cận này, nhưng không gian đó không phải là một sự lãng phí; trên thực tế, bây giờ chúng ta chỉ có một bộ trùng lặp dưới dạng một Bộ cũng như một Bộ khác với tất cả các bản sao cũng bị loại bỏ.


13

Thư viện StreamEx của tôi giúp tăng cường các luồng Java 8 cung cấp một hoạt động đặc biệt distinct(atLeast)có thể chỉ giữ lại các phần tử xuất hiện ít nhất một số lần được chỉ định. Vì vậy, vấn đề của bạn có thể được giải quyết như sau:

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

Bên trong nó tương tự như giải pháp @Dave, nó đếm các đối tượng, để hỗ trợ các số lượng mong muốn khác và nó thân thiện với song song (nó sử dụng ConcurrentHashMapcho luồng song song, nhưng HashMapcho tuần tự). Đối với lượng lớn dữ liệu, bạn có thể tăng tốc độ bằng cách sử dụng .parallel().distinct(2).


26
Câu hỏi là về Java Streams, không phải thư viện của bên thứ ba.
ᄂ ᄀ

9

Bạn có thể nhận được bản sao như thế này:

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
Đó không phải là một phép toán O (n ^ 2) sao?
Trejkaz

4
Cố gắng sử dụngnumbers = Arrays.asList(400, 400, 500, 500);
Tagir Valeev

1
Điều này có tương tự như tạo vòng lặp 2 chiều sâu không? for (..) {for (..)} Chỉ tò mò về cách hoạt động bên trong của nó
redigaffi

Mặc dù đó là một cách tiếp cận tốt, nhưng việc trang bị streambên trong streamrất tốn kém.
Vishwa Ratna

4

Tôi nghĩ các giải pháp cơ bản cho câu hỏi nên như sau:

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

tốt, bạn không nên thực hiện thao tác bộ lọc, nhưng để hiểu rõ hơn, tôi đã sử dụng nó, hơn nữa, nên có một số bộ lọc tùy chỉnh trong các phiên bản sau.


3

Tập hợp nhiều là cấu trúc duy trì số lần xuất hiện cho mỗi phần tử. Sử dụng Guava thực hiện:

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

2

việc tạo một bản đồ hoặc luồng bổ sung tốn nhiều thời gian và không gian…

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]


… Và đối với câu hỏi được cho là [trùng lặp]

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

Nếu bạn chỉ cần phát hiện sự hiện diện của các bản sao (thay vì liệt kê chúng, đó là điều OP muốn), chỉ cần chuyển đổi chúng thành cả Danh sách và Tập hợp, sau đó so sánh các kích thước:

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

Tôi thích cách tiếp cận này vì nó có ít chỗ sai hơn.


0

Tôi nghĩ rằng tôi có giải pháp tốt để khắc phục sự cố như thế này - Danh sách => Danh sách với nhóm theo Something.a & Something.b. Có định nghĩa mở rộng:

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

}

class A, list1 nó chỉ là dữ liệu đến - phép thuật nằm trong Objects.hash (...) :)


1
Cảnh báo: Nếu Objects.hashtạo ra cùng một giá trị cho (v.a_1, v.b_1, v.c_1, v.d_1)(v.a_2, v.b_2, v.c_2, v.d_2), thì chúng sẽ được coi là bằng nhau và bị loại bỏ dưới dạng trùng lặp, mà không thực sự kiểm tra xem các giá trị a, b, c và d có giống nhau không. Đây có thể là một rủi ro có thể chấp nhận được hoặc bạn có thể muốn sử dụng một chức năng khác với chức năng Objects.hashđược đảm bảo để tạo ra một kết quả duy nhất trên miền của bạn.
Marty Neal

0

Bạn có phải sử dụng các thành ngữ java 8 (hơi nước) không? Có lẽ một giải pháp đơn giản sẽ là chuyển độ phức tạp sang một cấu trúc dữ liệu giống như bản đồ, giữ các số làm khóa (không lặp lại) và thời gian nó xuất hiện dưới dạng giá trị. Bạn có thể họ lặp lại bản đồ đó để làm điều gì đó chỉ với những con số có giá trị> 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

Hãy thử giải pháp này:

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

Điều gì về việc kiểm tra các chỉ mục?

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

1
Sẽ hoạt động tốt, nhưng cũng có hiệu suất O (n ^ 2) như một số giải pháp khác ở đây.
Florian Albrecht
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.