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

Posted

技术标签:

【中文标题】如何在c#中保持内部顺序的同时将2个排序的列表合并到一个随机列表中【英文标题】:How to merge 2 sorted listed into one shuffled list while keeping internal order in c# 【发布时间】:2011-06-02 10:46:46 【问题描述】:

我想生成一个混洗后的合并列表,以保持列表的内部顺序。

例如:

列表 A:11 22 33

列表 B:6 7 8

有效结果:11 22 6 33 7 8

无效结果:22 11 7 6 33 8

【问题讨论】:

@Mitch 这样的评论有什么目的?做个好人! @marcog:我既不好也不坏。 【参考方案1】:

这可能会更容易,假设您有一个包含三个值的列表,以匹配另一个表中的 3 个值。 您还可以使用身份 (1,2) 对身份进行排序

Create TABLE #tmp1 (ID int identity(1,1),firstvalue char(2),secondvalue char(2))
Create TABLE #tmp2 (ID int identity(1,1),firstvalue char(2),secondvalue char(2))

Insert into #tmp1(firstvalue,secondvalue) Select firstvalue,null secondvalue from firsttable

Insert into #tmp2(firstvalue,secondvalue) Select null firstvalue,secondvalue from secondtable

Select a.firstvalue,b.secondvalue from #tmp1 a join #tmp2 b on a.id=b.id

DROP TABLE #tmp1
DROP TABLE #tmp2

【讨论】:

【参考方案2】:

这是一个确保输出均匀分布的解决方案,而且原因很简单。这个想法是首先生成一个标记列表,其中每个标记表示特定列表的一个元素,而不是特定元素。例如,对于两个具有 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

【讨论】:

【参考方案3】:

这可以通过根据每个列表中剩余的元素数量调整概率来完成,而不是生成索引列表。在每次迭代中,A 将剩余 A_size 个元素,B 将剩余 B_size 个元素。从 1..(A_size + B_size) 中选择一个随机数 R。如果 R

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_size、B_size 和 C_size 的列表 A、B 和 C,请在 1..(A_size+B_size+C_size) 中选择 R。如果 R

【讨论】:

【参考方案4】:

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

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

在大多数答案中提到的方法中(即通过合并排序合并两个列表,但每次随机选择一个列表头),输出[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 次,所以我们的概率为@ 987654328@。经验评估也很容易验证这些结果。

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

有一种巧妙的方法可以生成列表 r,使其均匀分布,如以下 Math StackExchange 问题所示:https://math.stackexchange.com/questions/3218854/randomly-generate-a-sorted-set-with-uniform-distribution

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

【讨论】:

在此要点中:gist.github.com/setzer22/76c12d2a9b4d0e519e4b746f3d47795f 您可以找到完整算法的 clojure 实现。【参考方案5】:

原答案:

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;
            
    

【讨论】:

第二个答案有什么好处? Nothing 只是使用 linq 的替代答案 第一个答案不会随机分布。考虑A 有 1 个值,B 有 10 个值的情况。您更有可能将来自 A 的值放在列表前面附近。 任何不可预测的东西都被认为是随机的。所以说第一个不是随机的是不正确的,但是你的观点是正确的,它不会像第二个那样随机。【参考方案6】:

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

这将与您的 RNG 一样随机。

【讨论】:

这似乎不能产生所有可能性(但至少看起来与上述答案不同)。考虑两个单元素列表:您将在 [0,1) 中生成一个始终为零的数字,因此您将始终首先生成第一个列表元素的结果。如果您在 [0, B.Length] 中生成一个数字,我认为它应该可以工作? 感谢您的回答(以及来自@BeeOnRope 的评论)!我正在研究一种模拟算法,其中实现均匀分布非常重要,其他答案都没有解决这个问题。 @BeeOnRope 不幸的是,经过经验评估后,我发现此解决方案也不会产生均匀分布。对r 进行排序时会出现问题。例如,对r进行排序后,序列[0,0,0]的可能性远小于[0,1,2],因为只有一个列表会被排序到第一个,但不止一个(例如 [1,0,2], [2,1,0],...)将被排序到第二个。【参考方案7】:

只需随机选择一个列表(例如,生成一个介于 0 和 1 之间的随机数,如果

【讨论】:

以上是关于如何在c#中保持内部顺序的同时将2个排序的列表合并到一个随机列表中的主要内容,如果未能解决你的问题,请参考以下文章

LINQ合并2个列表,保持顺序和来源[重复]

在保持原始行顺序的同时合并两个数据框

如何在保持数据分布的同时从python中的列表中随机采样

分治策略合并多个排序数组

Firestore - 在本地合并两个查询

如何合并、拆分和查询第 k 个排序列表?