Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How could I improve this List iteration with a Stream?

I'm attempting to count the number of times an Int is seen in a field within a List of Objects.

This is the code I have

TreeMap<Integer, Double> ratings = new TreeMap();
ArrayList<Establishment> establishments = new ArrayList<>();

double one = 0;
double two = 0;
double three = 0;
double five = 0;

for (Establishment e : establishments) {
    if (e.getRating() == 1) {
        one++;
    }
    if (e.getRating() == 2) {
        two++;
    }
    if (e.getRating() == 3) {
        three++;
    }
    if (e.getRating() == 5) {
        five++;
    }
}

    ratings.put(1, (one / establishments.size()) * 100);
    ratings.put(2, (two / establishments.size()) * 100);
    ratings.put(3, (three / establishments.size()) * 100);
    ratings.put(5, (five / establishments.size()) * 100);

yet it isn't ideal, if more ratings are added (say 20+) then you'd have a bunch of doubles being created, and it's not maintainable.

I know I could do something with a Stream if I had a list of ints, say

listOfInts.stream().filter(i -> i == 3).count()

yet this is a list of objects, which contain an int and I need to calculate the number of ratings == X in that list of objects.

so pseudocode for what I need:

establishemnts.getAllRatings().stream().filter(ratings -> ratings == 3).count()*

* Repeating for each of the rating types 1 - 5

** There is no getAllRatings - I guess that's the issue I'm trying to solve)

like image 710
Wayneio Avatar asked Mar 10 '19 19:03

Wayneio


People also ask

How do I iterate through a stream list?

You can use stream() method of the List interface which gives a stream to iterate using forEach method. In forEach method, we can use the lambda expression to iterate over all elements.

How streams are better than forEach?

If you have a huge list, a parallel stream will perform better. Purely thinking in terms of performance, you shouldn't use a for-each loop with an ArrayList, as it creates an extra Iterator instance that you don't need (for LinkedList it's a different matter).

What is the performance benefit of using streams?

There are a lot of benefits to using streams in Java, such as the ability to write functions at a more abstract level which can reduce code bugs, compact functions into fewer and more readable lines of code, and the ease they offer for parallelization.


3 Answers

Using more or less only streams:

List<Establishment> establishments = new ArrayList<>();
Map<Integer, Double> ratings = establishments.stream()
        .collect(Collectors.groupingBy(Establishment::getRating, Collectors.counting()))
        .entrySet()
        .stream()
        .collect(Collectors.toMap(e -> e.getKey(), 
                                  e -> 100.0 * (e.getValue() / establishments.size())));
like image 177
Marek Broda Avatar answered Oct 16 '22 14:10

Marek Broda


You can do:

Map<Integer, Double> ratings = new TreeMap<>();
List<Establishment> establishments = new ArrayList<>();
establishments.stream() 
              .collect(Collectors.groupingBy(Establishment::getRating, Collectors.counting()))
              .forEach((k, v) -> ratings.put(k, (double)v/establishments.size() * 100));

Which will use Collectors::groupingBy with Collectors::counting, that will create a Map consisting of the count of the ratings, then use forEach to add them the TreeMap

Or, as suggested by VGR, an even more elegant use of Collectors::collectingAndThen:

Map<Integer, Double> ratings =
establishments.stream()
              .collect(
                       Collectors.groupingBy(Establishment::getRating, 
                       Collectors.collectingAndThen(Collectors.counting(), c -> c * 100.0 / establishments.size())
               ));

Which will directly create the Map without having to create a Map, stream over it again, and then collect to a Map again

like image 36
GBlodgett Avatar answered Oct 16 '22 15:10

GBlodgett


Here is how you can implement getAllRatings using streams to get list of ratings

List<Integer> attributes = establishments.stream().map(es -> es.getRating()).collect(Collectors.toList());

So to get count of ratings for a particular rating you can use

establishments.stream().map(es -> es.getRating()).collect(Collectors.toList()).stream().filter(ratings -> ratings == 3).count()

If you want to get the ratings count/percentage for all ratings then please use the code in @Marek's answer

like image 3
Aarish Ramesh Avatar answered Oct 16 '22 15:10

Aarish Ramesh