8

I've written the following snippet to count the number of occurrences of each element. Is it possible to achieve this in a much shorter way?

int[] arr = {1, 6, 2, 8, 5, 4, 7, 7, 5, 7};
Arrays.stream(arr)
        .collect(ArrayList::new, ArrayList::add, ArrayList::addAll)
        .stream()
        .collect(Collectors.groupingBy(s -> s))
        .forEach((k, v) -> System.out.println(k+" "+v.size()));

Also I would like to display only the elements which occur more than 1 time. So I tried modifying as below which resulted in an error.

.forEach((k, v) -> if(v.size() > 1) System.out.println(k+" "+v.size()));

What is the correct way to do this?

0

5 Answers 5

12

For the latter question, you have to change

.forEach((k, v) -> if(v.size() > 1) System.out.println(k+" "+v.size()));

to

.forEach((k, v) -> {if(v.size() > 1) System.out.println(k+" "+v.size());});

For the first part, it's not clear why you need the first collect followed by a second Stream pipeline. If the purpose was to convert an IntStream to a Stream<Integer>, use boxed():

Arrays.stream(arr)
      .boxed()
      .collect(Collectors.groupingBy(s -> s))
      .forEach((k, v) -> System.out.println(k+" "+v.size()));

As Dici suggested, you can also chain Collectors to group each number with its number of occurrences :

Map<Integer,Integer> occurrences = 
    Arrays.stream(arr)
          .boxed()
          .collect(Collectors.groupingBy(s -> s, Collectors.counting()));
Sign up to request clarification or add additional context in comments.

5 Comments

There's a slighlty better way using Collectors.groupingBy(Function.identity(), Collectors.counting()). With a bunch of static imports it looks nicer
Also is it possible to return the result as a Map<Integer, Integer> instead of printing it?
@DhiwaTdG Yes, you can use Dici's suggestion.
I tried that but it returns all the values whereas I wish to return only the elements which occur more than one time.
@DhiwaTdG You might have to run that Map through another Stream pipeline to filter out the numbers having a single occurrence. I'm not sure whether you can do that in the same Stream pipeline.
3

If you are open to using a third-party library, Eclipse Collections has a Bag type which can be used as follows:

Bags.mutable.with(1, 6, 2, 8, 5, 4, 7, 7, 5, 7)
    .selectDuplicates()
    .forEachWithOccurrences((k, count) -> System.out.println(k+" "+count));

If you have to keep int[] arr variable as an int array, then you can use an IntBag as follows:

IntBags.mutable.with(arr)
    .selectDuplicates()
    .forEachWithOccurrences((k, count) -> System.out.println(k+" "+count));

IntBag is a primitive collection so does not box the int values into Integer wrappers.

Note: I am a committer for Eclipse Collections.

Comments

2
List<Integer> numsList=Arrays.asList(1,2,3,5,2,4,3,1,2,2,4,4,5);
Map<Integer, Long> map=numsList.stream().collect(Collectors.groupingBy(Integer::intValue,Collectors.counting()));
map.forEach((k,v)->{System.out.println(k+" = "+v);});

Comments

0

I want to share my solution as well!!!

// Solution 1 [Improved from Eran's solution & suggestion]
int[] arr = {1, 6, 2, 8, 5, 4, 7, 7, 5, 7};
Map<Integer, Long> counts = Arrays.stream(arr)
    .boxed()
    .collect(collectingAndThen(groupingBy(n -> n, counting()),
        map -> map.entrySet().stream()
            .filter(n -> n.getValue() > 1)
            .collect(toMap(Entry::getKey, Entry::getValue))
));
System.out.println(counts.toString());

// Solution 2 [Improved from Dici's suggestion]
int[] arr = {1, 6, 2, 8, 5, 4, 7, 7, 5, 7};
Map<Object, Long> counts = Arrays.stream(arr)
    .collect(ArrayList::new, ArrayList::add, ArrayList::addAll)
    .stream()
    .collect(groupingBy(Function.identity(), counting()));
counts.values().removeIf(count -> count < 2);
System.out.println(counts.toString());  

2 Comments

Note that by groupingBy specification it's not guaranteed that returned map will be mutable. So such solution may fail in future Java (or non-OpenJDK-based) implementations.
You can easily solve the issue mentioned by @Tagir by using .collect(groupingBy( Function.identity(), HashMap::new, counting()));. Then, the result in guaranteed to be a mutable HashMap and you can use removeIf. Note that this also works together with collectingAndThen. By the way, you don’t need toString() when using System.out.println
0

Also might be done using frequency :

 List<Integer> list = ImmutableList.of(1, 2, 3, 4, 5, 6, 3, 4, 5);
 Map<Integer, Integer> result = list.stream().distinct().collect(Collectors.toMap(Function.identity(), token -> Collections.frequency(list, token)));

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.