Получив количество групп, скажем
ngroups = 3
код предложит пользователю ввести серию имен, а затем "stop"
, когда у пользователя больше нет имен для ввода. Предположим, что пользователь вводит следующие строки в указанном порядке:
"Lucy\n", "Hank\n", "Billy-Bob\n", "Trixie\n", "Veronica\n", "Roxy\n",
"Norm\n", "Herb\n", "stop\n"
Мы можем создать нужный массив с помощью следующего кода:
0.step.with_object(Array.new(ngroups) { [] }) do |i,a|
print 'Please enter a name or "stop" if finished: '
reply = gets.chomp
break a if reply == "stop"
a[i % ngroups] << reply
end
#=> [["Lucy", "Trixie", "Norm"], ["Hank", "Veronica", "Herb"],
# ["Billy-Bob", "Roxy"]]
Обратите внимание, что я не просил пользователя вводить группу для каждого имени. Это потому, что вы сказали, что имена должны присваиваться группам в порядке групп, возобновляясь с первой группой после каждого присвоения имени последней группе.
Для любого новичка в Ruby этот код должен выглядеть довольно сложным. Это, однако, очень похоже на Ruby, и любой Rubiest поймет это сразу после того, как получит некоторый опыт работы с языком. Позвольте мне сломать шаги. С терпением вы сможете понять, что происходит.
Числовой # шаг , перечислитель
enum0 = 0.step #=> (0.step)
Если вы изучите документацию для этого метода, вы увидите, что 0.step
без блока возвращает перечислитель, который является экземпляром класса Перечислитель . Перечислители очень важны в Ruby. Они генерируют значения, которые могут быть переданы другим методам или заблокированы. Здесь enum0
генерирует следующую последовательность:
enum0.next #=> 0
enum0.next #=> 1
enum0.next #=> 2
... до бесконечности . См. Перечислитель # следующий .
* +1034 * Аналогично,
e = 21.step(by: -3) #=> (21.step(by: -3))
e.next #=> 21
e.next #=> 18
e = 2.34.step #=> (2.34.step)
e.next #=> 2.34
e.next #=> 3.34
Enumerator # with_object
На самом деле нам не нужно использовать этот метод. Вместо этого мы могли бы написать следующее, что дает тот же результат:
a = []
0.step do |i|
print 'Please enter a name or "stop" if finished: '
reply = gets.chomp
break a if reply == "stop"
a[i % ngroups] << reply
end
a
Как видите, использование each_object
просто сохраняет две строки кода: a = []
и a
в конце. 1 . Этот метод и его двоюродный брат Enumerable # each_with_object , которые широко используются программистами Ruby, на самом деле довольно просты.
Другой перечислитель
Следующее у нас есть:
enum1 = enum0.with_object(Array.new(ngroups) { [] })
#=> #<Enumerator: (0.step):with_object([[], [], []])>
Как видно из возвращаемого значения для enum1
, enum1
можно рассматривать как составной перечислитель , хотя в Ruby такой формальной концепции нет. Давайте посмотрим, какие значения генерирует этот перечислитель:
enum1.next #=> [0, [[], [], []]]
enum1.next #=> [1, [[], [], []]]
enum1.next #=> [2, [[], [], []]]
Вы видите, что каждое сгенерированное значение представляет собой массив из двух элементов: первый счетчик начинается с нуля, а второй - массив из трех групп. Группы теперь пусты, но они будут заполнены по мере выполнения расчетов.
Если бы мы хотели, мы могли бы теперь написать исходный код следующим образом 1
enum1.each do |i,a|
print 'Please enter a name or "stop" if finished: '
reply = gets.chomp
break a if reply == "stop"
a[i % ngroups] << reply
end
См. Перечислитель # каждый .
Передача элементов, сгенерированных enum1
в блок
Когда первый элемент генерируется и передается в блок, переменным блока i
и a
присваиваются следующие значения:
i, a = enum1.next #=> [0, [[], [], []]]
i #=> 0
a #=> [[], [], []]
Процесс разбиения массива, возвращаемого enum1.next
, на его компоненты называется декомпозиция массива . Это мощный и ценный инструмент.
Теперь мы можем выполнять операции блока.
print 'Please enter a name or "stop" if finished: '
reply = "Lucy" # = gets.chomp
break a if reply == "stop" # do not break
a[i % ngroups] << reply
#=> a[0 % 3] << "Lucy"
#=> a[0] << "Lucy"
#=> ["Lucy"]
Далее enum1
генерирует его второе значение и передает его в блок, значения присваиваются переменным блока и выполняются операции блока.
i, a = enum1.next #=> [1, [["Lucy"], [], []]]
i #=> 1
a #=> [["Lucy"], [], []]
Обратите внимание, что a
был обновлен для отображения "Lucy"
, добавленного к a[0]
. Продолжая,
print 'Please enter a name or "stop" if finished: '
reply = "Hank" # = gets.chomp
break a if reply == "stop" # do not break
a[i % ngroups] << reply
#=> a[1 % 3] << "Hank"
#=> a[1] << "Hank"
#=> ["Hank"]
Теперь
a #=> => [["Lucy"], ["Hank"], []]
Остальные расчеты аналогичны.
1. Перед запуском следующего мне нужно выполнить enum1.rewind
для повторной инициализации перечислителя. См. Перечислитель # перемотка .