从他们位置的玩家得分列表中查找最大可能总数

时间:2016-05-28 01:43:27

标签: c# linq

如果有一个球员名单,我如何找到最大可能的球队得分?

我维持一个社交幻想联盟(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; }

但是,确定团队可能取得的最大可能得分比我想象的要困难得多。在那里,可以跨职位共享高分。此外 - 你可能选择的“最好的球队”可能并不意味着球员的得分最高。

Player scores in red boxes are the scores I think produce the highest possible score

我为这个例子计算的最佳分数(即你可以在每个位置挑选的最佳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
    }));
}

2 个答案:

答案 0 :(得分:1)

我不知道如何挑选球队的规则,但可能只有一名球员必须填补这六个角色中的每一个?

如果你没有太多的玩家(例如在上面的桌子上它可能会很好用),那么蛮力方法可行。 然后,如果您有n个玩家,则第一个选择n,第二个选择n-1(因为您不能在两个不同的位置拥有相同的玩家),这总共提供nP6falling 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();
    }
}