简体   繁体   中英

Indexing when using a hashmap with streams

I have a HashMap of teams that I need to print out. They need to be indexed and sorted by some parameters(not relevant). Everything works fine except for the indexing part which I got working in a way but it doesn't feel right.

int i=0;

public void printTable()
{
    StringBuilder sb = new StringBuilder();

    i = 0;

    table.keySet().stream()
                .map(key -> table.get(key))
                .sorted(Comparator.comparing(Team::getPoints).thenComparing(Team::goalDifference).reversed().thenComparing(Team::getName))
                .forEach(team -> sb.append(String.format("%2d%s %-15s%5d%5d%5d%5d%5d\n", ++i, ".", team.getName(), team.getPlayed(),
                         team.getWins(), team.getDraws(), team.getLosses(), team.getPoints())));

    System.out.print(sb.toString());
}

Is there a better way of doing this? I don't think Intstream would help here because I wouldn't be able to get the object from the HashMap using an index.

Updated Answer

You should avoid stateful streams / lambda expressions. reference . Due to this reason, my original answer below is a bad idea.

A better approach is to separate stream and printing.

List<Team> teams = table.values()
        .stream()
        .sorted(...)
        .collect(Collectors.toList());

StringBuilder sb = new StringBuilder();
for(int i=0; i<teams.size(); i++) {

    Team team = teams.get(i);
    sb.append(String.format("%2d. %-15s%5d%5d%5d%5d%5d\n", 
            i,
            team.getName(),
            team.getPlayed(),
            team.getWins(),
            team.getDraws(),
            team.getLosses(), 
            team.getPoints()));
}

System.out.println(sb.toString());

Original Answer [Bad Idea]

You cannot modify the reference of the variables/modify primitives that you are used within a stream or lambda. You can rewrite your code like below,

AtomicInteger i = new AtomicInteger();
String result = table.values()
        .stream()
        .sorted(...)
        .map(e -> i.incrementAndGet() + "_" + e)
        .collect(Collectors.joining());

AtomicInteger will give you mutability without changing the reference/modifying the primitive. You don't need to do look-ups in the map, you can directly iterate over values.

Edit

As @Eugene pointed out in the comment, the approach with AtomicInteger cannot be used if you are using parallel stream.

I would separate the sorting from the printing.

Sorting:

List<Team> sorted = table.values().stream()
    .sorted(Comparator.comparing(Team::getPoints)
            .thenComparing(Team::goalDifference)
            .reversed()
            .thenComparing(Team::getName))
    .collect(Collectors.toList());

Printing:

String table = IntStream.range(0, sorted.size())
    .mapToObj(i -> String.format("%2d%s %-15s%5d%5d%5d%5d%5d", 
            i, ".", 
            sorted.get(i).getName(), 
            sorted.get(i).getPlayed(),
            sorted.get(i).getWins(), 
            sorted.get(i).getDraws(), 
            sorted.get(i).getLosses(), 
            sorted.get(i).getPoints()))
    .collect(Collectors.joining("\n"));

This could be improved if the Team class had a method that accepts the position and returns the formatted string:

public String formatWithPosition(int position) {
    return String.format("%2d%s %-15s%5d%5d%5d%5d%5d", 
            position, ".", 
            name, played, wins, draws, losses, points);
}

Then, you could use it as follows:

String table = IntStream.range(0, sorted.size())
    .mapToObj(Team::formatWithPosition)
    .collect(Collectors.joining("\n"));

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