【问题标题】:Assign to each item another item in C#在 C# 中为每个项目分配另一个项目
【发布时间】:2021-04-01 15:59:30
【问题描述】:

我想写一个圣诞彩票算法。我希望我输入的人与其他不是自己的人匹配并打印在屏幕上。我写了一个类似下面的代码。但是因为它每次都会从列表中删除一个元素,所以我的程序匹配的人数少于输入的人数。

我怎样才能找到解决这个问题的方法?

List<string> person = new List<string>() { "Joe","John","Chris","Henry","Tom","Patrick"};
        List<string> personCopy = new List<string>(person);

        for (int i = 0; i < person.Count; i++)
        {
            string person1 = person[i];
            Random rnd = new Random();
            var index = rnd.Next(personCopy.Count);
            while (i == index) {
                index = rnd.Next(personCopy.Count);
            }

            string person2 = personCopy[index];
            person.RemoveAt(i);
            personCopy.RemoveAt(index);
            Console.WriteLine("{0}  > {1}",person1,person2);
            
        }

【问题讨论】:

  • @JohnG 首先,感谢您的警告。我会想。但是当我以这种方式组织它时,它只给出 3 个匹配项,而不是 6 个不同的匹配项。
  • 向后迭代
  • 作为替代方案...随机播放您的列表(查找“随机播放 C#”,或 stackoverflow.com/questions/4412405/…)。然后将第一个人与第二个人联系起来,依此类推,直到最后一个人(您与第一个人联系起来)。这是随机的,你不可能将一个人与他/她自己联系起来
  • @Flydog57 有效并提供了一个很好的解决方案,但并不能使所有解决方案成为可能。考虑构建一个以人为节点的有向图,我们需要让每个节点都有一条进出边。这最终看起来像一个具有任意数量的断开连接的简单循环的图。该解决方案仅创建包含所有节点的单个循环的图。话虽如此,我认为这是一个极好的、简单的解决方案,通常这样做的人很可能会接受这个警告。如果我不需要其他解决方案,我会这样做。
  • 规范是“我希望我输入的人与其他不是自己的人匹配并打印在屏幕上”。这符合规范(据我所知)。我们的想法不是考虑所有可能的解决方案,而是要找到一个解决方案。这实际上等同于从洗好的牌组中分发两张牌,并根据发牌进行匹配。使用 shuffle 通常是解决 Random 问题的最简单方法

标签: c# list arraylist


【解决方案1】:

除了排他性问题(有人选择自己的名字)之外,改组效果很好。

考虑到这一点,有很多方法可以实现这一目标。这种方法是“相对” O(n)(线性)时间复杂度,尽管它可能不是最有效的,因为它必须在池得到时执行IndexOf长度为 2。您不希望某人成为自己的秘密圣诞老人。另外,因为这个事实它不会是最均匀的概率分布¯\_(ツ)_/¯

简而言之,不要在生死攸关的秘密圣诞老人情况下使用它。

给定

private static IEnumerable<(T, T)> FunkyLotto<T>(IEnumerable<T> source)
{
    var pool = source.ToList();
    var iteration = pool.ToList();
    foreach (var item in iteration)
    {
        int choice;
        if (pool.Count == 2 && pool.IndexOf(item) >= 0) 
            // special case when there is only 2 left, and there is a case for a duplicate
            choice = (pool.IndexOf(item) + 1) % pool.Count;
        else
            choice = _r.Next(0, pool.Count);
        choice = Equals(item, pool[choice]) ? (choice + 1) % pool.Count : choice;
        yield return (item, pool[choice]);
        pool.RemoveAt(choice);
    }
}

用法

var list = new[] {"Joe", "John", "Chris", "Henry", "Tom", "Patrick"};

foreach (var result in FunkyLotto(list))
    Console.WriteLine(result);

输出

(Joe, Patrick)
(John, Chris)
(Chris, Joe)
(Henry, Tom)
(Tom, John)
(Patrick, Henry)

Full Demo Here

或者更有效的变体,它省略了Remove 的内存副本,并对池中倒数第二个和最后一个项目进行引用检查。

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Swap<T>(T[] array, int indexA, int indexB)
{
   var tmp = array[indexA];
   array[indexA] = array[indexB];
   array[indexB] = tmp;
}


private static IEnumerable<(T, T)> FunkyLotto2<T>(IEnumerable<T> source)
{
   var pool = source.ToArray();
   var iteration = pool.ToArray();
   var poolLength = pool.Length;

   foreach (var item in iteration)
   {
      int choice;
      if (poolLength == 2)
         choice = Equals(item, pool[0]) ? 1 : 0;
      else
      {
         choice = _r.Next(0, poolLength);
         choice = Equals(item, pool[choice]) ? (choice + 1) % poolLength : choice;
      }

      yield return (item, pool[choice]);
      Swap(pool, choice, poolLength - 1);
      poolLength--;
   }

}

还有一个更有效的变体,它使用数组参数/结果,以及基于指针的堆栈分配索引数组。

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void Swap(int* array, int indexA, int indexB)
{
   var tmp = array[indexA];
   array[indexA] = array[indexB];
   array[indexB] = tmp;
}

public static unsafe (T, T)[] FunkyLotto3<T>(T[] source)
{
   var pool = stackalloc int[source.Length];

   for (var i = 0; i < source.Length; i++)
      pool[i] = i;

   var result = new (T, T)[source.Length];

   var poolLength = source.Length;

   for (var index = 0; index < source.Length; index++)
   {
      int choice;
      if (poolLength == 2)
         choice = index == pool[0] ? 1 : 0;
      else
      {
         choice = _r.Next(0, poolLength);
         choice = index == pool[choice] ? (choice + 1) % poolLength : choice;
      }

      result[index] = (source[index], source[pool[choice]]);
      Swap(pool, choice, poolLength - 1);
      poolLength--;
   }

   return result;
}

因为我可以,这里有一些基准

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.1256 (1909/November2018Update/19H2)
AMD Ryzen 9 3900X, 1 CPU, 24 logical and 12 physical cores
.NET Core SDK=5.0.101
  [Host]        : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
  .NET Core 5.0 : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT

Job=.NET Core 5.0  Runtime=.NET Core 5.0
Method N Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
Original 100 6.209 us 0.0478 us 0.0448 us 0.7019 - - 5896 B
Modified 100 3.575 us 0.0301 us 0.0282 us 0.6943 - - 5824 B
Modified2 100 1.312 us 0.0041 us 0.0036 us 0.0267 - - 224 B
Original 1000 67.507 us 0.2349 us 0.2197 us 6.4697 0.1221 - 54640 B
Modified 1000 29.546 us 0.4567 us 0.4272 us 6.5002 0.1221 - 54568 B
Modified2 1000 13.413 us 0.0230 us 0.0215 us 0.2289 - - 2024 B
Original 10000 1,005.864 us 2.7138 us 2.5385 us 64.4531 7.8125 - 553608 B
Modified 10000 289.806 us 1.7409 us 1.5432 us 65.9180 10.2539 - 553536 B
Modified2 10000 130.208 us 0.4183 us 0.3493 us 2.1973 - - 20024 B

这里有完整的测试代码

[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[MemoryDiagnoser]

public class Test
{
   private byte[] data;

   [Params(100, 1000, 10000)] public int N;

   [GlobalSetup]
   public void Setup()
   {
      data = new byte[N];
      new Random(42).NextBytes(data);
   }

   private static readonly Random _r = new Random();

   private static IEnumerable<(T, T)> FunkyLotto<T>(IEnumerable<T> source)
   {
      var pool = source.ToList();
      var iteration = pool.ToList();
      foreach (var item in iteration)
      {
         int choice;
         if (pool.Count == 2 && pool.IndexOf(item) >= 0) // special case when there is only 2 left, and there is a case for a duplicate
            choice = (pool.IndexOf(item) + 1) % pool.Count;
         else
            choice = _r.Next(0, pool.Count);
         choice = Equals(item, pool[choice]) ? (choice + 1) % pool.Count : choice;
         yield return (item, pool[choice]);
         pool.RemoveAt(choice);
      }
   }


   [MethodImpl(MethodImplOptions.AggressiveInlining)]
   private static void Swap<T>(T[] array, int indexA, int indexB)
   {
      var tmp = array[indexA];
      array[indexA] = array[indexB];
      array[indexB] = tmp;
   }


   private static IEnumerable<(T, T)> FunkyLotto2<T>(IEnumerable<T> source)
   {
      var pool = source.ToArray();
      var iteration = pool.ToArray();
      var poolLength = pool.Length;

      foreach (var item in iteration)
      {
         int choice;
         if (poolLength == 2)
            choice = Equals(item, pool[0]) ? 1 : 0;
         else
         {
            choice = _r.Next(0, poolLength);
            choice = Equals(item, pool[choice]) ? (choice + 1) % poolLength : choice;
         }

         yield return (item, pool[choice]);
         Swap(pool, choice, poolLength - 1);
         poolLength--;
      }

   }


   [MethodImpl(MethodImplOptions.AggressiveInlining)]
   private static unsafe void Swap(int* array, int indexA, int indexB)
   {
      var tmp = array[indexA];
      array[indexA] = array[indexB];
      array[indexB] = tmp;
   }

   public static unsafe (T, T)[] FunkyLotto3<T>(T[] source)
   {
      var pool = stackalloc int[source.Length];

      for (var i = 0; i < source.Length; i++)
         pool[i] = i;

      var result = new (T, T)[source.Length];

      var poolLength = source.Length;

      for (var index = 0; index < source.Length; index++)
      {
         int choice;
         if (poolLength == 2)
            choice = index == pool[0] ? 1 : 0;
         else
         {
            choice = _r.Next(0, poolLength);
            choice = index == pool[choice] ? (choice + 1) % poolLength : choice;
         }

         result[index] = (source[index], source[pool[choice]]);
         Swap(pool, choice, poolLength - 1);
         poolLength--;
      }

      return result;
   }

   [Benchmark]
   public (byte, byte)[] Original() => FunkyLotto(data).ToArray();

   [Benchmark]
   public (byte, byte)[] Modified() => FunkyLotto2(data).ToArray();

   [Benchmark]
   public (byte, byte)[] Modified2() => FunkyLotto3(data);
}

【讨论】:

    【解决方案2】:

    这是因为您在向上迭代其索引时从 person 中删除了一个元素。

    i=0 的第一次迭代中,您删除了0 处的元素,这会将所有内容向下移动(即1 处的旧元素现在位于0,来自2 的元素位于1 等)。

    在此之后,您递增i,使其值现在为1。现在在下一次迭代中,您将采用元素 1(之前位于 2)。这已跳过最初位于位置1 的元素。这种情况继续发生,每次迭代都会跳过另一个元素。

    解决这个问题的方法相当简单,但如果您根据这些知识自己弄清楚,它可能对您更有帮助。如果你真的需要清楚地说明如何做到这一点,我可以做到——但我仍然强烈建议你先弄清楚自己是如何做到的。编程会遇到这样的小问题,学习自己解决这些问题很重要。

    您可能会遇到的下一个问题是您要从personpersonCopy 中删除不同的元素,因此同一元素的索引实际上不会匹配。因此,元素“不同”的测试根本没有测试它,当您到达最终元素时,我希望该解决方案将导致选择随机元素的循环在您解决当前问题后永远不会终止。我认为有一种不同的方法比你目前的方法更有意义,但如果你先修复你目前拥有的东西,而不是盲目地复制我说会更好用的东西,你可能会学到更多。

    【讨论】:

    • 感谢您的评论。当我按照您的指示检查我的代码时,我解决了问题
    猜你喜欢
    • 1970-01-01
    • 2020-03-14
    • 1970-01-01
    • 1970-01-01
    • 2011-01-19
    • 2020-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多