说我有以下简单的数据结构。
items=${','.join(data)}
到目前为止一切顺利。现在的问题是,我如何获得球员排名? 我有另一种排名数据结构,即 -
public class Player {
private Long id;
private String name;
private Integer scores;
//getter setter
}
所以我有一个播放器列表,我想计算一个排名列表,我想使用java8 stream api。
我有一个名为public class Ranking {
private Integer score;
private Integer rank;
//getter/setter
}
的服务,如下所示
PlayerService
计算很简单,得分最高的人排名最高。
如果我有public class PlayerService {
@Autowired
private PlayerRepository playerRepository;
public List<Ranking> findAllRanking(Long limit) {
List<Player> players = playerRepository.findAll();
// calculation
return null;
}
分,那么排名将是
5,7,8,9,3
任何帮助将不胜感激。
答案 0 :(得分:5)
试试这个:
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
变量score
,no
和rank
是.map()
中lambda函数中的自由变量。所以不能重新分配它们。如果他们的类型是int
而不是int[]
,则无法编译代码。
答案 1 :(得分:3)
接受答案的问题是,一旦你并行转换流(players.parallelStream()
),你会得到意想不到的结果,因为你从单例数组中读取/更新值的竞争条件
也许你可以将你的任务分解成多个步骤。首先将分数列表按相反顺序排序,然后生成索引流。从那里,您将每个索引映射到其对应的排名。
如果您需要为同一分数获得相同的排名,则需要在mapToObj
语句中测试多个条件。它使代码不是很漂亮,但你总是可以在辅助方法中提取它。
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());
那就是说,如果你不打算并行化这一步,我会选择好的旧的for循环。
如果您不需要为相同的分数获得相同的排名,则可以查看此帖子Zipping streams using JDK8 with lambda (java.util.stream.Streams.zip),例如proton-pack:
List<Ranking> rankings = StreamUtils.zip(IntStream.rangeClosed(1, players.size()).boxed(),
players.stream().map(Player::getScores).sorted(reverseOrder()),
Ranking::new)
.collect(toList());
答案 2 :(得分:0)
我相信你希望玩家拥有一个属性,一个排名,对吗?为什么不在播放器的类中添加成员变量(或实例变量)。我相信我们在面向对象编程中称之为“聚合”或“对象组合”。 https://en.wikipedia.org/wiki/Object_composition
考虑一下......
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()
}
}
答案 3 :(得分:0)
为什么不根据他们的分数对付款人进行排序? 所以..沿着这些方向的东西:
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;
}
我将此作为练习留给您确定是否产生您想要的订单或完全相反。
答案 4 :(得分:0)
如果您首先重新考虑先决条件,您将获得更好的解决方案。作为旁注,我不明白,为什么在整个代码中使用Integer
。如果您确实null
rank
或scores
的可能值,则解决方案将变得更加复杂。由于我怀疑这是你想要的,我建议改为使用int
。
最大的障碍是您需要生成这个相当多余的Ranking
个实例。所有你需要的,是分数一次排序,他们的排名由他们的位置隐含:
List<Integer> rankAndScore = players.stream().map(Player::getScores)
.sorted(Comparator.reverseOrder()).collect(toList());
此结果列表包含降序排名,排名由位置隐含,第一项具有第一名,第二项具有第二名,依此类推。无需明确存储排名数。唯一要注意的是集合索引从零开始,而排名从一开始。
在问题中打印值的一种方法是:
System.out.println("rank\tscore");
IntStream.range(0, rankAndScore.size())
.forEachOrdered(r->System.out.println(r+1+"\t"+rankAndScore.get(r)));
或者,您可以简单地使用int[]
数组来表示排名和分数:
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]));
IntStream
不支持按降序排序,但如图所示,可以通过按升序排序并调整数组处理来简单地修复它。