随机化列表,同时确保重复项不连续

Randomising a list whilst ensuring duplicates are not sequential

我正在寻找一种随机化列表顺序的有效机制,但需要注意的是,如果列表长度允许,重复项之间必须至少有 9 个项目,否则它们之间的距离必须尽可能远列表的长度将允许。

首先,这仅适用于小型数据集,列表中的项目永远不会超过 150 个,可能只有 1 个。

所以基本前提是这样的:

  1. 列出可能包含一些重复的人名(通常任何给定名称的实例永远不会超过 2 个,但在特殊情况下可能有 3 个)
  2. 随机化列表(我已经使用 Fisher-Yates(或 Knuth 洗牌)使这部分工作)
  3. 如果列表包含重复项,请找出它们之间少于 9 项的项,并尽可能调整以使差距至少为 9 项。

第 3 部分是棘手的部分,我没有关于 如何 系统应确保间距正确的任何业务规则的指南,只是 应该 尽可能这样。 在最简单的层面上,检查违规情况然后适当移动列表元素的迭代循环似乎是可行的方法,但我可以想象几种情况,其中对一对进行调整然后导致另一对出现问题,依此类推。

我不是在找人为此编写代码,我只是在寻找一些明智的想法,以一种好的、有效的方式来解决这个问题。

源列表将是C#中的IList<string>以供参考。

试试这个:

通过 #0 随机播放所有名称

第 1 关:找出哪些名称存在不止一次。

第 2 次:为每个多名称找到其最接近的配对,将此名称与相距 9 个位置的名称交换 如果 它不是一个称为多名称的名称-姓名。在这种情况下与 name 交换 10 个位置(重复并增加距离)

当然你需要注意一个名字是否接近列表的开头或结尾并适当处理。

我可能会这样做:

  1. 删除所有重复项,每项保留一个副本,但要记录每个项删除了多少。
  2. 随机化唯一条目列表
  3. 对于第 1 步中删除的每个重复项:
    1. 找到列表中的重复项。
    2. 将列表拆分为每个位置的部分 ± 9,忽略零或负长度分区。
    3. 确定哪个不包含重复的分区是最大的。
    4. 在该分区的随机位置插入副本。

试试下面的代码。我使用 Linq 将字符串组合在一起以获取所有重复项。然后我创建一个随机的唯一字符串列表。然后将剩余的重复项添加到列表中,将字符串均匀分布。结果完全随机且分布均匀。

注意:我发现了一个小错误。更改下面的行

from : firstItem += spacing;​
to : firstItem += spacing + 1;​

在调试代码时,我发现 firstItem 偶尔会变为负数,因此我添加了代码以确保 firstItem 始终为正数。然后我开始思考为什么在 firstItem 大于数组大小的情况下我没有得到任何溢出。那是当我意识到我必须在间距上加 1 时。带有数组 A、B、C、D、E 的旧代码将给出 1、1、1、1、1、A、B、C、D、E。新代码将给出 1,A,1,B,1,C,1,D,1,E.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication20
{
    class Program
    {
        static void Main(string[] args)
        {
            Random rand = new Random();
            List<string> input = new List<string>();
            List<string> output = new List<string>();
            //create 150 random strings with duplicates
            for(int i = 0; i < 150; i++)
            {
                input.Add(rand.Next(0,25).ToString());
            }
            //create dictionary with two columns key, number of entries
            Dictionary<string, Value> dict = input.AsEnumerable()
                .GroupBy(x => x)
                .ToDictionary(x => x.Key, y => new Value { count = y.Count(), ranNumber = rand.Next() });

            dict = dict.OrderBy(x => x.Value.ranNumber).ToDictionary(x => x.Key, y => y.Value);
            //add 1 sorted numbers to output
            foreach(string key in dict.Keys)
            {
                output.Add(key);
            }
            //add rest of numbers
            foreach (string key in dict.Keys)
            {
                int numberOfItems = dict[key].count;
                if (dict[key].count > 1)
                {
                    int arraySize = output.Count;
                    int spacing = arraySize / numberOfItems;
                    int firstItem = 0;
                    //center around middle
                    if (numberOfItems % 2 == 0)
                    {
                        firstItem = (arraySize / 2) - (((numberOfItems / 2) * spacing) + (spacing / 2));
                    }
                    else
                    {
                        firstItem = (arraySize / 2) - (((numberOfItems - 1) / 2) * spacing);
                    }
                    if (firstItem < 0)
                    {
                        firstItem = 0;
                    }
                    //remove existing item
                    output.Remove(key);
                    //insert items
                    for (int i = 0; i < numberOfItems; i++)
                    {
                        output.Insert(firstItem,key);
                        firstItem += spacing;
                    }
                }
            }

            
        }
        public class Value
        {
            public int count { get; set; }
            public int ranNumber { get; set; }
        }

    }
}