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)
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.
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).
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.
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())));
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With