Reputation: 2300
Say I have following simple data structure.
public class Player {
private Long id;
private String name;
private Integer scores;
//getter setter
}
So far so good. Now question is , how do I get what's a players rank? I have another data structure for ranking, that is-
public class Ranking {
private Integer score;
private Integer rank;
//getter/setter
}
So I have a list of player and i want to compute a list of ranking and I would like to use java8 stream api.
I have a service named PlayerService
as following
public class PlayerService {
@Autowired
private PlayerRepository playerRepository;
public List<Ranking> findAllRanking(Long limit) {
List<Player> players = playerRepository.findAll();
// calculation
return null;
}
The calculation is simple, whoever has most score has most ranking.
If I have 5,7,8,9,3
scores then ranking would be
rank score
1 9
2 8
3 7
4 5
5 3
Any help would be appreciated.
Upvotes: 2
Views: 11483
Reputation: 298349
You will get a better solution, if you rethink your prerequisites first. As a side note, I don’t understand, why you are using Integer
throughout your code. If you really consider null
a possible value for rank
or scores
, the solution will become much more complicated. Since I doubt that this is what you want, I recommend using int
instead.
The biggest obstacle is your requirement of producing this rather superfluous Ranking
instances. All you need, are the scores as once sorted, their rank is implied by their positions:
List<Integer> rankAndScore = players.stream().map(Player::getScores)
.sorted(Comparator.reverseOrder()).collect(toList());
this resulting list contains the scores in descending order and the rank is implied by the position, the first item has the first rank, the second item the second, and so on. There is no need to explicitly store the rank numbers. The only thing to care is that the collection indices start with zero whereas the ranks start with one.
One way to print the values as in your question is:
System.out.println("rank\tscore");
IntStream.range(0, rankAndScore.size())
.forEachOrdered(r->System.out.println(r+1+"\t"+rankAndScore.get(r)));
Alternatively, you could simply use an int[]
array to represent rank and score:
int[] rankAndScore = players.stream().mapToInt(Player::getScores).sorted().toArray();
System.out.println("rank\tscore");
IntStream.rangeClosed(1, rankAndScore.length)
.forEachOrdered(r->System.out.println(r+"\t"+rankAndScore[rankAndScore.length-r]));
The IntStream
doesn’t support sorting in descending order but as shown, it can be simply fixed by sorting to ascending order and adapting the processing of the array.
Upvotes: 1
Reputation: 93862
The problem with the accepted answer is that once you turn the stream in parallel (players.parallelStream()
), you'll get unexpected results, because of race conditions when you read/update the value from the singletons arrays.
Maybe you could decompose your task into multiple steps. First sort the list of scores into reverse order, and then generate a stream of indices. From there you map each index to its corresponding rank.
You need to test multiple conditions into the mapToObj
statement if you need to have the same rank for a same score. It makes the code not very pretty but you can always extract this in a helper method.
List<Integer> scores = players.stream().map(Player::getScores).sorted(reverseOrder()).collect(toList());
List<Ranking> rankings =
IntStream.range(0, scores.size())
.mapToObj(i -> i == 0 ? new Ranking(1, scores.get(i)) :
scores.get(i - 1).equals(scores.get(i)) ? new Ranking(i, scores.get(i)) :
new Ranking(i + 1, scores.get(i)))
.collect(toList());
That said, if you don't plan to parallelize this step, I would go with the good old for-loop.
If you don't need to have the same rank for the same score, you can check this thread Zipping streams using JDK8 with lambda (java.util.stream.Streams.zip), for example with proton-pack:
List<Ranking> rankings = StreamUtils.zip(IntStream.rangeClosed(1, players.size()).boxed(),
players.stream().map(Player::getScores).sorted(reverseOrder()),
Ranking::new)
.collect(toList());
Upvotes: 3
Reputation:
Try this:
List<Player> players = new ArrayList<Player>() {{
add(new Player(1L, "a", 5));
add(new Player(2L, "b", 7));
add(new Player(3L, "c", 8));
add(new Player(4L, "d", 9));
add(new Player(5L, "e", 3));
add(new Player(6L, "f", 8));
}};
int[] score = {Integer.MIN_VALUE};
int[] no = {0};
int[] rank = {0};
List<Ranking> ranking = players.stream()
.sorted((a, b) -> b.getScores() - a.getScores())
.map(p -> {
++no[0];
if (score[0] != p.getScores()) rank[0] = no[0];
return new Ranking(rank[0], score[0] = p.getScores());
})
// .distinct() // if you want to remove duplicate rankings.
.collect(Collectors.toList());
System.out.println(ranking);
// result:
// rank=1, score=9
// rank=2, score=8
// rank=2, score=8
// rank=4, score=7
// rank=5, score=5
// rank=6, score=3
The variables score
, no
and rank
are free variables in the lambda function in .map()
. So they must not be reassigned. If their types are int
instead of int[]
, you cannot compile the code.
Upvotes: 6
Reputation: 938
Why not sort the payers according to their score? so.. something along those lines:
public class PlayerService {
@Autowired
private PlayerRepository playerRepository;
public List<Ranking> findAllRanking(Long limit) {
List<Player> players = playerRepository.findAll();
// --- sorting players according to their scores
Collections.sort(players, new Comparator<Player>(){
@Override
public int compare(Players thiz, Player that){
return new Integer(thiz.scores).compareTo(that.scores);
});
List<Ranking> rankingList = new ArrayList<>();
int i = 0;
for(Player p : players){
rankingList.add(new Ranking(p.score, ++i));
}
return rankingList;
}
I leave that as an exercise to you to determine if that produces your wanted order or the exact reverse.
Upvotes: 0
Reputation: 26
I believe you want the Player to have as an attribute, a Ranking, right? Why not add a member variable (or instance variable) in the player's class. I believe we call this "aggregation" or "Object composition" in object oriented programming. https://en.wikipedia.org/wiki/Object_composition
Consider this...
public class Player {
private Long id;
private String name;
private Integer scores;
private Ranking rank;
...
//You can add a constructor that include the Ranking, you can also add setter and getters to set the Ranking object for this player.
public int getRank()
{
return rank.getRank(); //assuming class Ranking has getRank() method
}
public void setRank(int rank)
{
rank.setRank(rank); //assuming class Ranking has setRank()
}
}
Upvotes: 0