使用前面的代码,我成功地设法生成了一组数字并将元素在数组中的位置洗牌:
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如何让我像这样洗牌呢?
答案 0 :(得分:5)
Enumerable.OrderBy如何使用keySelector?
Enumerable.OrderBy<T>
懒惰地返回 - 不直接调用keySelector。结果是IOrderedEnumerable<T>
,它将在枚举时执行排序。
枚举时,为每个元素调用一次keySelector。键的顺序定义了元素的新顺序。
这里有一个漂亮的sample implementation。
那么LINQ如何让我像这样洗牌呢?
var randomNumbers = Enumerable
.Range(0, 100)
.OrderBy(x => Guid.NewGuid());
为每个元素调用Guid.NewGuid。对第二个元素的调用可能会生成一个高于或低于第一个元素调用的值。
randomNumbers
是IOrderedEnumerable<int>
,每次枚举时都会产生不同的顺序。每次枚举randomNumbers
时,每个元素调用一次KeySelector。
答案 1 :(得分: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());
根据生成的Guids对数字进行排序。
现在guids是什么?
它们是以十六进制形式表示的128位整数。由于2 ^ 128是如此之大,因此生成两个Guid的机会非常少/几乎不可能。由于Guids表现出某种随机性,因此排序也是随机的。
两个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());
}
答案 2 :(得分:2)
那么这是如何运作的?
以下查询使用Comparer<Guid>.Default
进行比较。
.OrderBy(x => Guid.NewGuid())
由于每个生成的GUID实际上都是唯一的(因为您在OrderBy
子句中生成),您相信您正在获得随机顺序(这是不正确的理解)。
如果再次运行查询,您将再次看到(可能)洗牌后的结果,因为将生成新的GUID集。
如果您将使用预定义的GUID,您将看到订单。
示例randomNumbers1
和randomNumbers2
在下面具有相同的值。
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
)。如果您使用已订购的元素,您将获得有序输出而不是随机输出。