Как перечислимые.OrderBy использовать keySelector

используя код продолжения, мне удалось создать коллекцию чисел и перетасовать положение элементов в массиве:

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);

насколько я понимаю, я передаю свойство, которое я хочу отсортировать, и я предполагаю, что LINQ будет использовать Comparer<T>.Default определить порядок коллекция, если нет явного IComparer указывается для второй перегрузки. Я действительно не вижу, как любая из этой разумной логики может быть применена для перетасовки массива таким образом. Итак, как LINQ позволяет мне перетасовывать массив?

3 ответов


как перечисли.OrderBy использовать keySelector?

Enumerable.OrderBy<T> лениво возвращает-keySelector не вызывается напрямую. В результате получается IOrderedEnumerable<T> это будет выполнять порядок при перечислении.

при перечислении keySelector вызывается один раз для каждого элемента. Порядок ключей определяет новый порядок элементов.

вот отличный пример реализации.


Итак, как LINQ позвольте мне перетасовать такой массив?

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

Guid.NewGuid вызывается для каждого элемента. Вызов второго элемента может генерировать значение выше или ниже, чем вызов первого элемента.

randomNumbers - это IOrderedEnumerable<int> который производит другой порядок каждый раз, когда он перечисляется. KeySelector вызывается один раз на элемент каждый раз randomNumbers перечисляется.


вы довольно близки к пониманию того, как работает такая перетасовка.. Во втором случае

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

the Comparer<int>.Default используется (лица сортируются по их Age, простой).

в первом случае это.

как это работает?.

каждый раз, когда вы делаете Guid.NewGuid() (предположительно) другой/оригинальный/не дублированный Guid произведен. Теперь, когда вы делаете

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

цифры сортировка на основе сгенерированных GUID.

теперь, что такое guids?

это 128-битные целые числа, представленные в шестнадцатеричной форме. Поскольку 2^128-такое большое число, шансы на создание двух GUID очень редки / почти невозможны. Поскольку GUID демонстрируют некоторую случайность, порядок также будет случайным.

как два GUID сравниваются для обеспечения выполнения заказа?

вы можете подтвердить его основе на тривиальный эксперимент. Do:

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.


в сторону:

вы должны использовать Фишер-Йейтс тасу для скорости. Может быть

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());
}

Итак, как это работает?

следующий запрос использует Comparer<Guid>.Default для сравнения.

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

поскольку каждый сгенерированный GUID практически уникален (как вы генерируете в OrderBy само предложение), вы считаете, что получаете случайный порядок (что является неправильным пониманием).
Если вы снова запустите запрос, вы снова увидите (предположительно) перетасованный результат, поскольку будет создан новый набор GUID.

если вы будете использовать предопределенные GUIDs, вы увидите заказ.

пример 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 в вашем примере). Если вы используете упорядоченные элементы, вы получите упорядоченный вывод вместо перетасованного.