3

I am writing a simple method to print out statistics of a series of outcomes of games. Every Game has a list of Outcomes in it, which contain enums according to the outcome of a game. My instructor has commented a TODO in my code:

public static void printStatistics(List<Game> games) {
    float win = 0;
    float lose = 0;
    float draw = 0;
    float all = 0;

    //TODO: should be implemented /w stream API
    for (Game g : games) {
        for (Outcome o : g.getOutcomes()) {
            if (o.equals(Outcome.WIN)) {
                win++;
                all++;
            } else if (o.equals(Outcome.LOSE)) {
                lose++;
                all++;
            } else {
                draw++;
                all++;
            }
        }
    }
    DecimalFormat statFormat = new DecimalFormat("##.##");

    System.out.println("Statistics: The team won: " + statFormat.format(win * 100 / all) + " %, lost " + statFormat.format(lose * 100 / all)
            + " %, draw: " + statFormat.format(draw * 100 / all) + " %");

}

I am familiar with lambda expressions. I tried looking online for solutions, but could not find examples of a stream accessing fields of a field of a class. I would be happy if you could give me a solution, or provide me with a relevant tutorial. Thanks.

5
  • Why use stream ? Won't be more efficient as you need to compute multiple values not directly related to object you manipulate Commented Mar 4, 2019 at 21:51
  • That was my first question too. There is no way i can get all 3 values that i need with just 1 pipline, right? Commented Mar 4, 2019 at 21:54
  • Why are you using floats for discrete values? Commented Mar 4, 2019 at 21:54
  • If it's just for the floating-point math, you can make the literals floats instead. E.g. win * 100f / all Commented Mar 4, 2019 at 21:57
  • Thanks for pointing that out! Commented Mar 4, 2019 at 22:03

3 Answers 3

5

You can stream games, flatMap to outcomes, then collect them to a map of counts:

Map<Outcome, Long> counts = games.stream()
        .map(Game::getOutcomes)
        .flatMap(Collection::stream)
        .collecting(Collectors.groupingBy(o -> o, Collectors.counting()));

long win = counts.getOrDefault(Outcome.WIN, 0L);
long lose = counts.getOrDefault(Outcome.LOSE, 0L);
long draw = counts.getOrDefault(Outcome.DRAW, 0L);
long all = games.stream()
        .mapToInt(g -> g.getOutcomes().size())
        .sum();
Sign up to request clarification or add additional context in comments.

Comments

2

groupingBy fits this case:

Map<Outcome, Long> map = games.stream()
        .flatMap(game -> game.getOutcomes().stream())
        .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

And get win, lose count:

long win = map.get(Outcomes.WIN);
long lose = map.get(Outcomes.LOSE);
...

To get all count you need to sum all values from map

long all = map.values().stream()
    .mapToLong(Long::valueOf)
    .sum();

Comments

0

To access the fields of the class in a Stream, use map:

games.stream().map(game -> 
  game.getOutcomes().stream().map(outcome -> {
    // do something with each `outcome`
  })
)

The above code assumes that getOutcomes() returns a List, which is not specified in the current version of OP's question.

Note that you cannot simply increment counters within a Stream, as you might hope to, because all variables used within Streams must be final or effectively final. You'll have to dig a bit more into the Stream API to figure out how to increment the way you did in your original solution.

Hint: you want to do something like this, which utilises Collectors.groupingBy().

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.