it-swarm.com.ru

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

315
Tom Lehman

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

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

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

521
Robert Gamble

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

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

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

75
AShelly

Я не говорю, что 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 решает эту проблему.

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

56
Luis Esteban

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

ary.each_with_index { |val, idx| # ...
17
J Cooper

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

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

12
Pistos

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

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
Brent.Longborough

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

Каждый элемент имеет 2 элемента: a метка меню и a 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) просто означает увеличение значения среза. Таким образом, как хеш, но с группами, состоящими из любого количества элементов. Отлично.

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

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

5
Dave Everitt

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

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

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

    array.each_index
    
  3. Пройдите через индексы + переменную индекса:

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

    array.length.times do | i |
    
5
Jake

Если вы используете 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 эта проблема решается с помощью параметра в методе 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]]

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

3
maxhawkins

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

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

2
Dariusz G. Jagielski

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

Пример:

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

производит:

0 -- 1 -- 2 --
2
Amjed Shareef