Как правильно перебирать массив в 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|! Разве это не безумие?


person Tom Lehman    schedule 22.11.2008    source источник
comment
Я предполагаю, что array#each_with_index использует |value, key|, потому что имя метода подразумевает порядок, тогда как порядок, используемый для hash#each, имитирует синтаксис hash[key] = value?   -  person Benjineer    schedule 12.05.2014
comment
Если вы только начинаете работать с циклами в Ruby, ознакомьтесь с с помощью выбора, отклонения, сбора, внедрения и обнаружения   -  person Matthew Carriere    schedule 14.01.2015


Ответы (12)


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

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

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

person Robert Gamble    schedule 22.11.2008
comment
Не по теме я не могу найти each_with_index в ruby ​​1.9 в массиве - person CantGetANick; 20.04.2012
comment
@CantGetANick: класс Array включает модуль Enumerable, в котором есть этот метод. - person xuinkrbin.; 10.09.2012

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

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

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

person AShelly    schedule 22.11.2008
comment
отличный ответ! Я хотел бы упомянуть обратное, например #reject, и псевдонимы, например #collect. - person Emily Tui Ch; 10.01.2020

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

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

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

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

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 продолжает искать новые пути.


Теперь я хочу задать другой вопрос: «Каков« правильный »способ перебрать Range в 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 решает эту проблему.

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

person Community    schedule 27.03.2009
comment
1.upto(10) do |i| puts i end и 10.downto(1) do puts i end дают желаемую симметрию. Надеюсь, это поможет. Не уверен, что это сработает для строк и тому подобного. - person Suren; 04.05.2012
comment
(1..10) .to_a.sort {| x, y | y ‹=› x} .each {| i | ставит i = # {i}} - реверс медленнее. - person Davidslv; 17.12.2012
comment
Вы можете [*1..10].each{|i| puts "i=#{i}" }. - person Hauleth; 13.02.2015

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

ary.each_with_index { |val, idx| # ...
person J Cooper    schedule 22.11.2008

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

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

person Pistos    schedule 23.11.2008
comment
хеши и массивы - это одно и то же! массивы сопоставляют целые числа с объектами, а хэши сопоставляют объекты с объектами. массивы - это просто частные случаи хешей, не так ли? - person Tom Lehman; 23.11.2008
comment
Как я уже сказал, массив - это упорядоченный набор. Отображение (в общем смысле) неупорядочено. Если вы ограничите набор ключей целыми числами (например, с массивами), просто так получится, что набор ключей имеет порядок. В общем сопоставлении (хеш-массив / ассоциативный массив) ключи могут не иметь порядка. - person Pistos; 24.11.2008
comment
@Horace: Хеши и массивы - это не одно и то же. Если одно является особым случаем другого, они не могут быть одинаковыми. Но что еще хуже, массивы - это не особый вид хэшей, это всего лишь абстрактный способ их просмотра. Как указывал выше Брент, взаимозаменяемое использование хешей и массивов может указывать на запах кода. - person Zane; 25.03.2014

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

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

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

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

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

    array.length.times do | i |
    
person Jake    schedule 21.08.2015

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

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}"
}
person Brent.Longborough    schedule 22.11.2008

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

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

Использование массива с each_slice выполняет свою работу:

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

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

Так что это просто спасибо за непреднамеренный намек на решение!

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

person Dave Everitt    schedule 15.06.2009

Если вы используете миксин enumerable (как это делает 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]]

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

person maxhawkins    schedule 13.01.2009

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

Пример:

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

производит:

0 -- 1 -- 2 --
person Amjed Shareef    schedule 29.09.2014
comment
Это неправда. Как указано в комментариях к принятому ответу, причина, по которой each_with_index не отображается в документации, заключается в том, что она предоставляется модулем Enumerable. - person ihaztehcodez; 24.01.2015

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

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

person Dariusz G. Jagielski    schedule 09.01.2014

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

Один умный способ, о котором еще не упоминалось, - это то, как это делается в стандартной библиотеке Ruby Facets. расширения. Из здесь:

class Array

  # Iterate over index and value. The intention of this
  # method is to provide polymorphism with Hash.
  #
  def each_pair #:yield:
    each_with_index {|e, i| yield(i,e) }
  end

end

Уже существует Hash#each_pair, псевдоним Hash#each. Итак, после этого патча у нас также есть Array#each_pair, и мы можем использовать его взаимозаменяемо для перебора как хешей, так и массивов. Это устраняет наблюдаемое безумие OP, что Array#each_with_index имеет обратные аргументы блока по сравнению с Hash#each. Пример использования:

my_array = ['Hello', 'World', '!']
my_array.each_pair { |key, value| pp "#{key}, #{value}" }

# result: 
"0, Hello"
"1, World"
"2, !"

my_hash = { '0' => 'Hello', '1' => 'World', '2' => '!' }
my_hash.each_pair { |key, value| pp "#{key}, #{value}" }

# result: 
"0, Hello"
"1, World"
"2, !"
person tanius    schedule 25.04.2020