如何将2个已排序的列表合并到一个混洗列表中,同时在c#中保持内部顺序

时间:2011-01-02 09:42:37

标签: c# algorithm sorting shuffle

我想要一个混乱的合并列表,它将保持列表的内部顺序。

例如:

列表A:11 22 33

列表B:6 7 8

有效结果: 11 22 6 33 7 8

无效结果:22 11 7 6 33 8

7 个答案:

答案 0 :(得分:2)

只需随机选择一个列表(例如,生成0到1之间的随机数,如果< 0.5列表A,否则列出B),然后从该列表中取出该元素并将其添加到新列表中。重复,直到每个列表中没有剩余元素。

答案 1 :(得分:2)

原始答案:

static IEnumerable<T> MergeShuffle<T>(IEnumerable<T> lista, IEnumerable<T> listb)
{
    var first = lista.GetEnumerator();
    var second = listb.GetEnumerator();

    var rand = new Random();
    bool exhaustedA = false;
    bool exhaustedB = false;
    while (!(exhaustedA && exhaustedB))
    {
        bool found = false;
        if (!exhaustedB && (exhaustedA || rand.Next(0, 2) == 0))
        {
             exhaustedB = !(found = second.MoveNext());
            if (found)
                yield return second.Current;
        }
        if (!found && !exhaustedA)
        {
            exhaustedA = !(found = first.MoveNext());
            if (found)
                yield return first.Current;
        }
    }                
}

基于marcog答案的第二个答案

    static IEnumerable<T> MergeShuffle<T>(IEnumerable<T> lista, IEnumerable<T> listb)
    {
        int total = lista.Count() + listb.Count();
        var random = new Random();
        var indexes = Enumerable.Range(0, total-1)
                                .OrderBy(_=>random.NextDouble())
                                .Take(lista.Count())
                                .OrderBy(x=>x)
                                .ToList();

        var first = lista.GetEnumerator();
        var second = listb.GetEnumerator();

        for (int i = 0; i < total; i++)
            if (indexes.Contains(i))
            {
                first.MoveNext();
                yield return first.Current;
            }
            else
            {
                second.MoveNext();
                yield return second.Current;
            }
    }

答案 2 :(得分:1)

在区间[0,A.Length)中生成B.Length个随机整数。对随机数进行排序,然后从i迭代0..A.Length,将A[i]添加到r[i]+i中的B位置。 +i是因为当您从B插入值时,您正在将A中的原始值向右移动。

这与您的RNG一样随机。

答案 3 :(得分:1)

如果您需要将输出均匀分布,则此页面中提供的答案均无效。

为说明我的示例,假设我们要合并两个列表A=[1,2,3]B=[a,b,c]

在大多数答案中提到的方法中(即,将两个列表合并为la mergesort,但每次都随机选择一个列表头),输出[1 a 2 b 3 c]的可能性远小于[1 2 3 a b c]。直观上,发生这种情况是因为当列表中的元素用完时,另一个列表中的元素会附加在末尾。因此,第一种情况的概率为0.5*0.5*0.5 = 0.5^3 = 0.125,但是在第二种情况下,存在更多的随机随机事件,因为随机头必须被选择5次而不是3次,这给了我们概率0.5^5 = 0.03125中的。经验评估也可以轻松验证这些结果。

@marcog建议的答案几乎是正确的。但是,存在r的排序后分布不均匀的问题。发生这种情况是因为原始列表[0,1,2][2,1,0][2,1,0]都被排序为[0,1,2],这使得排序后的r比{ {1}}仅有一种可能性。

有一种巧妙的方式来生成列表[0,0,0],使其均匀分布,如下面的数学StackExchange问​​题:https://math.stackexchange.com/questions/3218854/randomly-generate-a-sorted-set-with-uniform-distribution

所示。

要总结该问题的答案,必须采样| B |。集合r中的元素(均匀随机且无重复),对结果进行排序,然后将其索引减去此新列表中的每个元素。结果是列表{0,1,..|A|+|B|-1},可用于替换@marcog的答案。

答案 4 :(得分:0)

除了生成索引列表之外,还可以根据每个列表中剩余元素的数量来调整概率,从而完成此操作。在每次迭代中,A将剩余A_size个元素,B将剩余B_size个元素。从1 ..(A_size + B_size)中选择一个随机数R。如果R <= A_size,则使用A中的元素作为输出中的下一个元素。否则,请使用B中的元素。

int A[] = {11, 22, 33}, A_pos = 0, A_remaining = 3;
int B[] = {6, 7, 8}, B_pos = 0, B_remaining = 3;

while (A_remaining || B_remaining) {
  int r = rand() % (A_remaining + B_remaining);

  if (r < A_remaining) {
    printf("%d ", A[A_pos++]);
    A_remaining--;
  } else {
    printf("%d ", B[B_pos++]);
    B_remaining--;
  }
}

printf("\n");

随着列表变小,从中选择元素的可能性将降低。

这可以缩放到多个列表。例如,给定列表A,B和C的大小分别为A_size,B_size和C_size,请在1 ..(A_size + B_size + C_size)中选择R。如果R <= A_size,则使用A中的元素。否则,如果R <= A_size + B_size,则使用B中的元素。否则C。

答案 5 :(得分:0)

这是一个确保输出均匀分布的解决方案,并且很容易得出原因。首先想到的是生成令牌列表,其中每个令牌代表特定列表的一个元素,而不是特定元素。例如,对于两个具有3个元素的列表,我们生成以下令牌列表:0、0、0、1、1、1。然后,对令牌进行洗牌。最后,我们为每个令牌生成一个元素,然后从相应的原始列表中选择下一个元素。

public static IEnumerable<T> MergeShufflePreservingOrder<T>(
    params IEnumerable<T>[] sources)
{
    var random = new Random();
    var queues = sources
        .Select(source => new Queue<T>(source))
        .ToArray();
    var tokens = queues
        .SelectMany((queue, i) => Enumerable.Repeat(i, queue.Count))
        .ToArray();
    Shuffle(tokens);
    return tokens.Select(token => queues[token].Dequeue());

    void Shuffle(int[] array)
    {
        for (int i = 0; i < array.Length; i++)
        {
            int j = random.Next(i, array.Length);
            if (i == j) continue;
            if (array[i] == array[j]) continue;
            var temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }
    }
}

用法示例:

var list1 = "ABCDEFGHIJKL".ToCharArray();
var list2 = "abcd".ToCharArray();
var list3 = "@".ToCharArray();
var merged = MergeShufflePreservingOrder(list1, list2, list3);
Console.WriteLine(String.Join("", merged));

输出:

  

ABCDaEFGHIb @ cJKLd

答案 6 :(得分:-1)

只需遍历两个列表,同时随机选择要从中挑选项目的列表。然后将它们粘贴到另一个列表中这是一种方法:

static IEnumerable<T> Shuffle<T>(IEnumerable<T> listB, IEnumerable<T> listA)
{
    var rng = new Random();
    var lists = new[] { new Queue<T>(listA), new Queue<T>(listB) };
    while (lists.Any(l => l.Any()))
    {
        int i = rng.Next(2);
        var selected = lists[i];
        if (!lists[i].Any())
            selected = lists[1-i];
        yield return selected.Dequeue();
    }
}