Java 8,流查找重复元素


87

我试图列出整数列表中的重复元素,例如

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

使用jdk 8的流。有人尝试过吗?要删除重复项,我们可以使用distinct()api。但是,找到重复的元素呢?有人可以帮我吗?



如果您不想收集信息流,那么本质上可以归结为“我如何一次查看信息流中的多个项目”?
托尔比约恩Ravn的安德森

Set <Integer>项目= new HashSet(); Numbers.stream()。filter(n-> i!tems.add(n))。collect(Collectors.toSet());
Saroj Kumar Sahoo

Answers:


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 '18 -4-3

3
它可能比@Dave选项要慢,但是它更漂亮,因此我将对性能造成影响。
jDub9

@jwilner关于n ^ 2解决方案的观点是指在过滤器中使用Collections.frequency吗?
mancocapac

5
@mancocapac是的,它是二次方的,因为频率调用必须访问数字中的每个元素,并且每个元素都被调用。因此,对于每个元素,我们访问每个元素-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
这个答案是正确的一个imo,因为它是线性的,并且不违反“无状态谓词”规则。
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/...
马特·麦克亨利

1
@MattMcHenry:这是否意味着该解决方案可能会产生意外行为,还是不好的做法?
IcedD​​ante

7
@IcedD​​ante在本地化的情况下(例如,在那里您确定流是肯定的)sequential(),这可能是安全的。在更一般的情况下,流可能是parallel(),因此可以保证以奇怪的方式中断流。
马特·麦克亨利

5
除了在某些情况下产生意外行为外,这还混合了范例,因为Bloch认为您不应该在第三版的Effective Java中使用。如果发现自己正在编写此代码,则只需使用for循环即可。
jwilner '18 -4-3

6
在Hibernate Validator 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());

这种方法会使空间复杂度提高一倍,但是空间并不是浪费。实际上,我们现在仅将重复项作为一个Set以及将所有重复项都删除的另一个Set。


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,2015年

4
尝试使用numbers = Arrays.asList(400, 400, 500, 500);
Tagir Valeev 2015年

1
这类似于创建2深度循环吗?for(..){for(..)}只是
好奇

尽管这是一种不错的方法,但是拥有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,b,c和d是否相同。这可能是可以接受的风险,或者您可能希望使用除Objects.hash保证可以在整个域中产生唯一结果之外的其他功能。
马蒂·尼尔

0

您是否必须使用Java 8习语(蒸汽)?可以认为,一种简单的解决方案是将复杂度移至类似于地图的数据结构,该结构将数字作为键(不重复)并将其出现的时间作为值。您可以让他们迭代该映射仅对出现的数字> 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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.