【问题标题】:Efficient way to return combination of players返回组合的有效方法
【发布时间】:2013-01-06 20:39:46
【问题描述】:

我正在编写一个多人游戏,其中每个玩家必须与组中的每个玩家只玩一次。即,如果您有 3 名玩家:Joe、Mary 和 Peter,则这些组合将是:Joe & Mary、Joe & Peter 和 Mary & Peter。

计算轮数的代码非常简单。由于轮数等于n! /r! * (n - r)!其中 n 等于玩家人数,r 等于 2(因为游戏每轮有 2 名玩家进行)。

 public int factorial(int n)
 {
      if (n == 0)
          return 1;
      return n * factorial(n - 1);
 }

 public int calcNoOfRounds()
 {
      return factorial(noOfPlayers) / (factorial(2) * factorial(noOfPlayers -2));
 }

但是,我坚持想出一种有效的方法来返回实际的玩家组合。我尝试了以下代码。它可以工作,但是它太手动了,有些事情我想改进。在这段代码中,我将 p1 vs p2、p2 vs p3、p3 vs p4 ... p(n-1) vs p(n) 配对。然后我从第 3 名球员开始,将这些球员与上述所有球员进行匹配,除了他们之前的球员,即 p3 vs p1、p4 vs p1、p4 vs p2、p5 vs p1、p5 vs p2、p5 vs p3 等。 . 你觉得我能做得更好吗?

 public void calcPlayerCombinations()
 {
     List<string> playerNames = new List<string>();

     for (int i = 0; i < noOfPlayers; i++)
     {
          playerNames.Add(players[i].PlayerName);
     }

     for (int i = 0; i < noOfPlayers - 1; i++)
     {
          playerCombinations.Add(playerNames[i] + " " + playerNames[i + 1]);
     }

     for (int j = 3; j <= noOfPlayers; j++)
     {
          int counter = 1;

          do
          {
             playerCombinations.Add(playerNames[j -1] + " " + playerNames[counter -1]);
             counter++;

          } while (counter != (j - 1));
     }
 }

我不喜欢这样,因为如果游戏真的在玩,你希望同一个玩家连续玩 6 场比赛吗?我可以随机选择一个组合进行一轮是的,但我仍然想知道更好的方法以供将来参考。

感谢您的帮助!

【问题讨论】:

    标签: c# algorithm combinations


    【解决方案1】:

    您为什么不将每个玩家(作为配对中的“第一”)与比他们晚的每个玩家(作为“第二”)配对?例如:

    public static IEnumerable<string> PairPlayers(List<string> players)
    {
        for (int i = 0; i < players.Count - 1; i++)
        {
            for (int j = i + 1; j < players.Count; j++)
            {
                yield return players[i] + " " + players[j];
            }
        }
    }
    

    (如果您愿意,显然您也可以急切地这样做。)

    我可能误解了要求。

    【讨论】:

    • 感谢您的回答。那将与我刚刚所做的非常相似(尽管以更简单的方式完成)。并没有我当时想的那么糟糕。我倾向于比实际问题更努力地思考。请问产量是什么意思?你为什么要返回 IEnumerable
    • 我还有一个问题。我如何使用此算法,但同时没有同一玩家玩的连续回合?由于使用此算法,玩家 1 必须连续进行 5 轮比赛,而我不希望这样。我尝试使用不重复的随机数字生成一个列表,并从列表中随机选择一个玩家组合,但这几乎会导致无限循环。我该怎么做?
    • @Bernice - 请不要在 cmets 中提出新问题,它们很可能不会被阅读、回答,更重要的是,不会被正在寻找类似答案的其他人发现。
    • 你是对的 Erno。以后的问题会记住这一点:)我还是这个网站的新手。
    • @Bernice:获得完整列表后,您可以将其随机播放。至于yield return 是什么——这是一个迭代器块;在 MSDN 上搜索它,你会找到一个很好的教程。
    【解决方案2】:

    此示例说明如何将玩家列表用作队列。当一名球员上场后,他们会被排在后面,并且最不可能再次被选中。它还展示了如何做 Jon Skeet 所做但渴望的事情(没有 yield)。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace SOPlayersOrder
    {
        class Program
        {
            /// <summary>
            /// Represents a match up between two players.
            /// It is tempting to use strings for everything, but don't do it,
            /// you'll only end up having to split those strings and you will
            /// not benefit from type safety.
            /// </summary>
            public class MatchUp
            {
                public string Player1 { get; set; }
                public string Player2 { get; set; }
    
                public override string ToString()
                {
                    return string.Format("{0} vs {1}", Player1, Player2);
                }
            }
    
            public static IEnumerable<MatchUp> PairPlayers(List<string> players)
            {
                var results = new List<MatchUp>();
                for (int i = 0; i < players.Count - 1; i++)
                {
                    for (int j = i + 1; j < players.Count; j++)
                    {
                        var matchup = new MatchUp { Player1 = players[i], Player2 = players[j] };
                        //yield return matchup; //this is how Jon Skeet suggested, I am showing you "eager" evaluation
                        results.Add(matchup);
                    }
                }
                return results;
            }
    
            public static IEnumerable<MatchUp> OrganiseGames(IEnumerable<string> players, IEnumerable<MatchUp> games)
            {
                var results = new List<MatchUp>();
                //a list that we will treat as a queue - most recently played at the back of the queue
                var playerStack = new List<string>(players);
                //a list that we can modify
                var gamesList = new List<MatchUp>(games);
                while (gamesList.Count > 0)
                {
                    //find a game for the top player on the stack
                    var player1 = playerStack.First();
                    var player2 = playerStack.Skip(1).First();
                    //the players are in the order of least recently played first
                    MatchUp matchUp = FindFirstAvailableGame(playerStack, gamesList);
                    //drop the players that just played to the back of the list
                    playerStack.Remove(matchUp.Player1);
                    playerStack.Remove(matchUp.Player2);
                    playerStack.Add(matchUp.Player1);
                    playerStack.Add(matchUp.Player2);
                    //remove that pairing
                    gamesList.Remove(matchUp);
                    //yield return matchUp; //optional way of doing this
                    results.Add(matchUp);
                }
                return results;
            }
    
            private static MatchUp FindFirstAvailableGame(List<string> players, List<MatchUp> gamesList)
            {            
                for (int i = 0; i < players.Count - 1; i++)
                {
                    for (int j = i + 1; j < players.Count; j++)
                    {
                        var game = gamesList.FirstOrDefault(g => g.Player1 == players[i] && g.Player2 == players[j] ||
                                                                 g.Player2 == players[i] && g.Player1 == players[j]);
                        if (game != null) return game;
                    }
                }
                throw new Exception("Didn't find a game");
            }
    
            static void Main(string[] args)
            {
                var players = new List<string>(new []{"A","B","C","D","E"});
                var allGames = new List<MatchUp>(PairPlayers(players));
    
                Console.WriteLine("Unorganised");
    
                foreach (var game in allGames)
                {
                    Console.WriteLine(game);
                }
    
                Console.WriteLine("Organised");
    
                foreach (var game in OrganiseGames(players, allGames))
                {
                    Console.WriteLine(game);
                }
    
                Console.ReadLine();
            }
        }
    }
    

    【讨论】:

    • 哇,谢谢韦斯顿!这是一个非常详细的答案和一段简洁的代码:) 测试它并且它有效。非常感谢您的宝贵时间:)
    • 让我有点困惑的是这一行: var game = gamesList.FirstOrDefault(g => g.Player1 == player[i] && g.Player2 == player[j] || g.Player2 == 玩家[i] && g.Player1 == 玩家[j]);这是什么意思?我理解该方法的目的,但我对它的工作原理感到困惑。谢谢
    • 它正在寻找包含两个玩家players[i]players[j] 的第一个MatchUp。表达式 g =&gt; ... 称为 lambda 表达式。 g 代表列表中的MatchUp=&gt; 的右侧是一个布尔表达式,告诉我们它是否适合这些玩家。 or (||) 是因为玩家可能处于不同的顺序,尽管现在考虑一下,这不应该发生。如果它没有找到MatchUp(因为该游戏已经玩过),那么OrDefault 部分会导致它返回null
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多