8

使用前面的代码,我成功地生成了一个数字集合并打乱了元素在数组中的位置:

var randomNumbers = Enumerable.Range(0, 100)
                    .OrderBy(x => Guid.NewGuid());

一切功能都很好,但是当我试图理解Enumerable.OrderBy. 以下面的代码为例:

var pupils = new[] 
{ 
    new Person() { Name = "Alex", Age = 17 },
    new Person() { Name = "Jack", Age = 21 } 
};

var query = pupils.OrderBy(x => x.Age);

我的理解是,我传递了我希望排序的属性,并且我假设如果没有为第二个重载指定Comparer<T>.Default显式,则 LINQ 将用于确定如何对集合进行排序。IComparer我真的看不到如何应用任何这种合理的逻辑来以这种方式对数组进行洗牌。那么 LINQ 是如何让我像这样对数组进行洗牌的呢?

4

3 回答 3

7

Enumerable.OrderBy 如何使用 keySelector?

Enumerable.OrderBy<T>懒惰地返回 - 不直接调用 keySelector。结果是IOrderedEnumerable<T>在枚举时将执行排序。

枚举时,对每个元素调用一次 keySelector。键的顺序定义了元素的新顺序。

这是一个漂亮的示例实现


那么 LINQ 是如何让我像这样对数组进行洗牌的呢?

var randomNumbers = Enumerable
  .Range(0, 100)
  .OrderBy(x => Guid.NewGuid());

为每个元素调用 Guid.NewGuid。对第二个元素的调用可能会生成一个高于或低于对第一个元素的调用的值。

randomNumbersIOrderedEnumerable<int>每次枚举时都会产生不同的顺序。每次randomNumbers枚举时,每个元素都会调用一次 KeySelector。

于 2013-01-02T17:55:58.997 回答
4

你非常接近理解这种洗牌是如何工作的。在你的第二种情况下

pupils.OrderBy(x => x.Age);

Comparer<int>.Default使用(这些人是按他们的Age,简单的)。

在您的第一种情况下,Comparer<Guid>.Default使用。

现在它是如何工作的?.

每次你这样做Guid.NewGuid()(大概)都会产生不同的/原始的/非重复Guid的。现在当你做

var randomNumbers = Enumerable.Range(0, 100).OrderBy(x => Guid.NewGuid());

这些数字根据生成的 Guid 进行排序。

现在什么是指南

它们是以十六进制形式表示的 128 位整数。由于 2^128 是如此之大,因此生成两个 Guid 的机会非常罕见/几乎不可能。由于 Guid 表现出某种随机性,因此排序也是随机的。

如何比较两个 Guid 以强制执行排序?

您可以根据一个简单的实验来确认它。做:

var guids = Enumerable.Range(0, 10).Select((x, i) => 
    {
        Guid guid = Guid.NewGuid();
        return new { Guid = guid, NumberRepresentation = new BigInteger(guid.ToByteArray()), OriginalIndex = i };
    }).ToArray();

var guidsOrderedByTheirNumberRepresentation = guids.OrderBy(x => x.NumberRepresentation).ToArray();
var guidsOrderedAsString = guids.OrderBy(x => x.Guid.ToString()).ToArray();

var randomNumbers = Enumerable.Range(0, 10).OrderBy(x => guids[x].Guid).ToArray();

//print randomNumbers.SequenceEqual(guidsOrderedByTheirNumberRepresentation.Select(x => x.OriginalIndex)) => false

//print randomNumbers.SequenceEqual(guidsOrderedAsString.Select(x => x.OriginalIndex)) => true

所以Comparer<Guid>.Default是基于guid的字符串表示。


在旁边:

您应该使用Fisher-Yates改组来提高速度。或许

public static IEnumerable<T> Shuffle<T>(this IList<T> lst)
{
    Random rnd = new Random();
    for (int i = lst.Count - 1; i >= 0; i--)
    {
        int j = rnd.Next(i + 1);
        yield return lst[j];
        lst[j] = lst[i];
    }
}

或者为了简洁起见,可能只是(这仍然比 Guid 方法更快)

public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> lst)
{
    Random rnd = new Random();
    return lst.OrderBy(x => rnd.Next());
}
于 2013-01-02T10:06:03.880 回答
2

那么这是如何工作的呢?

以下查询Comparer<Guid>.Default用于比较。

  .OrderBy(x => Guid.NewGuid())

由于每个生成的 GUID 实际上都是唯一的(正如您在OrderBy子句本身中生成的那样),您相信您得到的是随机顺序(这是不正确的理解)。
如果再次运行查询,您将再次看到(可能)洗牌结果,因为将生成新的 GUID 集。

如果您将使用预定义的 GUID,您将看到顺序。

示例randomNumbers1randomNumbers2在下面具有相同的值。

var randomGuids = Enumerable.Range(0,10).Select (x => Guid.NewGuid()).ToArray();

var randomNumbers1 = Enumerable.Range(0, 10).OrderBy(x => randomGuids[x]);

var randomNumbers2 = Enumerable.Range(0, 10).OrderBy(x => randomGuids[x]);

我真的看不到如何应用任何这种合理的逻辑来以这种方式对数组进行洗牌。

您可以随机播放,因为元素之间没有顺序(GUID在您的示例中)。如果您使用已排序的元素,您将获得有序输出而不是随机输出。

于 2013-01-02T08:24:20.790 回答