Каков «правильный» способ перебора массива в Ruby? - PullRequest
315 голосов
/ 22 ноября 2008

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|! Разве это не безумие?

Ответы [ 11 ]

519 голосов
/ 22 ноября 2008

Это будет повторять все элементы:

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

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

75 голосов
/ 22 ноября 2008

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

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

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

56 голосов
/ 28 марта 2009

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

Когда я имею дело с массивами, я фокусируюсь на элементах массива (не на индексе, потому что индекс является временным). Каждый метод имеет индекс, то есть каждый + индекс или | каждый, индекс | или |value,index|. Это также согласуется с тем, что индекс рассматривается как необязательный аргумент, например, | Значение | эквивалентно значению | 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's dangerous

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

class Range

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

end

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

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

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

(Спасибо, что уделили мне время на чтение.)

17 голосов
/ 22 ноября 2008

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

ary.each_with_index { |val, idx| # ...
12 голосов
/ 23 ноября 2008

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

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

7 голосов
/ 22 ноября 2008

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

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}"
}
5 голосов
/ 21 августа 2015

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

  1. Просто просмотрите значения:

    array.each
    
  2. Просто пройдите индексы:

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

    for i in array
    
  4. Счетчик цикла управления + индексная переменная:

    array.length.times do | i |
    
5 голосов
/ 15 июня 2009

Я пытался построить меню (в 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 ID name) просто означает увеличение значения среза. Таким образом, как хеш, но с группами, состоящими из любого количества элементов. Совершенная.

Так что я просто хочу поблагодарить вас за непреднамеренный намек на решение!

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

3 голосов
/ 13 января 2009

Если вы используете перечислимый миксин * (как делает 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 эта проблема решается с помощью параметра для метода flatten, который указывает, насколько глубокой должна быть рекурсия.

# 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]]

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

2 голосов
/ 29 сентября 2014

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

Пример:

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

производит:

0 -- 1 -- 2 --
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...