我们被允许订购(随机)吗?

时间:2015-09-28 10:09:54

标签: c# .net random shuffle

我有时使用OrderBy (item => R.NextDouble())对列表或数组进行随机播放,其中Random R在其他位置初始化。

现在这显然是一个黑客,虽然在这个网站上到处推荐的那个,并且它在实践中确实很好用。

然而,还有人指出,这使得假设排序算法不会因改变单个项目的值而变得混淆。进入一个无限循环。

我的问题是,是否存在某种隐含或明确的保证,这种情况不会发生。我在MSDN中找不到任何关于它的信息。

虽然有非常好的,专门的O(n)算法用于此目的,但我并不总是想在小方项目中搜索和复制它们(我们不是在讨论prod)。他们显然是正确的解决方案。

请注意,性能根本不是问题。此外,不需要良好或安全的随机性(在这种情况下必须使用加密库) - 这只是稍微改组一些东西

3 个答案:

答案 0 :(得分:4)

根据文档,这不能保证有效。

我偷看了内部。排序算法首先将所有排序键捕获到临时数组中,然后再从不调用它们。所以在当前的实现中这是安全的。新的.NET版本甚至补丁都可以使这种技术无效。

你可能会争辩说,出于兼容性原因,这种情况永远不会改变,而真正可能的情况就像95%(来源:我)。微软有很高的compat标准。

另一个问题是,如果为两个键生成相同的值,它们将不会随机定位。相反,它们将基于排序算法的内部定位,这可能是确定性的。例如,他们可能永远不会切换他们的相对顺序。

我只会依赖短期投掷项目。

这种精神的另一个黑客是按Guid.NewGuid()排序。这为您节省了另一行代码并且没有播种问题。另一方面,guid可能有一个模式,甚至可能是顺序的。也很成问题。

在生产代码的代码审查中,我会失败。

答案 1 :(得分:2)

不能保证继续正常工作。

它不太可能永远停止工作,因为这种改变需要改变关键值的预先计算,这在许多情况下会非常昂贵。因此,打破它的变化不太可能发生。从调用选择器的频率来看,它也是其他方式的可观察变化。这种可观察到的变化并没有被排除(我已经使用了.NET的PR,减少了接受其他linq方法调用选择器的频率)但是这种变化必须具有非常强大的好处,特别是因为它会增加而不是减少通话次数。

它当前不能很好地工作,因为Enumerable.OrderBy(与linq中的其他OrderBy方法不同,例如Queryable或PLinq中的方法)保证提供稳定的排序(等值始终按原始顺序排列)会给你一个偏见。

对于快速编写随机排序非常有用,其中随机播放的质量只需要非常好。对于更强大的随机播放,请使用以下内容:

public static class Shuffler
{
  public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, int seed)
  {
    return new ShuffledEnumerable<T>(source, seed);
  }

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

  private class ShuffledEnumerable<T> : IEnumerable<T>
  {
    private IEnumerable<T> _source;
    private int? _seed;

    public ShuffledEnumerable(IEnumerable<T> source)
    {
      _source = source;
    }

    public ShuffledEnumerable(IEnumerable<T> source, int seed)
      : this(source)
    {
      _seed = seed;
    }

    public IEnumerator<T> GetEnumerator()
    {
      Random rnd = _seed.HasValue ? new Random(_seed.GetValueOrDefault()) : new Random();
      T[] array = _source.ToArray();
      int count = array.Length;
      for (int i = array.Length - 1; i > 0; --i)
      {
        int j = rnd.Next(0, i + 1);
        if (i != j)
        {
          T swapped = array[i];
          array[i] = array[j];
          array[j] = swapped;
        }
      }
      return ((IEnumerable<T>)array).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
      return GetEnumerator();
    }
  }
}

如果您需要通过将Random替换为合适的加密PRNG来获得随机性的加密质量,则可以再次进一步改进。

答案 2 :(得分:2)

虽然它有效并且很可能不会改变,但这并不能保证。但如果您希望100%确定,您可以通过使用显式投影来保证自己

enumerable.Select(item => new { Source = item, Order = R.NextDouble() }.OrderBy(item => item.Order).Select(item.Source)

这当然更加冗长,但仍然很容易实现快速而肮脏的方法。