Как работает Ruby sort_by {rand}?
Я думаю, что это отличный однострочный Ruby:
someArray.sort_by {rand}
Это краткий, читаемый, и он работает, но я не совсем понимаю, как это сделать. Вот что я знаю:
-
rand
оценивает число от 0 до 1 (например, 0.783468632804653)
-
rand
неоднократно оценивается в коде выше, потому что назначение его x
сначала прерывает случайный сортировку
-
sort_by {0.783468632804653}
или любое другое число, которое я пробовал, не влияет на массив
ruby-doc.org не помог мне в этом случае.
Может кто-нибудь объяснить это шаг за шагом?
Update
Я использую Ruby дольше, и я вижу, что мне не хватало концепции или двух здесь. Главное, что:
-
rand
- метод (определенный на ядре); он генерирует случайное число
-
{rand}
- это блок, который sort_by
сохраняет, называя его каждый раз, он хочет сравнить два элемента в коллекции. Если коллекция представляет собой группу объектов, представляющих страны, она должна иметь возможность захватить два из них и определить, какой из них будет первым. Вы сначала ставите имя с самым длинным именем? Тот, у кого самая большая масса суши? Блок должен ответить на этот вопрос, вернув значение, которое говорит "вы спросили об Испании против Камеруна, и я говорю, что Камерун на первом месте". (Вы можете сделать это с помощью {|country| country.name.length}
Остальная часть работы sort_by
объясняется в документации. Я все еще не совсем уверен, почему возвращение случайного числа работает вообще - предположительно sort_by
округляет его до -1, 0 или 1, в зависимости от того, что ближе? Но в любом случае получение различного случайного числа каждый раз, когда вы вызываете блок, сильно отличается от получения того же числа каждый раз. Когда sort_by
говорит, "какая из этих двух стран на первом месте?", {rand}
ставит повязку, поворачивается примерно в 10 раз, указывает и говорит "тот!".:)
Ответы
Ответ 1
В Ruby 1.8/1.9 обе версии sort
и sort_by
реализованы в C, это является грубым эквивалентом того, как это работает:
Скажите, что вы начинаете с [1,2,3,4]
и вызываете sort_by{rand}
:
-
(Я придумал некоторые случайные числа):
Создается массив кортежей: [[0.12232, 1],[0.53434, 2],[0.333, 3],[0.99, 4]]
В примерно эквивалентном Ruby-коде это: [1,2,3,4].map{|x| [rand, x]}
-
Быстрая сортировка Ruby выполняется на массиве, основанном на первом элементе: (обратите внимание, что внутренняя реализация далека от тривиальной и содержит тонну оптимизаций для уже упорядоченных массивов и т.д.)
[[0.12232, 1],[0.333, 3],[0.53434, 2],[0.99, 4]]
В грубом Ruby этот шаг: ary.sort{|x,y| x[0] <=> y[0]}
-
Указатели копируются из нового отсортированного массива в правильное положение в исходном массиве.
[1,3,2,4]
В грубом Ruby этот шаг: ary.map{|x,y| y}
Этот метод иногда называют " Schwartzian Transform". Кэширование означает, что дорогостоящая операция выполняется не более N раз. Это очень эффективный способ рандомизации массива.
Примечание: array.shuffle!
будет наиболее эффективным встроенным способом перетасовки массива (на месте), поскольку использует современную версию Fisher-Yates:
static VALUE
rb_ary_shuffle_bang(VALUE ary)
{
long i = RARRAY_LEN(ary);
rb_ary_modify(ary);
while (i) {
long j = rb_genrand_real()*i;
VALUE tmp = RARRAY_PTR(ary)[--i];
RARRAY_PTR(ary)[i] = RARRAY_PTR(ary)[j];
RARRAY_PTR(ary)[j] = tmp;
}
return ary;
}
Ответ 2
В блоке rand
появляется ключ, который используется при сортировке. Он отличается каждый раз, когда он оценивается, поэтому вы получаете случайный порядок.
Когда вы помещаете там один номер, он один и тот же, поэтому порядок не изменяется. Это означает, что алгоритм сортировки является "стабильным" - он не перемещает записи в порядке.
И вот какой-то еще более короткий, еще более четкий код:
someArray.shuffle
Ответ 3
sort_by
является уточнением sort
, которое используется так:
people.sort do |person1, person2|
person1 <=> person2
end
Функция sort
дает блок, когда ему нужно знать порядок двух вещей, в данном случае людей. Блок возвращает -1, если левая вещь меньше, чем правильная, 0, если они равны, и 1, если правильная вещь больше левой. Оператор космического корабля <=>
обладает прекрасным свойством, что он возвращает -1, 0 или +1, что именно нужно.
Я не смотрел, но хорошо, что Ruby использует алгоритм quicksort.
Какой-то умный человек заметил, что мы продолжаем делать то же самое с левой стороны оператора космического корабля, как и на правой стороне, и придумали sort_by
, используя так:
people.sort_by do |person|
person.name
end
Вместо алгоритма сортировки, дающего два объекта блоку и позволяющего блоку сравнивать их, алгоритм дает единый объект блоку. Затем блок возвращает любой атрибут или значение, которое должно использоваться для сортировки. Ruby запоминает значение, возвращаемое блоком для каждого элемента, и сравнивая эти значения, знает, в каком порядке вложить вещи. Это аккуратно, что вам больше не нужно повторять.
Ваш код в случайном порядке работает, просто "создавая материал", когда алгоритм сортировки выдает блок. Вместо того, чтобы возвращать что-то разумное, блок дает случайное значение. Это заставляет алгоритм сортировки сортировать вещи, ну, случайным образом.
Ответ 4
Что sort_by
можно разбить на два простых шага:
-
Он вызывает метод map
/collect
на предоставленном массиве и с предоставленным блоком. В вашем случае результатом этого будет всего лишь массив случайных чисел - позвольте этому промежуточному массиву A1. Обратите внимание, что он имеет длину исходного массива.
-
A1 сортируется нормально, но возвращаемое - это не отсортированный A1, а исходный массив, в котором элементы перемещаются так же, как соответствующие из A1, а при сортировке!
Как работает следующий пример:
["Paulo", "Sergito", "Nick"].sort_by {|word| word.length}
Он сортирует слова по их длине, потому что сначала массив слов отображается в массив длин, и затем эти длины сортируются, а слова в исходном массиве перемещаются соответственно.