Як по-різному діяти на першій ітерації в циклі Ruby?


74

Я завжди використовую лічильник для перевірки першого елемента ( i==0) у циклі:

i = 0
my_array.each do |item|
  if i==0
    # do something with the first item
  end
  # common stuff
  i += 1
end

Чи є більш елегантний спосіб зробити це (можливо, метод)?


8
Чому хтось би проголосував за закриття цього? Це цілком реальне питання.
Телемах

1
Що в цьому випадку заважає вам робити те, що вам потрібно, з першим елементом масиву, а потім робити звичайний eachцикл із лише загальними матеріалами?
Рассел

@Russell Якщо він зробить особливе для першого елемента (скажімо за допомогою array.firstабо array[0]), а потім запустить eachцикл, йому все одно доведеться тестувати для першого елемента, якщо він не хоче робити звичайні дії з першим елементом.
Телемах

1
Так , але в наведеному вище прикладі , він дійсно хоче зробити загальний матеріал до першого пункту. А якщо цього не стане, чи не міг би він просто зробити my_array[1..-1].each?
Рассел

Я думаю, Рассел мав на увазі справу з першим елементом поза циклом, а потім із другим елементом.
Panagiotis Panagi

Відповіді:


74

Ви можете зробити це:

my_array.each_with_index do |item, index|
    if index == 0
        # do something with the first item
    end
    # common stuff
end

Спробуйте на Ideone .


1
Я думаю, що це, мабуть, не найкраща відповідь - це в основному те, що ОП написав у своєму питанні, але використовуючи .each_with_indexзамість .each. Наведена .drop(1)нижче пропозиція виглядає більш рубіноподібною і взагалі уникає індексів.
jm3

Що робити, якщо я хочу пропустити перші дві ітерації? Я спробував if index > 1і отримав помилку undefined method '>' for nil:NilClass
Аарон Франке

46

Використання each_with_index, як описували інші, було б непоганим, але для різноманітності тут є інший підхід.

Якщо ви хочете зробити щось конкретне лише для першого елемента і щось загальне для всіх елементів, включаючи перший, ви можете зробити:

# do something with my_array[0] or my_array.first
my_array.each do |e| 
  # do the same general thing to all elements 
end

Але якщо ви не хочете робити загального з першим елементом, який ви можете зробити:

# do something with my_array[0] or my_array.first
my_array.drop(1).each do |e| 
  # do the same general thing to all elements except the first 
end

+1 У мене була така сама ідея. Різниця лише в тому, коли масив порожній.
undur_gongor

13
Мені подобається my_array.drop(1)це більш декларативно.
Tokland

1
Вони працюють, але не в ситуації, коли особлива поведінка знаходиться посередині циклу. У цьому випадку, я сподіваюся, вам потрібно зробити кожну_з_індексом як найбільш читабельне рішення ..
Jun-Dai Bates-Kobashigawa

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

Крім того, @tokland, я змінив відповідь на використання drop. Це набагато приємніше.
Рассел

3

Масиви мають метод "кожний_з_індексом", який зручний для цієї ситуації:

my_array.each_with_index do |item, i|
  item.do_something if i==0
  #common stuff
end

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

3

Найкраще підходить залежно від ситуації.

Інший варіант (якщо ви знаєте, що ваш масив не порожній):

# treat the first element (my_array.first)
my_array.each do | item |
   # do the common_stuff
end

2

each_with_indexвід Enumerable (Enumerable вже змішаний з Array, тому ви можете викликати його на масив без будь-яких проблем):

irb(main):001:0> nums = (1..10).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
irb(main):003:0> nums.each_with_index do |num, idx|
irb(main):004:1* if idx == 0
irb(main):005:2> puts "At index #{idx}, the number is #{num}."
irb(main):006:2> end
irb(main):007:1> end
At index 0, the number is 1.
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

2

Якщо вам потім масив не потрібен:

ar = %w(reversed hello world)

puts ar.shift.upcase
ar.each{|item| puts item.reverse}

#=>REVERSED
#=>olleh
#=>dlrow

На відміну від коду у питанні, ви не змінюєте перше слово.
undur_gongor

Так, мабуть, elseдесь уявляв . Але це робить те, що сказано в назві питання.
steenslag

1

Ruby's Enumerable#injectнадає аргумент, який можна використовувати для того, щоб робити щось інакше на першій ітерації циклу:

> l=[1,2,3,4]
=> [1, 2, 3, 4]
> l.inject(0) {|sum, elem| sum+elem}
=> 10

Аргумент не є суворо необхідним для таких загальних речей, як суми та продукти:

> l.inject {|sum, elem| sum+elem}
=> 10

Але коли ви хочете зробити щось інше на першій ітерації, цей аргумент може бути вам корисний:

> puts fruits.inject("I like to eat: ") {|acc, elem| acc << elem << " "}
I like to eat: apples pears peaches plums oranges 
=> nil

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

Я не injectвідповідаю тому, про що він запитує, але, можливо, ви бачите щось, що ми пропустили. Перший раз він хоче зробити щось інше за допомогою масиву. Його i += 1це ручний лічильник, а не сума.
Телемах

@Russel, ха! Я почав писати про це injectі забув, що вся суть аргументу робила щось інше на першій ітерації. Дякую.
sarnold

@telemachus, оновлено :) дякую за повідомлення, що настав час сну. :)
sarnold

0

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

do_this if ($first_time_only ||= [true]).shift

Його обсяг відповідає власнику: $first_time_onlyбуде глобально один раз; @first_time_onlyбуде один раз для екземпляра, і first_time_onlyбуде один раз для поточного обсягу.

Якщо ви хочете перші кілька разів тощо, ви можете легко [1,2,3]визначити, якщо вам потрібно розрізнити, в якій з перших ітерацій ви перебуваєте, або навіть щось химерне, [1, false, 3, 4]якщо вам потрібно щось дивне.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.