如果有一个球员名单,我如何找到最大可能的球队得分?
我维持一个社交幻想联盟(Aussie Rules)网站,你可以在赛季开始时挑选18名球员,然后每周选择一个由6名球员组成的球队,每名球员分配1名球员。您一周的得分是您选择的每个球员得分的总和。例如。如果每位球员得分为50,那么本周的球队得分将为300。
为了在本轮结束后分析您的团队,我想展示一些指标。第一个是每个职位的最高分,第二个是本周的团队。
我可以找出每个位置中得分最高的分数(“球员得分列表”截图中的绿色突出显示)。
foreach (var player in playerList.Where(p => p.Forward == playerList.Max(x => x.Forward) && p.Forward > 0)) { player.ForwardTopScore = true; }
foreach (var player in playerList.Where(p => p.TallForward == playerList.Max(x => x.TallForward) && p.TallForward > 0)) { player.TallForwardTopScore = true; }
foreach (var player in playerList.Where(p => p.Offensive == playerList.Max(x => x.Offensive) && p.Offensive > 0)) { player.OffensiveTopScore = true; }
foreach (var player in playerList.Where(p => p.Defensive == playerList.Max(x => x.Defensive) && p.Defensive> 0)) { player.DefensiveTopScore = true; }
foreach (var player in playerList.Where(p => p.OnBaller == playerList.Max(x => x.OnBaller) && p.OnBaller > 0)) { player.OnBallerTopScore = true; }
foreach (var player in playerList.Where(p => p.Ruck == playerList.Max(x => x.Ruck) && p.Ruck > 0)) { player.RuckTopScore = true; }
但是,确定团队可能取得的最大可能得分比我想象的要困难得多。在那里,可以跨职位共享高分。此外 - 你可能选择的“最好的球队”可能并不意味着球员的得分最高。
我为这个例子计算的最佳分数(即你可以在每个位置挑选的最佳6名球员) 288 。即通过将你在红色方框中突出显示的6个分数加起来你得到288.看看即使Josh Kennedy在“关闭”位置得到57分,你最好选择他在“FW”位置因为下一个最佳“FW”球员得分19(托比·格林)之间的差异为23. 请记住,你必须在比赛开始前将球员分配到1个位置,这样你每个球员只能得1分。
有什么建议吗?我如何编写一个循环/查询来提取玩家列表及其得分,以构成 288 的最佳团队得分?
仅仅是为了获得更多信息,playerList是这样构建的,我稍后在一些获得统计数据的网络服务调用后添加实际分数(Kicks,Handballs等)。
List<PlayerScore> playerList = new List<PlayerScore>();
foreach (var t in teams)
{
playerList.AddRange(t.TeamSelections.Where(x => x.DateInactive == null).OrderBy(x => x.PlayerName).Select(p => new PlayerScore
{
TeamId = p.TeamID,
PlayerName = p.PlayerName,
Club = p.Club,
ClubAbbreviation = Helper.Stats.GetClubAbbreviation(p.Club),
TeamLeagueId = p.Team.LeagueId,
TeamSelection = p.TeamSelectionId
}));
}
答案 0 :(得分:1)
我不知道如何挑选球队的规则,但可能只有一名球员必须填补这六个角色中的每一个?
如果你没有太多的玩家(例如在上面的桌子上它可能会很好用),那么蛮力方法可行。 然后,如果您有n
个玩家,则第一个选择n
,第二个选择n-1
(因为您不能在两个不同的位置拥有相同的玩家),这总共提供nP6
(falling factorial)种可能性。这非常大,大约为n⁶
。
如果你想实现这一点,你可以快速而又脏,实现一个六深的循环(确保排除已经选择的玩家),检查分数,并跟踪最高分。(/ p) >
减少检查可能性的一种方法,我认为这是合理的:在位置X中仅从该位置的前6位得分者中选择你的球员。直觉就是这样:如果我为其他职位选择(最佳或不是)5名球员,我就无法选择位置X的所有六名最佳得分手!所以至少其中一个仍然可用。然后,我做得比选择仍然离开的最好的人做得更好。所以我当然可以排除那些没有进入前6名的人。在有联系的情况下可能会出现问题,在这种情况下,为了安全起见,请保留任何前6名职位的人。
这种方式(假设没有联系),你最多只能搜索6⁶
种可能性(如果相同的玩家在不同的类别中获得前6名,则更少)。而对于前6名的初步搜索,即使对于大量名单也是易于处理的。
任何或所有这些都可以通过LINQ完成,但不一定非必要。
答案 1 :(得分:0)
所以在这里结束了......可能会帮助任何人在一组数字中挑选最高总数的逻辑。到目前为止一直很好,我还没有看到它没有返回正确结果的实例。请随意建议代码清理/优化。
private static IEnumerable<IEnumerable<T>> GetPermutations<T>(IEnumerable<T> list, int length)
{
return length == 1 ? list.Select(t => new[] {t}) : GetPermutations(list, length - 1).SelectMany(t => list.Where(o => !t.Contains(o)), (t1, t2) => t1.Concat(new[] {t2}));
}
public static List<PlayerScore> TeamOfTheWeek(List<PlayerScore> playerList)
{
// Remove the players who scored 0 accross the board.
playerList.RemoveAll(player => player.Forward + player.TallForward + player.Offensive + player.Defensive + player.OnBaller + player.Ruck == 0);
// Rank each player score within a position.
var forwardRank = playerList.RankByDescending(p => p.Forward, (p, r) => new {Rank = r, Player = p});
var tallForwardRank = playerList.RankByDescending(p => p.TallForward, (p, r) => new {Rank = r, Player = p});
var offensiveRank = playerList.RankByDescending(p => p.Offensive, (p, r) => new { Rank = r, Player = p });
var defensiveRank = playerList.RankByDescending(p => p.Defensive, (p, r) => new { Rank = r, Player = p });
var onBallerRank = playerList.RankByDescending(p => p.Defensive, (p, r) => new { Rank = r, Player = p });
var ruckRank = playerList.RankByDescending(p => p.Ruck, (p, r) => new { Rank = r, Player = p });
for (int i = playerList.Count - 1; i >= 0; i--)
{
//var rankName = forwardRank.First(x => x.Player.PlayerName == playerList[i].PlayerName).Player.PlayerName;
var fw = forwardRank.First(x => x.Player.PlayerName == playerList[i].PlayerName).Rank;
var tf = tallForwardRank.First(x => x.Player.PlayerName == playerList[i].PlayerName).Rank;
var off = offensiveRank.First(x => x.Player.PlayerName == playerList[i].PlayerName).Rank;
var def = defensiveRank.First(x => x.Player.PlayerName == playerList[i].PlayerName).Rank;
var ob = onBallerRank.First(x => x.Player.PlayerName == playerList[i].PlayerName).Rank;
var ruck = ruckRank.First(x => x.Player.PlayerName == playerList[i].PlayerName).Rank;
if (fw >= 6 && tf >= 6 && off >= 6 && def >= 6 && ob >= 6 && ruck >= 6)
{
// Player is outside top 6 for each position so remove, and reduce permutations.
playerList.RemoveAt(i);
}
}
// Now update the playerId as this is used to join back to the array later.
var playerId = 0;
foreach (var p in playerList.OrderBy(p => p.PlayerName))
{
p.Id = playerId;
playerId = playerId + 1;
}
// Create and fill the position scores.
List<int[]> positionScoreArray = new List<int[]>();
foreach (var player in playerList.OrderBy(p => p.PlayerName))
{
// Player scored more than 0 so add to the positionScoreArray.
int[] playerScores = { player.Forward, player.TallForward, player.Offensive, player.Defensive, player.OnBaller, player.Ruck };
positionScoreArray.Add(playerScores);
}
// Players remaining in list pulled into array, ready for processing.
string[] playerNameArray = playerList.OrderBy(x => x.PlayerName).Select(p => p.PlayerName).ToArray();
// Load up the actual position scores to use in Parallel.For processing.
for (int i = 0; i < playerNameArray.Length; i++)
{
for (int j = 0; j < positionScoreArray.Count; j++)
{
if (j == 0)
{
var player = playerList.FirstOrDefault(p => p.PlayerName == playerNameArray[i]);
if (player != null)
positionScoreArray[i][j] = player.Forward;
}
if (j == 1)
{
var player = playerList.FirstOrDefault(p => p.PlayerName == playerNameArray[i]);
if (player != null)
positionScoreArray[i][j] = player.TallForward;
}
if (j == 2)
{
var player = playerList.FirstOrDefault(p => p.PlayerName == playerNameArray[i]);
if (player != null)
positionScoreArray[i][j] = player.Offensive;
}
if (j == 3)
{
var player = playerList.FirstOrDefault(p => p.PlayerName == playerNameArray[i]);
if (player != null)
positionScoreArray[i][j] = player.Defensive;
}
if (j == 4)
{
var player = playerList.FirstOrDefault(p => p.PlayerName == playerNameArray[i]);
if (player != null)
positionScoreArray[i][j] = player.OnBaller;
}
if (j == 5)
{
var player = playerList.FirstOrDefault(p => p.PlayerName == playerNameArray[i]);
if (player != null)
positionScoreArray[i][j] = player.Ruck;
}
}
}
Stopwatch parallelForEachStopWatch = new Stopwatch();
parallelForEachStopWatch.Start();
var count = 0;
var playerIds = Enumerable.Range(0, playerNameArray.Length).ToList();
var best = new { PlayerIds = new List<int>(), TeamScore = 0 };
var positions = new[] { "FW", "TF", "Off", "Def", "OB", "Ruck" };
// Thread safe the Parallel.ForEach
lock (ThreadSafeObject)
{
Parallel.ForEach(GetPermutations(playerIds, positions.Length), perm =>
{
var teamScore = 0;
var players = perm.ToList();
for (int i = 0; i < positions.Length; i++) teamScore += positionScoreArray[players[i]][i];
if (teamScore > best.TeamScore) best = new {PlayerIds = players, TeamScore = teamScore};
if (count++%100000 == 0) Debug.WriteLine($"{count - 1:n0}");
}
);
}
parallelForEachStopWatch.Stop();
TimeSpan parallelForEach = parallelForEachStopWatch.Elapsed;
Debug.WriteLine($"Parallel.ForEach (secs): {parallelForEach.Seconds}");
Debug.WriteLine($"Permutations: {count:n0}");
Debug.WriteLine($"Team Score: {best.TeamScore}");
// Track Parallel.ForEach result.
var tcTotwRequest = new TelemetryClient();
tcTotwRequest.TrackEvent($"Permutations: {count:n0} Score: {best.TeamScore} Time (sec): {parallelForEach.Seconds}");
lock (ThreadSafeObject)
{
if (best.PlayerIds.Count > 0)
{
for (int i = 0; i < positions.Length; i++)
{
// Update the playerList, marking best players with TeamOfTheWeek position.
var player = playerList.FirstOrDefault(p => p.Id == best.PlayerIds[i]);
if (player != null)
{
player.TeamOfTheWeekPosition = positions[i];
player.TeamOfTheWeekScore = best.TeamScore;
}
}
}
}
return playerList.OrderBy(p => p.PlayerName).ToList();
}
}