74

I always use a counter to check for the first item (i==0) in a loop:

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

Is there a more elegant way to do this (perhaps a method)?

Panagiotis Panagi
  • 9,927
  • 7
  • 55
  • 103
  • 8
    Why would anyone vote to close this? It is a very real question. – Telemachus Nov 18 '11 at 11:15
  • 1
    What prevents you in this case from doing what you need with the first item in the array, then doing a normal `each` loop with just the common stuff? – Russell Nov 18 '11 at 11:18
  • @Russell If he does the special thing to the first item (say with `array.first` or `array[0]`) and then runs the `each` loop, he will *still* have to test for the first item if he *doesn't* want to do the regular thing to the first item also. – Telemachus Nov 18 '11 at 11:22
  • 1
    Yes but in the example above he *does* want to do the common stuff to the first item. And if he doesn't, couldn't he just do `my_array[1..-1].each`? – Russell Nov 18 '11 at 11:24
  • I think Russell means dealing with the first item outside the loop, and then loop from the second item. – Panagiotis Panagi Nov 18 '11 at 11:25
  • In the example above the common stuff will happen to all items including the first item. If you need to do different things to different elements it doesn't sound like a very good candidate for code in a loop... so I think having it outside the loop makes more sense. – Russell Nov 18 '11 at 11:31
  • http://stackoverflow.com/questions/2241684/magic-first-and-last-indicator-in-a-loop-in-ruby-rails is a similar question. – Andrew Grimm Nov 18 '11 at 13:27
  • possible duplicate of [Do something special first time around in a Ruby loop](http://stackoverflow.com/questions/1535293/do-something-special-first-time-around-in-a-ruby-loop) – chech Jun 27 '13 at 12:35

8 Answers8

75

You can do this:

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

Try it on ideone.

detunized
  • 15,059
  • 3
  • 48
  • 64
  • 1
    I think this is perhaps not the best answer — it's basically what the OP wrote in his question, but using `.each_with_index` instead of `.each`. The `.drop(1)` suggestion below seems more ruby-like and avoids indexes altogether. – jm3 Mar 31 '16 at 18:26
  • What if I want to skip the first two iterations? I tried `if index > 1` and got the error `undefined method '>' for nil:NilClass` – Aaron Franke Sep 03 '19 at 14:54
47

Using each_with_index, as others have described, would work fine, but for the sake of variety here is another approach.

If you want to do something specific for the first element only and something general for all elements including the first, you could do:

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

But if you want to not do the general thing with the first element you could do:

# 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
Russell
  • 12,261
  • 4
  • 52
  • 75
3

Arrays have an "each_with_index" method which is handy for this situation:

my_array.each_with_index do |item, i|
  item.do_something if i==0
  #common stuff
end
Toby Hede
  • 36,755
  • 28
  • 133
  • 162
  • This is really handy for the classic situation of looping an object in a view when you want to render something different. Thanks a lot! – Apollo Jan 07 '13 at 19:02
3

What fits best is depending on the situation.

Another option (if you know your array is not empty):

# treat the first element (my_array.first)
my_array.each do | item |
   # do the common_stuff
end
undur_gongor
  • 15,657
  • 5
  • 63
  • 75
2

each_with_index from Enumerable (Enumerable is already mixed in with Array, so you can call it on an array without any trouble):

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]
Telemachus
  • 19,459
  • 7
  • 57
  • 79
2

If you don't need the array afterwards:

ar = %w(reversed hello world)

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

#=>REVERSED
#=>olleh
#=>dlrow
steenslag
  • 79,051
  • 16
  • 138
  • 171
1

Ruby's Enumerable#inject provides an argument that can be used for doing something differently on the first iteration of a loop:

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

The argument is not strictly necessary for common things like sums and products:

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

But when you want to do something different on the first iteration, that argument might be useful to you:

> puts fruits.inject("I like to eat: ") {|acc, elem| acc << elem << " "}
I like to eat: apples pears peaches plums oranges 
=> nil
sarnold
  • 102,305
  • 22
  • 181
  • 238
  • Neat and lovely as `inject` is I don't think it answers the OPs question - which was how to do something different for the first iteration of the loop only. – Russell Nov 18 '11 at 11:15
  • I don't `inject` fits what he's asking about, but maybe you're seeing something we missed. He wants to do something *different* the first time through an array. His `i += 1` is a manual counter, not a sum. – Telemachus Nov 18 '11 at 11:18
  • @Russel, ha! I got to writing about `inject` and forgot that the whole point of the argument was doing something differently on the first iteration. Thanks. – sarnold Nov 18 '11 at 11:19
  • @telemachus, updated :) thanks for the notice that it's bed time. :) – sarnold Nov 18 '11 at 11:24
0

Here's a solution that doesn't need to be in an immediately enclosing loop and avoids the redundancy of specifying a status placeholder more than once unless you really need to.

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

Its scope matches the holder: $first_time_only will be globally once; @first_time_only will be once for the instance, and first_time_only will be once for the current scope.

If you want the first several times, etc, you can easily put [1,2,3] if you need to distinguish which of the first iterations you're in, or even something fancy [1, false, 3, 4] if you need something weird.

android.weasel
  • 3,343
  • 1
  • 30
  • 41