简体   繁体   中英

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)

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

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())));

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

To get TreeMap<Integer, Double> ratings you can use stream api with groupingBy and summingDouble as a downstream:

TreeMap<Integer, Double> ratings = establishments.stream()
        .collect(Collectors.groupingBy(Establishment::getRating, TreeMap::new,
                Collectors.summingDouble(v -> ((1.0 / establishments.size())* 100))));

And to count the number of times:

long count = establishments.stream()
        .filter(e -> e.getRating() == 3)
        .count();

In loop you can sum values:

for (Establishment e : establishments) {
    // Get old value OR set to "0"
    Double rating = ratings.getOrDefault(e.getRating(), 0);
    // increase rating (counter)
    ratings.put(e.getRating(), rating + 1);
}

And calculate average at the end:

// Calculate part of equation which is not changing in loop
int size = establishments.size() * 100;

for (Map.Entry<Integer, Double> entry : ratings.entrySet()) {
    // Calculate average
    Double average = entry.getValue() / size;
    // Add it to the map
    ratings.put(entry.getKey(), average);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM