0

I am kind of new to Java and wanted to ask how I can find the most occurring String within an ArrayList.

I have two classes. One holds game activities and the winner for those games. The other class is supposed to be the Event that holds all the games.

For the event class, I have to find the person with the most game activities won. I made an ArrayList in the events class that holds all the winners from the games. Now I need to find the name that occurs most often in the ArrayList and output the String.

private ArrayList<Game> games;

public String getEventWinner(){
ArrayList<String> winner;
winner = new ArrayList<String>();

for(Game game : games)
{
winner.add(game.getWinner());
}
return eventWinner;  
}

That gets me all the winners from the games in an ArrayList, but now I do not know how to proceed and couldn't find any answer online. Could somebody lead me in the right direction?

3
  • 1
    you can use hashmap String is key and counter is integer. Commented Mar 22, 2021 at 15:10
  • Please check this stackoverflow.com/questions/505928/… (hint - second answer is what you are probably looking for) Commented Mar 22, 2021 at 15:22
  • It's generally preferred to use the List interface type instead of the ArrayList type, which is an implementation of List. Commented Mar 22, 2021 at 15:50

2 Answers 2

3

Calculate the frequencies of the strings into a map, find the entry with max value, and return it.

With Stream API this could look like this:

public static String getEventWinner(List<Game> games) {
    return games.stream()
        .map(Game::getWinner) // Stream<String>
        .collect(Collectors.groupingBy(
            winner -> winner, LinkedHashMap::new,
            Collectors.summingInt(x -> 1)
        )) // build the frequency map
        .entrySet().stream()
        .max(Map.Entry.comparingByValue()) // Optional<Map.Entry<String, Integer>>
        .map(Map.Entry::getKey) // the key - winner name
        .orElse(null);
}

Here LinkedHashMap is used as a tie breaker, however, it allows to select as a winner the person who appeared earlier in the game list.

Test:

System.out.println(getEventWinner(Arrays.asList(
        new Game("John"), new Game("Zach"), new Game("Zach"),
        new Game("Chad"), new Game("John"), new Game("Jack")
)));
// output John

If it is needed to define the winner the person who achieved the maximum number of wins earlier, another loop-based solution should be used:

public static String getEventWinnerFirst(List<Game> games) {
    Map<String, Integer> winMap = new HashMap<>();
    String resultWinner = null;
    int max = -1;
    for (Game game : games) {
        String winner = game.getWinner();
        int tempSum = winMap.merge(winner, 1, Integer::sum);
        if (max < tempSum) {
            resultWinner = winner;
            max = tempSum;
        }
    }
    return resultWinner;
}

For the same input data, the output will be Zach because he occurred twice in the list earlier than John.


Update 2

It may be possible to find the earliest achiever of the max result using Stream but a temporary map needs to be created to store the number of wins:

public static String getEventWinner(List<String> games) {
    Map<String, Integer> tmp = new HashMap<>();
    return games.stream()
        .map(Game::getWinner) // Stream<String>
        .map(winner -> Map.entry(winner, tmp.merge(winner, 1, Integer::sum))) // store sum in tmp map
        .collect(Collectors.maxBy(Map.Entry.comparingByValue()))
        .map(Map.Entry::getKey) // the key - winner name
        .orElse(null);
}
Sign up to request clarification or add additional context in comments.

5 Comments

Looks like it should be working. I do not see what is the added value of using a map factory in the groupingBy collector (the defaults, which happens to be HashMap, not linked would work fine I guess - but even not knowing the actual default, I see no need of the LinkedHashMap specifically ?). Then, Collectors.summingInt(x->1) like a more complex variant, in this case, of achieving Collectors.counting().
LinkedHashMap ensures insertion order and therefore the first winner from the input list who reached the max value will be selected. As for Collectors.summingInt, I prefer it to counting because long is redundant to count the entries of a list.
I don't exactly understand what's going on but it's working well. Thank you!
@AlexRudenko : that's a nice explanation - using the linked nature as a tie breaker. Maybe you could add a comment on that in your code sample ?
@GPI, explanation was nice but not quite correct, thanks for drawing attention to it, I updated the answer.
0

Stream APIs are great but can be a bit cryptic for the uninitiated. I find that non streaming code is often more readable.

Here is a solution in that spirit:

  List<String> winners = Arrays.asList("a","b","c","c","b","b");
  Map<String,Integer> entries = new HashMap<>();
  String mostFrequent=null;
  int mostFrequentCount=0;
  for (String winner : winners) {
      Integer count = entries.get(winner);
      if (count == null) count = 0;
      entries.put(winner, count+1);
      if (count>=mostFrequentCount)
      {
          mostFrequentCount=count;
          mostFrequent=winner;
      }
  }
  System.out.println("Most frequent = " + mostFrequent + " # of wins =  " + mostFrequentCount );
  

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.