【问题标题】:An extension method on IEnumerable needed for shuffling [duplicate]洗牌所需的 IEnumerable 上的扩展方法[重复]
【发布时间】:2011-08-14 00:12:41
【问题描述】:

我需要一个扩展方法来随机播放IEnumerable<T>。它还可以使用int 来指定返回的IEnumerable 的大小。更好地保持IEnumerable 的不变性。我目前对IList的解决方案-

public static IList<T> Shuffle<T>(this IList<T> list, int size)
{
    Random rnd = new Random();
    var res = new T[size];

    res[0] = list[0];
    for (int i = 1; i < size; i++)
    {
        int j = rnd.Next(i);
        res[i] = res[j];
        res[j] = list[i];
    }
    return res;
}

public static IList<T> Shuffle<T>(this IList<T> list)
{ return list.Shuffle(list.Count); }

【问题讨论】:

  • 请注意,为了让&lt; &gt; 出现,它们通常必须被格式化为代码,或者内联反引号(正如我添加的那样)或(如你所做的那样)四格缩进

标签: c# ienumerable shuffle


【解决方案1】:

您可以使用Fisher-Yates-Durstenfeld shuffle。无需将大小参数显式传递给方法本身,如果您不需要整个序列,您可以简单地调用Take

var shuffled = originalSequence.Shuffle().Take(5);

// ...

public static class EnumerableExtensions
{
    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(nameof(source));
        if (rng == null) throw new ArgumentNullException(nameof(rng));

        return source.ShuffleIterator(rng);
    }

    private static IEnumerable<T> ShuffleIterator<T>(
        this IEnumerable<T> source, Random rng)
    {
        var 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];
        }
    }
}

【讨论】:

  • 第二种方法的目的是抛出异常吗?
  • 是的,这样参数检查就急切地完成了,而不是被推迟了。如果第二个和第三个方法一起滚动,那么在您开始迭代序列之前不会进行任何参数检查。
  • 卢克,当您在 main 方法中调用 source.ToList() 时,这是否意味着 IEnumerable 已执行(可能如果它是 Linq 可枚举的,那么您正在破坏它们的延迟执行?更好要求 IList!
  • @nawfal:是的,该方法必须缓冲源IEnumerable&lt;&gt; 的内容,以便它可以执行随机播放。然后它懒惰地产生它的输出。我不确定您要求IList 是什么意思,或者这会有什么帮助。
  • @nawfal:许多内置的 LINQ 方法在内部缓冲整个序列,然后懒惰地产生结果:例如,GroupByOrderByOrderByDescendingThenBy、@ 987654331@、Reverse等都需要缓冲它们的源序列; ExceptGroupJoinIntersectJoin 等都缓冲它们的“次要”输入序列。这不是问题,imo,尽管清楚地记录一个方法是否需要在内部缓冲整个序列是个好主意。
【解决方案2】:

对 LINQ 的喜爱:

public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> list, int size)
{
    var r = new Random();
    var shuffledList = 
        list.
            Select(x => new { Number = r.Next(), Item = x }).
            OrderBy(x => x.Number).
            Select(x => x.Item).
            Take(size); // Assume first @size items is fine

    return shuffledList.ToList();
}

【讨论】:

  • 不是和OrderBy(x =&gt; r.Next())一样吗?
  • @Gulshan:是的。按随机数排序不被认为是一个很好的洗牌算法。它有效,但并不像它可能的那样随机。
  • 不,不一样。 OrderBy(x =&gt; r.Next()) 可能会将排序放入无限循环。这不行。
  • @Jim:OrderBy 方法实际上在内部做了类似的事情——使用投影为每个项目一次生成一个键,存储它,然后使用存储的排序的关键——因此当前的 MS 实现不存在无限循环的危险。 (虽然不能保证在不同的平台/版本上实现是相同的。)
  • @LukeH:你能给我一个指向更多信息的指针吗?你所说的对我来说毫无意义,尤其是在比较函数的上下文中(这就是 r.Next 在这里使用的内容)。我错过了什么?
【解决方案3】:

Anton 有这个想法,但你可以把它做成两行:

public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> enumerable)
{
    var r = new Random();
    return enumerable.OrderBy(x=>r.Next()).ToList();
}

不幸的是,它不能被延迟评估,因为r 在执行时将超出范围。您可以创建一个封装此代码并返回该代码的 IEnumerable 实现,但这会变得更加复杂。

【讨论】:

  • 听说这有点偏见。
  • @Gulshan:它不应该糟糕,尽管OrderBy 是一个稳定的类型可能会导致问题(Random 本身可能有一些固有的偏见)。使用OrderBy 的主要潜在缺点是它具有 O(n lg n) 时间复杂度;可以在 O(n) 中执行随机播放,并且没有偏差。
  • @Jim:OrderBy 的当前 CLR 实现只对每个元素执行一次投影,存储键,然后使用存储的键进行排序,因此目前没有无限循环的危险。 (当然,这依赖于理论上可以改变的实现细节。)
  • 这个“r 将超出范围”是怎么回事?变量捕获呢?在此代码 sn-p 中省略 .ToList() 应该不是问题...
  • -1 这是一种托管语言,r 分配在堆上并且永远不会“超出范围”,GC 将确保 r 不会被垃圾收集,直到它没有引用时间更长。
猜你喜欢
  • 2019-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-16
  • 2011-06-02
  • 2011-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多