【问题标题】:Pick Random string from List<string> with exclusions and non-repetitive pick从 List<string> 中选择带有排除项和非重复选择的随机字符串
【发布时间】:2018-05-23 13:11:14
【问题描述】:

我正在尝试编写一个可以让用户使用的程序:

  1. 加载一组字符串。
  2. 循环遍历该集合,然后从同一集合中选择另一个字符串。
  3. 避免拾取的琴弦再次被拾取。
  4. 有特定的字符串无法选择指定的字符串。

示例如下表:

下表是一个示例场景:

我应该如何以简单的方式做到这一点?

我有下面的代码,但是生成一个有效的集合需要很长时间,因为如果没有任何东西可以选择,它会重新启动所有内容,而不是消除这种可能性。

private List<Participant> _participants;

AllOverAgain:

var pickedParticipants = new List<Participant>();
var participantPicks = new List<ParticipantPick>();

foreach(var participant in _participants)
{
    var pickedParticipantNames = from rp in participantPicks select rp.PickedParticipant;

    var picks = (from p in _participants where p.Name != participant.Name & !Utilities.IsInList(p.Name, pickedParticipantNames) select p).ToList();

    var pick = picks[new Random().Next(0, picks.Count())];

    if(pick == null)
    {
        UpdateStatus($"No Available Picks left for {participant.Name}, Restarting...");
        goto AllOverAgain;
    }

    var exclusions = participant.Exclusions.Split(',').Select(p => p.Trim()).ToList();

    if(exclusions.Contains(pick.Name))
    {
        UpdateStatus($"No Available Picks left for {participant.Name}, Restarting...");

        goto AllOverAgain;
    }

    participantPicks.Add(new ParticipantPick(participant.Name, pick.Name, participant.Number));
}

return participantPicks; // Returns the final output result

Participant 类由以下属性组成:

public string Name { get; set; }
public string Number { get; set; }
public string Exclusions { get; set; }

ParticipantPick 类由以下属性组成:

public string Participant { get; set; }
public string PickedParticipant { get; set; }
public string Number { get; set; }

【问题讨论】:

  • 谁能选谁谁不能选谁的标准是什么?为什么James must not be able to pick Tyrone ?你为此写了什么代码?请分享该代码。
  • 当您创建picks 列表时,为什么不删除排除项 随机选择一个?这应该消除一些重新启动
  • 不,您首先从除参与者之外的整个可用列表中选择一个pick,然后检查该选择是否不在participant.Exclusions 中。
  • 不要使用goto
  • @Nii goto

标签: c# string random enumerable


【解决方案1】:

在更简单的版本中,项目可以只是随机播放:

string[] source = { "A", "B", "C", "D", "E", "F" };
string[] picked = source.ToArray(); // copy
var rand = new Random();

for (int i = source.Length - 1, r; i > 0; --i)
{
    var pick = picked[r = rand.Next(i)];    // pick random item less than the current one 
    picked[r] = picked[i];                  // and swap with the current one
    picked[i] = pick;

    Console.WriteLine(i + " swapped with " + r);
}

Console.WriteLine("\nsource: " + string.Join(", ", source) + 
                  "\npicked: " + string.Join(", ", picked));

样本结果:

5 swapped with 4
4 swapped with 2
3 swapped with 0
2 swapped with 1
1 swapped with 0

source: A, B, C, D, E, F
picked: F, D, B, A, C, E

或者,源可以随意打乱,每个人都可以选择列表中的下一个人。

【讨论】:

    【解决方案2】:

    此代码将始终为您提供符合您标准的输出:

    public static class Program
    {
    
        public static void Main(string[] args)
        {
            var gathering = new Gathering();
            gathering.MakeSelections();
    
            foreach (var item in gathering.participants)
            {
                Console.WriteLine(item.name + ":" + item.selectedParticipant);
            }
        }
    
        public class Participant
        {
            public string name;
            public List<string> exclusions;
            public string selectedParticipant;
        }
    
        public class Gathering
        {
            public List<Participant> participants;
            public List<string> availableParticipants;
            public List<string> usedNames;
            public Dictionary<string, string> result;
    
            public Gathering()
            {
                //initialize participants
                participants = new List<Participant>();
    
                participants.Add(new Participant
                {
                    name = "James",
                    exclusions = new List<string> { "Tiffany", "Tyrone" }
                });
    
                participants.Add(new Participant
                {
                    name = "John",
                    exclusions = new List<string> { }
                });
    
                participants.Add(new Participant
                {
                    name = "Judith",
                    exclusions = new List<string> { }
                });
    
                participants.Add(new Participant
                {
                    name = "Rebecca",
                    exclusions = new List<string> { "James", "Tiffany" }
                });
    
                participants.Add(new Participant
                {
                    name = "Tiffany",
                    exclusions = new List<string> { }
                });
    
                participants.Add(new Participant
                {
                    name = "Tyrone",
                    exclusions = new List<string> { }
                });
    
                //prevent participants from selecting themselves
                foreach (Participant p in participants)
                {
                    p.exclusions.Add(p.name);
                }
    
                //create list of all the names (all available participants at the beginning)
                availableParticipants = participants.Select(x => x.name).ToList();
    
            }
    
            public void MakeSelections()
            {
                Participant currentParticipant;
                Random randy = new Random();
    
    
                //Sort Participants by the length of their exclusion lists, in descending order.
                participants.Sort((p1, p2) => p2.exclusions.Count.CompareTo(p1.exclusions.Count));
    
                //Get the first participant in the list which hasn't selected someone yet
                currentParticipant = participants.FirstOrDefault(p => p.selectedParticipant == null);
    
                while (currentParticipant != null)
                {
                    //of the available participants, create a list to choose from for the current participant
                    List<string> listToChooseFrom = availableParticipants.Where(x => !currentParticipant.exclusions.Contains(x)).ToList();
    
                    //select a random participant from the list of eligible ones to be matched with the current participant
                    string assignee = listToChooseFrom[randy.Next(listToChooseFrom.Count)];
                    currentParticipant.selectedParticipant = assignee;
    
                    //remove the selected participant from the list of available participants
                    availableParticipants.RemoveAt(availableParticipants.IndexOf(assignee));
    
                    //remove the selected participant from everyone's exclusion lists
                    foreach (Participant p in participants)
                        if (p.exclusions.Contains(assignee))
                            p.exclusions.RemoveAt(p.exclusions.IndexOf(assignee));
    
                    //Resort Participants by the length of their exclusion lists, in descending order.
                    participants.Sort((p1, p2) => p2.exclusions.Count.CompareTo(p1.exclusions.Count));
    
                    //Get the first participant in the list which hasn't selected someone yet
                    currentParticipant = participants.FirstOrDefault(p => p.selectedParticipant == null);
                }
    
                //finally, sort by alphabetical order
                participants.Sort((p1, p2) => p1.name.CompareTo(p2.name));
    
            }
        }
    }
    

    【讨论】:

      【解决方案3】:

      解决此问题的一种方法是使用dictionary,使用tuple 的组合键和数据类型bool 的匹配值。

      Dictionary<Tuple<string, string>, bool>
      

      复合键 Tuple&lt;sring,string&gt; 将包含参与者的每个排列,并将它们与相应的 bool 值相匹配。

      例如,字典中填充的值如:

      Dictionary<Tuple<"Judith","James">, true>
      

      ...将表明 Judith 选择 James 是有效的。

      因此,让我们为每个可能的参与者组合创建一个字典,并将它们的值设置为true,以便它们在程序开始时有效。

      这可以通过cartesian join using an array with itself 来完成。

      Dictionary<Tuple<string, string>, bool> dictionary = participants.SelectMany(left => participants, (left, right) => new Tuple<string, string>(left, right)).ToDictionary(item=> item, item=>true);
      

      在获取所有可能选择的排列并将它们设置为 true 之后,我们可以通过“不允许选择”列表并将该复合键的字典值更改为 false。

      dictionary[new Tuple<string, string>(personNotAllowing, notAllowedPerson)] = false;
      

      您可以通过以下方式使用循环将参与者从选择中移除:

      for(int abc=0;abc<participants.Length;abc++)
      {
          //remove clone set
          Tuple<string, string> clonePair = Tuple.Create(participants[abc], participants[abc]);
          dictionary.Remove(clonePair);
      }
      

      或者只需将克隆对的值更改为 false。

      for(int abc=0;abc<participants.Length;abc++)
      {
          dictionary[Tuple.Create(participants[abc],participants[abc])] = false;
      }
      

      在这个示例程序中,我为参与者创建了一个string[],并为他们不允许的相应人员列表创建了一个string[]。然后我执行笛卡尔连接,参与者数组与本身。这导致每个排列都有一个初始的true 布尔值。

      我将不允许参与者的字典更改为假,并显示示例字典。

      之后,我创建了 10 个随机参与者实例,这些参与者正在挑选其他随机参与者并测试它是否有效。

      每次参与者选择另一个参与者时,我都会检查该复合键以查看其值是否为 true

      如果确实产生了有效的选择,那么结果参与者被选择的每个组合都被设置为false

       for(int j=0; j<participants.Length;j++)
       {
           //Make the partner never be able to be picked again
           Tuple<string, string> currentPair2 = Tuple.Create(partner, participants[j]);
           try
           {
               dictionary[currentPair2] = false;
           }
           catch
           {
           }
      }
      

      通过运行代码可以更好地说明这个概念。

      演示:

      static void Main(string[] args)
      {
          //Create participants set
          string[] participants = {"James","John","Tyrone","Rebecca","Tiffany","Judith"};
      
          //Create not allowed lists
          string[] jamesNotAllowedList = {"Tiffany", "Tyrone"};
          string[] johnNotAllowedList = {};
          string[] tyroneNotAllowedList = {};
          string[] rebeccaNotAllowedList ={"James", "Tiffany"};
          string[] judithNotAllowedList = {};
          //Create list of not allowed lists
          string[][] notAllowedLists = { jamesNotAllowedList, johnNotAllowedList, tyroneNotAllowedList, rebeccaNotAllowedList, judithNotAllowedList};
      
          //Create dictionary<Tuple<string,string>, bool> from participants array by using cartesian join on itself
          Dictionary<Tuple<string, string>, bool> dictionary = participants.SelectMany(left => participants, (left, right) => new Tuple<string, string>(left, right)).ToDictionary(item=> item, item=>true);
      
          //Loop through each person who owns a notAllowedList 
          for (int list = 0; list < notAllowedLists.Length; list++)
          {
              //Loop through each name on the not allowed list
              for (int person = 0; person<notAllowedLists[list].Length; person++)
              {
                  string personNotAllowing = participants[list];
                  string notAllowedPerson = notAllowedLists[list][person];
                  //Change the boolean value matched to the composite key
                  dictionary[new Tuple<string, string>(personNotAllowing, notAllowedPerson)] = false;
                  Console.WriteLine(personNotAllowing + " did not allow " + notAllowedPerson);
              }
          }                
      
          //Then since a participant cant pick itself
          for(int abc=0;abc<participants.Length;abc++)
          {
              //remove clone set
              Tuple<string, string> clonePair = Tuple.Create(participants[abc], participants[abc]);
              dictionary.Remove(clonePair);
          }
      
          //Display whats going on with this Dictionary<Tuple<string,string>, bool>
          Console.WriteLine("--------Allowed?--Dictionary------------\n");
          Console.WriteLine(string.Join("  \n", dictionary));
          Console.WriteLine("----------------------------------------\n\n");
      
          //Create Random Object
          Random rand = new Random();
      
          //Now that the data is organized in a dictionary..
            //..Let's have random participants pick random participants
      
          //For this demonstration lets try it 10 times
          for (int i=0;i<20;i++)
          {
              //Create a new random participant
              int rNum = rand.Next(participants.Length);
              string randomParticipant = participants[rNum];
              //Random participant picks a random participant
              string partner = participants[rand.Next(participants.Length)];
      
              //Create composite key for the current pair
              Tuple<string, string> currentPair = Tuple.Create(partner,randomParticipant);
      
              //Check if it's a valid choice
              try
              {
                  if (dictionary[currentPair])
                  {
                      Console.WriteLine(randomParticipant + " tries to pick " + partner);
                      Console.WriteLine("Valid.\n");
                      //add to dictionary
                      for(int j=0; j<participants.Length;j++)
                      {
                          //Make the partner never be able to be picked again
                          Tuple<string, string> currentPair2 = Tuple.Create(partner, participants[j]);
                          try
                          {
                              dictionary[currentPair2] = false;
                          }
                          catch
                          {
      
                          }
                      }
      
                  }
                  else
                  {
                      Console.WriteLine(randomParticipant + " tries to pick " + partner);
                      Console.WriteLine(">>>>>>>>Invalid.\n");
                  }
              }
              catch
              {
                  //otherwise exception happens because the random participant
                    //And its partner participant are the same person
      
                  //You can also handle the random participant picking itself differently
                    //In this catch block
      
                  //Make sure the loop continues as many times as necessary
                  //by acting like this instance never existed
                  i = i - 1;
              }
      
      
          }
      
      
          Console.ReadLine();
      
      }
      

      【讨论】:

      • 在创建每个 _notAllowedList 的部分,假设这可以在运行时完成是否安全?当然,詹姆斯和其他人并不总是参与者,他们的人数可能会上升或下降。
      • @Nii 字典是在更改其中的条目之前制作的。我将代码最小化为一行,但基本上它将参与者的所有可能组合创建为 default 值为 true 的键。字典可以存储在 4,294,967,296 键附近的某个地方。因此,只要您没有过多的参与者,理论上应该没问题。在您创建每个 notAllowedList 的部分中,实质上是在 notAllowedLists 中使用 notAllowedList 遍历每个人,并将字典中的条目设置为 false.Recomend test 1st。
      猜你喜欢
      • 2016-06-08
      • 1970-01-01
      • 2011-11-13
      • 2014-03-10
      • 1970-01-01
      • 2017-10-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多