Каков "правильный" способ перебора массива в Ruby?

PHP, для всех его бородавок, довольно хорош в этом отношении. Нет никакой разницы между массивом и хешем (может быть, я наивен, но это кажется мне совершенно правдоподобным), и итерации через либо вы просто делаете

foreach (array/hash as $key => $value)

В Ruby существует множество способов сделать такие вещи:

array.length.times do |i|
end

array.each

array.each_index

for i in array

Хеши имеют больше смысла, так как я всегда использую

hash.each do |key, value|

Почему я не могу сделать это для массивов? Если я хочу вспомнить только один метод, я предполагаю, что могу использовать each_index (поскольку он делает доступным как индекс, так и значение), но это раздражает необходимость делать array[index] вместо просто value.


О, хорошо, я забыл о array.each_with_index. Однако это отстой, потому что он идет |value, key| и hash.each идет |key, value|! Разве это не безумие?

Ответ 1

Это приведет к повторению всех элементов:

array = [1, 2, 3, 4, 5, 6]
array.each { |x| puts x }

Печать

1
2
3
4
5
6

Это приведет к повторению всех элементов, дающих вам значение и индекс:

array = ["A", "B", "C"]
array.each_with_index {|val, index| puts "#{val} => #{index}" }

Печать

A => 0
B => 1
C => 2

Я не совсем уверен в вашем вопросе, который вы ищете.

Ответ 2

Я думаю, что нет ни одного правильного пути. Существует много разных способов повторения и каждая из них имеет свою нишу.

  • each достаточно для многих обычаев, так как я не часто забочусь об индексах.
  • each_ with _index действует как Hash # each - вы получаете значение и индекс.
  • each_index - только индексы. Я не использую эту часто. Эквивалент "length.times".
  • map - это еще один способ итерации, полезный, когда вы хотите преобразовать один массив в другой.
  • select - это итератор для использования, когда вы хотите выбрать подмножество.
  • inject полезен для генерации сумм или продуктов или для сбора единственного результата.

Может показаться, что много помнить, но не беспокойтесь, вы можете обойтись, не зная их всех. Но когда вы начнете изучать и использовать разные методы, ваш код станет более чистым и понятным, и вы будете на пути к мастерству Ruby.

Ответ 3

Я не говорю, что Array|value,index| и Hash|key,value| не безумный (см. комментарий Horace Loeb), но я говорю, что есть разумный способ ожидать этого соглашения.

Когда я имею дело с массивами, я сосредоточен на элементах в массиве (а не на индексе, потому что индекс является временным). Каждый метод имеет индекс, то есть каждый + индекс, или | каждый, индекс | или |value,index|. Это также согласуется с тем, что индекс рассматривается как необязательный аргумент, например. | Значение | эквивалентно | value, index = nil | который согласуется с | значением, индексом |.

Когда я имею дело с хэшами, я часто больше сфокусирован на ключах, чем на значениях, и обычно я имею дело с ключами и значениями в этом порядке: key => value или hash[key] = value.

Если вам нужна утка-типизация, то либо явно используйте определенный метод, как показал Брент Лонгборо, или неявный метод, как показал maxhawkins.

Ruby - это все, что касается языка, подходящего для программиста, а не для программиста, подходящего для языка. Вот почему существует так много способов. Есть много способов думать о чем-то. В Ruby вы выбираете ближайший, а остальная часть кода обычно выпадает очень аккуратно и лаконично.

Что касается исходного вопроса: "Каков" правильный "способ итерации через массив в Ruby?", ну, я думаю, что основной способ (т.е. без мощного синтаксического сахара или объектно-ориентированной мощности) состоит в том, чтобы сделать:

for index in 0 ... array.size
  puts "array[#{index}] = #{array[index].inspect}"
end

Но Ruby - это мощный синтаксический сахар и объектно-ориентированная мощь, но в любом случае здесь эквивалент хэшей, и ключи можно заказать или нет:

for key in hash.keys.sort
  puts "hash[#{key.inspect}] = #{hash[key].inspect}"
end

Итак, мой ответ: "Правильный" способ итерации по массиву в Ruby зависит от вас (т.е. программиста или команды разработчиков) и от проекта ". Лучший программист Ruby делает лучший выбор (из которого синтаксическая сила и/или объектно-ориентированный подход). Лучший программист Ruby продолжает искать больше способов.


Теперь, я хочу задать еще один вопрос: "Каков" правильный "способ повторения диапазона в Ruby назад?"! (Этот вопрос, как я пришел на эту страницу.)

Хорошо (для форвардов):

(1..10).each{|i| puts "i=#{i}" }

но я не люблю делать (для назад):

(1..10).to_a.reverse.each{|i| puts "i=#{i}" }

Ну, я на самом деле не против этого слишком много, но когда я учусь назад, я хочу показать своим ученикам симпатичную симметрию (т.е. с минимальной разницей, например, только добавление реверса или шаг -1, но без изменения чего-либо еще). Вы можете сделать (для симметрии):

(a=*1..10).each{|i| puts "i=#{i}" }

и

(a=*1..10).reverse.each{|i| puts "i=#{i}" }

который мне не очень нравится, но вы не можете сделать

(*1..10).each{|i| puts "i=#{i}" }
(*1..10).reverse.each{|i| puts "i=#{i}" }
#
(1..10).step(1){|i| puts "i=#{i}" }
(1..10).step(-1){|i| puts "i=#{i}" }
#
(1..10).each{|i| puts "i=#{i}" }
(10..1).each{|i| puts "i=#{i}" }   # I don't want this though.  It dangerous

В конечном итоге вы можете сделать

class Range

  def each_reverse(&block)
    self.to_a.reverse.each(&block)
  end

end

но я хочу преподавать чистый Ruby, а не объектно-ориентированные подходы (пока). Я хотел бы повторить итерацию:

  • без создания массива (рассмотрим 0..1000000000)
  • работает для любого диапазона (например, строки, а не только целые)
  • без использования дополнительной объектно-ориентированной мощности (т.е. модификации класса)

Я считаю, что это невозможно без определения метода pred, что означает изменение класса Range для его использования. Если вы можете это сделать, сообщите мне, иначе подтверждение невозможности будет оценено, хотя это будет неутешительно. Возможно, Ruby 1.9 обращается к этому.

(Спасибо за ваше время, прочитав это.)

Ответ 4

Используйте each_with_index, когда вам нужны оба.

ary.each_with_index { |val, idx| # ...

Ответ 5

Другие ответы просто прекрасны, но я хотел указать еще одну периферийную вещь: массивы упорядочены, а хэши - не в 1.8. (В Ruby 1.9 хеши упорядочиваются по порядку вставки ключей.) Таким образом, не было бы смысла до 1.9 перебирать хеш так же, как и массивы, которые всегда имели определенный порядок. Я не знаю, какой порядок по умолчанию для ассоциативных массивов PHP (по-видимому, мой google fu недостаточно силен, чтобы понять это), но я не знаю, как вы можете рассматривать регулярные массивы PHP и ассоциативные массивы PHP для быть "тем же" в этом контексте, так как порядок ассоциативных массивов кажется undefined.

Таким образом, путь Ruby кажется мне более понятным и интуитивным.:)

Ответ 6

Попытка сделать то же самое последовательно с массивами и хешами может быть просто запахом кода, но, рискуя быть заклейменным как кодовое наполовину обезьян-патчер, если вы ищете последовательное поведение, сделать трюк?:

class Hash
    def each_pairwise
        self.each { | x, y |
            yield [x, y]
        }
    end
end

class Array
    def each_pairwise
        self.each_with_index { | x, y |
            yield [y, x]
        }
    end
end

["a","b","c"].each_pairwise { |x,y|
    puts "#{x} => #{y}"
}

{"a" => "Aardvark","b" => "Bogle","c" => "Catastrophe"}.each_pairwise { |x,y|
    puts "#{x} => #{y}"
}

Ответ 7

Я пытался создать меню (в Camping и Markaby), используя хэш.

Каждый элемент имеет 2 элемента: ярлык и URL, поэтому хэш кажется правильным, но URL '/' для 'Home' всегда был последним ( как вы ожидали бы для хэша), поэтому пункты меню появились в неправильном порядке.

Использование массива с each_slice выполняет задание:

['Home', '/', 'Page two', 'two', 'Test', 'test'].each_slice(2) do|label,link|
   li {a label, :href => link}
end

Добавление дополнительных значений для каждого элемента меню (например, как имя идентификатора CSS) означает только увеличение значения среза. Итак, как хэш, но с группами, состоящими из любого количества элементов. Совершенная.

Итак, это просто сказать спасибо за непреднамеренное намек на решение!

Очевидно, но стоит сказать: я предлагаю проверить, делится ли длина массива на значение среза.

Ответ 8

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

  • Просто пройдите значения:

    array.each
    
  • Просто пройдите по индексам:

    array.each_index
    
  • Перейдите по индексам + индексная переменная:

    for i in array
    
  • Контрольное число циклов + индексная переменная:

    array.length.times do | i |
    

Ответ 9

Если вы используете enumerable mixin (как это делает Rails), вы можете сделать что-то подобное приведенному фрагменту php. Просто используйте метод each_slice и сгладьте хэш.

require 'enumerator' 

['a',1,'b',2].to_a.flatten.each_slice(2) {|x,y| puts "#{x} => #{y}" }

# is equivalent to...

{'a'=>1,'b'=>2}.to_a.flatten.each_slice(2) {|x,y| puts "#{x} => #{y}" }

Требуется меньше патчей для обезьян.

Однако это вызывает проблемы, когда у вас есть рекурсивный массив или хэш с значениями массива. В ruby ​​1.9 эта проблема решается с помощью параметра метода сглаживания, который указывает, насколько глубоко рекурсивно.

# Ruby 1.8
[1,2,[1,2,3]].flatten
=> [1,2,1,2,3]

# Ruby 1.9
[1,2,[1,2,3]].flatten(0)
=> [1,2,[1,2,3]]

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

Ответ 10

Правильный путь - это тот, с которым вы чувствуете себя наиболее комфортно, и который делает то, что вы хотите. В программировании редко бывает "правильный" способ делать что-то, чаще всего есть несколько способов выбора.

Если вам комфортно с определенными делами вещей, делайте именно это, если это не сработает - тогда пришло время найти лучший способ.

Ответ 11

В Ruby 2.1 метод each_with_index удаляется. Вместо этого вы можете использовать each_index

Пример:

a = [ "a", "b", "c" ]
a.each_index {|x| print x, " -- " }

дает:

0 -- 1 -- 2 --