6

I have an array as follows:

int[] array = {11, 14, 17, 11, 48, 33, 29, 11, 17, 22, 11, 48, 18};

What I wanted to do was to find the duplicate values, and print them.

So my way of doing this was to convert to ArrayList, then to Set and use a stream on the Set.

ArrayList<Integer> list = new ArrayList<>(array.length);
for (int i = 0; i < array.length; i++) {
    list.add(array[i]);
}

Set<Integer> dup = new HashSet<>(list);

I then used a stream to loop through it and print the values using Collections.frequency.

dup.stream().forEach((key) -> {
            System.out.println(key + ": " + Collections.frequency(list, key));
        });

Which will of course print them all, even if the count is one.

I thought adding in if(key > 1) but it's the value I want not the key.

How can I get the value in this instance to print only where value > 2.

I could probably put in:

int check = Collections.frequency(list, key);
            if (check > 1) {

but this then duplicates Collections.frequency(list, key) in the stream and is quite ugly.

Youcef LAIDANI
  • 55,661
  • 15
  • 90
  • 140
achAmháin
  • 4,176
  • 4
  • 17
  • 40

10 Answers10

7

Probably you can to use filter to get only the values great than 2 :

dup.stream()
       .filter(t -> Collections.frequency(list, t) > 2)
       .forEach(key -> System.out.println(key + ": " + Collections.frequency(list, key)));

result in your case is :

11: 4

Edit

Another solution :

No need to use a Set or Collections.frequency you can just use :

Integer[] array = {11, 14, 17, 11, 48, 33, 29, 11, 17, 22, 11, 48, 18};
Arrays.stream(array).collect(Collectors.groupingBy(p -> p, Collectors.counting()))
        .entrySet().stream().filter(t -> t.getValue() > 1)
        .forEach(key -> System.out.println(key.getKey() + ": " + key.getValue()));

Output

48: 2
17: 2
11: 4
Youcef LAIDANI
  • 55,661
  • 15
  • 90
  • 140
4

The complete example without initialising dup in a loop and calling Collections.frequency for every unique element once:

Integer[] array = {11, 14, 17, 11, 48, 33, 29, 11, 17, 22, 11, 48, 18};
List<Integer> list = Arrays.asList(array);
Arrays.stream(array).collect(Collectors.toSet())
  .stream()
  .map(v -> new SimpleEntry<>(v, Collections.frequency(list, v)))
  .filter(v -> v.getValue() > 1)
  .forEach(v -> System.out.println(v.getKey() + ":" + v.getValue()));
Adam Siemion
  • 15,569
  • 7
  • 58
  • 92
4

The problem with Collections.frequency is that it has to traverse all the collection to find the frequency of the given element. If you do this for every element of the collection, then your solution is O(n^2), i.e. extremely inefficient, because the processing time increases as the square of the number of elements in the collection.

Instead, you could use a stream to create a map that counts the occurrences of each element, as follows:

int[] array = {11, 14, 17, 11, 48, 33, 29, 11, 17, 22, 11, 48, 18};

Map<Integer, Long> occurrences = Arrays.stream(array)
    .boxed()
    .collect(Collectors.groupingBy(
        Function.identity(), 
        Collectors.counting()));

Now, if you want to only keep the values with more than 1 occurrence, you can simply remove the entries of the map whose value is equal to 1:

occurrences.values().removeIf(v -> v == 1);

Finally, if you print the map:

System.out.println(occurrences);

You'll get the following output:

{48=2, 17=2, 11=4}

Or, to get the output in the format you expect:

occurrences.forEach((k, v) -> System.out.println(k + ": "+ v));

Another way, much shorter and without the overhead of streams:

Map<Integer, Long> occurrences = new HashMap<>();
for (int n : array) occurrences.merge(n, 1L, Long::sum);

Then, you remove entries for unique elements as shown before:

occurrences.values().removeIf(v -> v == 1);
fps
  • 33,623
  • 8
  • 55
  • 110
2

This should work:

int[] array = {11, 14, 17, 11, 48, 33, 29, 11, 17, 22, 11, 48, 18};

List<Integer> list = Arrays.stream(array).boxed().collect(Collectors.toList());

// print values appearing more than once
list.stream().filter(i -> Collections.frequency(list, i) >1)
        .collect(Collectors.toSet()).forEach(System.out::println);

Would print:

48  
17  
11  

If you also would like to get the occurences you could do something like this:

int[] array = {11, 14, 17, 11, 48, 33, 29, 11, 17, 22, 11, 48, 18};

List<Integer> list = Arrays.stream(array).boxed().collect(Collectors.toList());

list.stream()
    .filter(i -> Collections.frequency(list, i) >1)
    .collect(Collectors.toMap(identity(), v -> 1, Integer::sum))
    .forEach((x, y) -> System.out.println("Key: " + x +", occurence: "+ y));

which prints

Key: 48, occurence: 2
Key: 17, occurence: 2
Key: 11, occurence: 4
Diyarbakir
  • 1,999
  • 18
  • 36
2
package various;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class Frequency {
    public static void main(String[] args) {
        int[] array = {11, 14, 17, 11, 48, 33, 29, 11, 17, 22, 11, 48, 18};
        ArrayList<Integer> list = new ArrayList<>(array.length);
        for (int i = 0; i < array.length; i++) {
            list.add(array[i]);
        }

        Set<Integer> dup = new HashSet<>(list);

        dup.stream().forEach((key) -> {
            System.out.print((Collections.frequency(list, key) > 2) ? key + ": " + Collections.frequency(list, key) +"\n" : "");
        });
    }
}

Output:

11: 4
Mark
  • 56
  • 1
  • 9
2
HashMap<Integer, Integer> hashmap = new HashMap<>();
for (int i : array) {
    if (hashmap.containsKey(i)) {
        hashmap.put(i, hashmap.get(i) + 1);
    } else {
        hashmap.put(i, 1);
    }
}

Stores the value and its frequency.

{48=2, 17=2, 33=1, 18=1, 22=1, 11=4, 29=1, 14=1}

EDIT: if you want to print the duplicates;

hashmap.entrySet().stream().filter((entry) -> ((int) entry.getValue() >= 2)).forEach((entry) -> {
    System.out.println(entry.getKey() + ":" + entry.getValue());
});

Gives you:

48:2
17:2
11:4
Touniouk
  • 375
  • 1
  • 13
2

To implement the desired functionality, we can facilitate Java Streams, in particular, the Stream.collect method:

<R> R collect(Supplier<R> supplier,
    BiConsumer<R,? super T> accumulator,
    BiConsumer<R,R> combiner)

Please consider the following draft implementation:

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;

public class Program {
    public static void main(final String[] args) {
        // Input.
        final int[] inputArray = {11, 14, 17, 11, 48, 33, 29, 11, 17, 22, 11, 48, 18};

        // Processing.
        final DuplicateCounter<Integer> duplicateCounter = Arrays.stream(inputArray)
            .collect(
                DuplicateCounter<Integer>::new,
                DuplicateCounter<Integer>::accept,
                DuplicateCounter<Integer>::combine
            );

        // Output.
        System.out.println(duplicateCounter.getDuplicateCountMap());
    }

    private static final class DuplicateCounter<T> implements Consumer<T> {
        private final Map<T, Integer> countMap = new HashMap<>();

        public void accept(final T obj) {
            this.incrementCounter(obj, 1);
        }

        public void combine(final DuplicateCounter<T> other) {
            other.countMap.entrySet()
                .forEach(entry -> this.incrementCounter(entry.getKey(), entry.getValue()));
        }

        public Map<T, Integer> getDuplicateCountMap() {
            return this.countMap.entrySet()
                .stream()
                .filter(entry -> entry.getValue() > 1)
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        }

        private void incrementCounter(final T obj, int increment) {
            final Integer counter = this.countMap.getOrDefault(obj, 0);
            this.countMap.put(obj, counter + increment);
        }
    }
}

It outputs:

{48=2, 17=2, 11=4}

Additional references:

  1. Reduction (The Java™ Tutorials > Collections > Aggregate Operations).
  2. Java 8 Streams - collect vs reduce.
2

And yet another functional way:

HashMap<Integer, Long> result = Arrays.stream(array)
            .boxed()
            .collect(Collectors.collectingAndThen(Collectors.groupingBy(
                    Function.identity(),
                    HashMap::new,
                    Collectors.counting()),
                    map -> {
                        map.entrySet().removeIf(x -> x.getValue() == 1);
                        return map;
                    }));

Btw the accepted answer - is probably the worst here, like every single suggestion to use Collections.frequency for each element (even if unique)

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • Or `map.values().removeIf(v -> v < 2)`. But it's basically just a stateful version of the second solution in the accepted answer. – shmosel Nov 17 '17 at 22:32
  • @shmosel I haven't even looked below the edit there, thank you – Eugene Nov 17 '17 at 22:34
1

You can use Filter :

dup.stream().filter(key -> Collections.frequency(list,key)>2).forEach(System.out::println);
Azzabi Haythem
  • 2,318
  • 7
  • 26
  • 32
1

You don't have to convert it to ArrayList at all. Take a look at this:

HashSet<Integer> set = new HashSet<Integer>();
for (int i = 0; i < array.length; i++) {
    if(set.contains(array[i]))
        System.out.println(array[i]);
    else
        set.add(array[i]);
}
omerfarukdogan
  • 839
  • 9
  • 26