Який "правильний" спосіб перейти через масив у Ruby?


341

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

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

У Рубі існує маса способів зробити такі речі:

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#each_with_indexвикористовує, |value, key|тому що назва методу передбачає порядок, тоді як порядок, який використовується для hash#eachімітації hash[key] = valueсинтаксису?
Бенджінер

3
Якщо ви тільки починаєте цикл у Ruby, тоді перевірте, використовуючи виділити, відхилити, збирати, вводити та виявляти
Matthew Carriere

Відповіді:


560

Це повторить усі елементи:

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

Я не зовсім впевнений у вашому запитанні, якого саме ви шукаєте.


1
Поза темою Я не можу знайти every_with_index в рубіні 1.9 в масиві
CantGetANick

19
@CantGetANick: Клас Array включає в себе модуль Enumerable, який має цей метод.
ксінкрбін.

88

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

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

Це може здатися багато що запам'ятати, але не хвилюйтеся, ви можете обійтись, не знаючи їх усіх. Але як ви почнете вивчати та використовувати різні методи, ваш код стане чистішим і зрозумілішим, і ви будете на шляху до майстерності Рубі.


чудова відповідь! Я хотів би згадати зворотний на зразок #reject та псевдоніми, наприклад, #collect.
Емілі Туй Ч

60

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

Коли я маю справу з масивами, я зосереджений на елементах масиву (а не на індексі, тому що індекс є тимчасовим). Метод - кожен з індексом, тобто кожен + індекс, або | кожен, індекс |, або|value,index| . Це також відповідає тому, що індекс розглядається як необов'язковий аргумент, наприклад, | value | еквівалентно | значення, індекс = нуль | що відповідає | значенню, індексу |.

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

Якщо ви хочете набирати качок, то або явно використовуйте визначений метод, як показав Брент Лонгборо, або неявний метод, як показав maxhawkins.

Рубі полягає в тому, щоб пристосувати мову до програміста, а не про програміста, який відповідає мові. Ось чому існує так багато способів. Існує так багато способів думати про щось. У 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 продовжує шукати більше способів.


Тепер я хочу задати ще одне запитання: "Який" правильний "спосіб перебрати через Діапазон у Рубі назад?"! (Це питання, як я потрапив на цю сторінку.)

Приємно робити (для форвардів):

(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

але я хочу навчити чистому Рубі, а не об'єктно-орієнтованим підходам (поки що). Я хотів би повторити:

  • без створення масиву (врахуйте 0..1000000000)
  • працює для будь-якого діапазону (наприклад, рядки, а не лише цілі числа)
  • без використання додаткової об'єктно-орієнтованої потужності (тобто без модифікації класу)

Я вважаю, що це неможливо без визначення predметоду, що означає модифікацію класу Range для його використання. Якщо ви можете це зробити, будь ласка, повідомте мене, інакше підтвердження неможливості буде вдячне, хоча це буде невтішно. Можливо, Ruby 1.9 вирішує це.

(Дякуємо за Ваш час на читання.)


5
1.upto(10) do |i| puts i endі 10.downto(1) do puts i endвид надає бажаної симетрії. Сподіваюся, що це допомагає. Не впевнений, чи буде він працювати для рядків і подібних.
Сурен

(1..10) .to_a.sort {| x, y | y <=> x} .each {| i | ставить "i = # {i}"} - реверс відбувається повільніше.
Davidslv

Можна [*1..10].each{|i| puts "i=#{i}" }.
Hauleth

19

Використовуйте every_with_index, коли вам потрібно обоє.

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

12

Інші відповіді просто чудові, але я хотів би зазначити ще одну периферійну річ: масиви впорядковані, тоді як хеші не в 1.8. (У Ruby 1.9, хеші впорядковуються порядком вставки ключів.) Тому не було б сенсу до 1.9 повторювати хеш таким же чином / послідовністю, як і масиви, які завжди мали певне впорядкування. Я не знаю, яким є порядок за замовчуванням для асоціативних масивів PHP (мабуть, мій google fu недостатньо сильний, щоб це зрозуміти), але я не знаю, як можна вважати звичайні масиви PHP та асоціативні масиви PHP бути "однаковим" у цьому контексті, оскільки порядок для асоціативних масивів здається невизначеним.

Як такий, спосіб Рубі здається мені більш зрозумілим та інтуїтивним. :)


1
хеші та масиви - те саме! масиви відображають цілі числа на об’єкти та хеширують об'єкти на карті об'єктів. масиви - це лише особливі випадки хешей, ні?
Том Леман

1
Як я вже сказав, масив - це впорядкований набір. Зображення (у загальному сенсі) не упорядковане. Якщо ви обмежите набір ключів цілими числами (наприклад, з масивами), то так буває, що набір ключів має порядок. У загальному відображенні (Hash / Associative Array) ключі можуть не мати порядку.
Пістос

@Horace: Хеші та масиви - це не те саме. Якщо одне є особливим випадком іншого, вони не можуть бути однаковими. Але ще гірше, що масиви не є особливим видом хешей, це лише абстрактний спосіб їх перегляду. Як вище зазначав Брент, використання хесів та масивів взаємозамінно може вказувати на запах коду.
Зейн

9

Намагання робити те саме, що послідовно з масивами та хешами, може бути просто кодовим запахом, але, загрожуючи, що мене позначають як кодорового напівобщика-мавпи, якщо ви шукаєте послідовної поведінки, це зробить фокус ?:

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

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

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

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

    array.each_index
  3. Перейдіть через індекси + змінну індексу:

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

    array.length.times do | i |

5

Я намагався скласти меню (у Кемпінгу та Маркабі ) за допомогою хеша.

Кожен пункт має 2 елементи: мітку меню та URL-адресу , тому хеш здався правильним, але URL-адреса "/" для "Домашньої" завжди була останньою (як ви очікували на хеш), тому пункти меню з’являлися неправильно замовлення.

Використання масиву з each_sliceвиконує завдання:

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

Додавання додаткових значень для кожного пункту меню (наприклад, наприклад, ім'я CSS ID ) означає просто збільшення значення фрагмента. Отже, як хеш, але з групами, що складаються з будь-якої кількості елементів. Ідеально.

Тож це просто сказати подяку за ненавмисне натяк на рішення!

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


4

Якщо ви використовуєте безліч mixin (як Rails), ви можете зробити щось подібне до переліченого фрагменту php. Просто використовуйте метод every_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}" }

Потрібно менше виправлення мавп.

Однак це створює проблеми, коли у вас є рекурсивний масив або хеш зі значеннями масиву. У рубіні 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]]

Щодо питання, чи це кодовий запах, я не впевнений. Зазвичай, коли мені доводиться нахилятися назад, щоб повторити щось, я відступаю і розумію, що я неправильно атакую ​​проблему.


2

Правильний шлях - це той, з ким ви відчуваєте себе найбільш комфортно і який робить те, що ви хочете. У програмуванні рідко існує один "правильний" спосіб робити речі, частіше є кілька способів вибору.

Якщо вам подобається певний спосіб робити речі, робіть саме це, якщо це не працює - тоді саме час знайти кращий шлях.


2

У Ruby 2.1 видаляється метод every_with_index. Натомість ви можете використовувати every_index

Приклад:

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

виробляє:

0 -- 1 -- 2 --

4
Це не правда. Як зазначено в коментарях до прийнятої відповіді, причина each_with_indexв документації не відображається через те, що вона надається Enumerableмодулем.
ihaztehcodez

0

Використання одного і того ж методу для ітерації через масиви та хеші має сенс, наприклад, обробляти вкладені структури хеш-масивів, які часто є результатом парсерів, з читання файлів 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і можемо використовувати його взаємозамінно для повторення через хеші та масиви. Це виправляє спостережуване божевілля ОП, яке 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, !"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.