【问题标题】:Shuffled Combination of multiple List<int>多个 List<int> 的 Shuffled 组合
【发布时间】:2025-07-29 23:25:22
【问题描述】:

与此类似的问题:Combination of List<List<int>>

但是,改组使这变得不简单,IMO 需要一个完全不同的解决方案。我很高兴被证明是错误的,但这并不像改组结果那么容易,因为结果列表无法放入内存:

我需要合并 3 个 LARGE 列表:

List<int> A = new List<int> {1, 2, 3, ...};
List<int> B = new List<int> {4, 5, 6, ...};
List<int> C = new List<int> {7, 8, 9, ...};

输出是一个新的List&lt;object&gt; { {1, 4, 7}, {1, 4, 8}, ... }

但是,我需要对结果列表进行洗牌。如果每个单独的列表在组合时仍然被模式化,那么洗牌是不够的,虽然 3 个列表可以放入内存,但所有 3 个列表的组合将不适合。洗牌后的索引列表显然也太大而无法存储在内存中。

我尝试了许多不同的方法,但如果不先加载每个项目,我就找不到一种方法来随机化订单。这可能吗?谢谢!

【问题讨论】:

  • object是什么类型?
  • @TimSchmelter 本质上是一个包含所有 3 个整数的结构
  • 您需要从每个列表中取出所有数字而不重复,还是仅总数为A.Count+B.Count+C.Count并允许重复?
  • @TimSchmelter 总数应为A.Count*B.Count*C.Count。我一直在通过忽略重复来解决这个问题,然后检查处理端是否已经测试过某些东西。这工作正常,但也意味着我不知道我何时完成了所有可能的组合并且这样做时没有任何进展 - 基本上是一个长时间运行的无限循环。
  • 好吧,只要您有空闲的预处理时间,将包含ints 0 .. A.Count * B.Count * C.Count - 1 的文件存储到磁盘,然后对其执行Fisher-Yates shuffle。它非常慢(大量随机磁盘访问),但没有捷径可以正确洗牌。

标签: c# linq list generics combinations


【解决方案1】:

如果所有列表的大小相同:

var aShuffle = new List<int>(A.Count);
aShuffle.AddRange(A.Shuffle());
var bShuffle = new List<int>(B.Count);
bShuffle.AddRange(B.Shuffle());
var cShuffle = new List<int>(C.Count);
cShuffle.AddRange(C.Shuffle());

List<Trio> trios = new List<Trio>(aShuffle.Count);
for (int i = 0; i < aShuffle.Count; i++)
{
    trios.Add(new Trio { Value1 = aShuffle[i], Value2 = bShuffle[i], Value3 = cShuffle[i] });
}

使用这个结构:

public struct Trio
{
    public int Value1 { get; set; }
    public int Value2 { get; set; }
    public int Value3 { get; set; }
}

我已使用此扩展程序随机播放 (Fisher-Yates) 集合:

public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source)
{
    return source.Shuffle(new Random());
}

public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
{
    if (source == null) throw new ArgumentNullException("source");
    if (rng == null) throw new ArgumentNullException("rng");

    return source.ShuffleIterator(rng);
}

private static IEnumerable<T> ShuffleIterator<T>(
    this IEnumerable<T> source, Random rng)
{
    List<T> buffer = source.ToList();
    for (int i = 0; i < buffer.Count; i++)
    {
        int j = rng.Next(i, buffer.Count);
        yield return buffer[j];

        buffer[j] = buffer[i];
    }
}

Demonstration

Value1: 3 Value2: 5 Value3: 8
Value1: 1 Value2: 6 Value3: 9
Value1: 6 Value2: 4 Value3: 7
Value1: 2 Value2: 8 Value3: 33

【讨论】:

    【解决方案2】:

    您是否尝试过使用Union LINQ-Extension?

    你可以用它来统一你的列表,然后随机播放结果:)

    【讨论】:

      【解决方案3】:

      扩展我的评论...

      首先,确定您不能在物理内存中进行这种随机播放。如果您的物品少于UInt32.MaxValue,您仍然可以使用use managed arrays。此外,您只需为每个项目存储一个int(或可能是long),而不是项目本身。

      那么,你有这么多项你在内存中数不过来?

      首先在文件中为每个项目存储一个数字。

      using (var f = File.Create("shuffle.bin"))
      {
          var length = A.Count * B.Count * C.Count;
      
          f.SetLength(length * sizeof(long));
          for (long i = 0; i < length; i++)
          {
              var bytes = BitConverter.GetBytes(i);
              f.Write(bytes, 0, sizeof(long));
          }
      }
      

      洗牌这个文件需要很长时间。因此,我们可以根据需要一次洗出一个条目。提取一个数字应该不会花费太长时间。

      using (var f = File.Open("shuffle.bin", FileMode.Open))
      {
          // Calculate how many numbers are left in the file.
          long itemCount = f.Length / sizeof(long);
          if (itemCount == 0)
          {
              // We have used all the items - create another file, or throw
              // an exception, or whatever you want.
          }
      
          // You need an equivalent of `Random.Next(int max)` here that works on `long`s.
          long index = NextLong(itemCount);
      
          // Read out the number you've shuffled out.
          f.Seek(index * sizeof(long), SeekOrigin.Begin);
          var rtnBytes = new byte[sizeof(long)];
          f.Read(rtnBytes, 0, sizeof(long));
      
          // Read out the last number.
          f.Seek((itemCount - 1) * sizeof(long), SeekOrigin.Begin);
          var rplcBytes = new byte[sizeof(long)];
          f.Read(rplcBytes, 0, sizeof(long));
      
          // Replace the shuffled-out number with the last number.
          f.Seek(index * sizeof(long), SeekOrigin.Begin);
          f.Write(rplcBytes, 0, sizeof(long));
      
          // Trim the now-duplicate last number off the end of the file.
          f.SetLength((itemCount - 1) * sizeof(long));
      
          // Get the long and do with it what you want.
          long rtnLong = BitConverter.ToInt64(rtnBytes, 0);
      }
      

      然后您可以将这个long 转换为三个索引,如下所示:

      int indexA = (int)(rtnLong % (long)A.Count);
      rtnLong /= a.Count;
      int indexB = (int)(rtnLong % (long)B.Count);
      rtnLong /= b.Count;
      int indexC = (int)rtnLong;
      

      【讨论】: