Search code examples
javajava-streamcollectorsone-liner

Mapping the string representation of each line, given a list of lists of lines, to its total number of occurrences


Let me first describe more precisely the assignment. The following class and inner class are given:

public class Title {

    private List<Line> lines;

    public Title() {
        this(new ArrayList<>());
    }

    public Title(List<Line> lines) {
        this.lines = lines;
    }

    public void add(Line l) {
        lines.add(l);
    }

    @Override
    public String toString() {
        return lines.toString();
    }

    public List<Line> getLines() {
        return new ArrayList<>(lines);
    }
public  class Line {

    private String line;

    public Line(String line) {
        this.line = line;
    }

    @Override
    public String toString() {
        return line;
    }
}

Suppose we have a List<Title> titles. The assignment is to obtain a map from the string representation of each line, to its total number of occurrences in titles. As a hint, it is stated that flatMap is needed and that there is a solution with Collectors.toMap and one with Collectors.groupingBy.

I came up with the former, but I strongly fear it is needlessly convoluted. How could I improve it? Here it is:

import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.flatMapping;
import static java.util.Collections.frequency;

Map<String, Integer> countsStringMap1 = titles.stream()
                                              .flatMap(t-> t.getLines().stream().map(Line::toString))
                                              .distinct()
                                              .collect(toMap(Function.identity(),
                                                          s -> frequency(titles.stream()
                                                                               .flatMap(t-> t.getLines()
                                                                                             .stream()
                                                                                             .map(Line::toString))
                                                                                             .collect(toList()), s)));

I couldn't reach any solution with groupingBy, I tried a combination with Collectors.mapping but to no avail.

How can it be done?


Solution

  • You can use counting to count the number of occurrences as you groupBy lines by their string representation.

    Map<String, Long> countsStringMap = titles.stream()
            .flatMap(t -> t.getLines().stream().map(Title.Line::toString))
            .collect(Collectors.groupingBy(Function.identity(),
                    Collectors.counting()));
    

    Notice, that the distinct operation is removed in the solution intentionally since that would leave you with all distinct lines otherwise before you group them and end up counting each of them once only.