【问题标题】:Find max possible Total from a list of player scores across their positions从他们位置的球员得分列表中找到最大可能的总分
【发布时间】:2016-05-28 01:43:26
【问题描述】:

在给定球员名单的情况下,我如何找到可能的最高球队得分?

我维护着一个社交幻想联盟(澳大利亚规则)网站,在该网站上,您在赛季开始时挑选 18 名球员,然后您每周选择一支由 6 名球员组成的球队,每个位置分配一名球员。您本周的得分是您选择的每个玩家得分的总和。例如。如果每个球员都得 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。 IE。通过将我在红色框中突出显示的 6 个分数加起来,你得到 288。看看即使乔什·肯尼迪在“关闭”位置上得到 57 分,你还是选择他在“前锋”位置上会更好因为下一个最佳“FW”球员得分 19(托比格林)之间的差值为 23。请记住,您必须在回合开始前将一名球员分配到 1 个位置,因此您只能使用每个球员的 1 个分数。

有什么建议吗?我将如何编写一个循环/查询来提取球员列表和他们的分数,从而构成可能的最佳球队分数 288

只是为了获得更多信息,playerList 是这样构建的,稍后我会在一些获取统计数据(踢球、手球等)的 Web 服务调用之后添加实际分数。

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
    }));
}

【问题讨论】:

  • 所以每个玩家每周可以填补1个位置,您希望将他们分配到他们得分最高的位置。蛮力方法可能是最简单的(因为玩家的数量是有限的,您可以保存最好的分数,这样您每周只计算一次)。我会首先找到绝对得分最高的球员(在任何位置和球员)。然后是得分第二高的球员(不包括上一个位置),以此类推。只要您不断排除之前的球员和位置,并在剩余位置中不断找到最高的位置,我认为它会起作用。
  • 添加了更多信息,包括高分示例。制定“可能的最佳球队”并不总是意味着您希望每个球员在每个位置上的得分都最高,因为有时一个位置的第一名和第二名之间的差异意味着您最好使用该球员另一个职位。
  • @DylanS 不幸的是,贪婪地选择得分最高的球员并不总是给出最佳解决方案:在只有 2 名球员和 2 个位置的模型中,假设球员 1 得分 (99, 100) 和球员 2 得分(0, 99)。贪婪地为位置 2 选择玩家 1 得分仅为 100,而另一个选择得分为 198,即使它排除了绝对最高得分(100)。

标签: c# linq


【解决方案1】:

我不知道如何挑选球队的规则,但大概六个角色中的每一个都必须由一名球员担任?

如果您没有太多玩家,蛮力方法可能会奏效(例如,在上表中,它可能会奏效。) 然后,如果您有n 玩家,则第一个有n 选择,第二个有n-1 选择(因为您不能在两个不同的位置上拥有同一个玩家)等等。这给出了总共@ 987654325@ (falling factorial) 可能性。这是相当大的,大约为n⁶。 如果你想实现这一点,你可以快速而肮脏地实现一个六深循环(确保排除已经选择的玩家),检查分数,并跟踪最高的分数。

一种减少检查可能性的方法,我认为这是合理的,是:从 X 位置的前 6 名得分手中选择你的球员。直觉是这样的:如果我为我的其他位置选择了(最佳或不是)5 名球员,我不可能为位置 X 选择所有 6 名最佳得分手!所以至少其中一个仍然可用。那么我不能比选择仍然剩下的最好的更好了。因此,我当然可以将没有进入前 6 名的任何人排除在该位置之外。当出现并列时可能会出现问题,在这种情况下,为了安全起见,保留任何并列前 6 位的人。

这样(假设没有平局),您最多只需要搜索6⁶ 的可能性(如果相同的玩家在不同类别中获得前 6 名,则更少)。即使对于大型列表,前 6 名的初始搜索也很容易处理。

任何或所有这些都可以使用 LINQ 完成,但不是必须的。

【讨论】:

  • 干杯,在朋友的帮助下,我设法使用蛮力方法实现了这一目标,您建议的排名是减少排列的关键。
【解决方案2】:

所以这就是它的结局.. 可能会帮助任何与从一组数字中选择最高总数的逻辑作斗争的人。到目前为止一切顺利,我还没有看到它不返回正确结果的实例。请随意建议代码清理/优化。

    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();
    }
}

【讨论】:

    猜你喜欢
    • 2019-10-28
    • 2020-12-08
    • 2011-08-02
    • 1970-01-01
    • 2022-01-20
    • 2023-03-20
    • 1970-01-01
    • 2020-02-11
    • 2021-04-06
    相关资源
    最近更新 更多