Idiomatic way to detect first/last iteration?

Hi there, is there any recognized ruby idiomatic way for detecting
when you’re on the first and/or last iteration over a collection? Or,
if not, anyone know of any tricky ways?

Specifically, I’m trying to avoid using each_with_index (as shown
below):

genres = %w{ classical punk rock jazz }
genres.each_with_index do |genre, i|
desc = “one of my favorites”
desc = “my absolute favorite” if i.zero?
desc = “my least fav. of all my favorites” if i + 1 == genres.length
puts “#{genre} is #{desc}”
end

I just don’t like having to compare an index value (especially as when
detecting the final iteration) and hate even having it as a loop
parameter unless absolutely necessary. I know, I’m picky. Just wanted
to beat the bushes and see if any cool alternatives pop up.

On Thu, Jun 11, 2009 at 9:43 AM, Kendall G.
[email protected]wrote:

desc = “my absolute favorite” if i.zero?
desc = “my least fav. of all my favorites” if i + 1 == genres.length
puts “#{genre} is #{desc}”
end

I just don’t like having to compare an index value (especially as when
detecting the final iteration) and hate even having it as a loop
parameter unless absolutely necessary. I know, I’m picky. Just wanted
to beat the bushes and see if any cool alternatives pop up.

One option (not necessarily much prettier) would be to explicitly select
out
the portions

genres = %w{ classical punk rock jazz }
puts “My absolute favorite #{genres[0]}”
genres[1…-2].each{ |g| puts “Another favorite #{g}” }
puts “My least favorite #{genres[-1]}” unless genres.length == 1

John

I expect there’s a better way to do this, but as a quick dash-off…

x=%w(the big fat cat)

x.each do |y|
print "first " if (y==x.first)
print "last " if (y==x.last)
puts y
end


“… if you do follow your bliss you put yourself on a kind of
track that has been there all the while, waiting for you, and the life
that you ought to be living is the one you are living. When you can
see that, you begin to meet people who are in your field of bliss, and
they open doors to you. I say, follow your bliss and don’t be afraid,
and doors will open where you didn’t know they were going to be.” –
Joseph Campbell

On Jun 11, 11:18 am, John W Higgins [email protected] wrote:

One option (not necessarily much prettier) would be to explicitly select out
the portions

genres = %w{ classical punk rock jazz }
puts “My absolute favorite #{genres[0]}”
genres[1…-2].each{ |g| puts “Another favorite #{g}” }
puts “My least favorite #{genres[-1]}” unless genres.length == 1

This is certainly an option in general. However, for my purposes, I
should provide more context of my actual situation (instead of my
contrived example).

I’m using ruby to build a rails application (I know, now this post may
become a bit rails specific, but hang with me). Specifically, I’m
writing code to generate/render the view of one of my pages (using
erb).

The gist of it is that I’m outputting a bunch or “rows”, 1 for each
entry in a collection. However, I need the rendering to be slightly
different for the first and last row. I can totally use
each_with_index to do this, but I’m hoping for something else. The
rails stack provides a “helper” lib method called “cycle” that allows
me to elegantly render rows differently based on some repetition cycle
(every-other, or every-fifth, etc). The cycle helper allows me to
avoid using each_with_index and testing the “index % some_val” for row
coloring. I just want to know if there is a clean, idiomatic way,
specifically in ruby (not rails library code) to do something similar,
detecting the first/last iteration of a collection.

I may just end up writing a rails helper to do something to clean it
up (and keep my view code clean and low on logic).

On Thu, Jun 11, 2009 at 12:52 PM, Matthew K. Williams
[email protected]wrote:

I expect there’s a better way to do this, but as a quick dash-off…

x=%w(the big fat cat)

x.each do |y|
print "first " if (y==x.first)
print "last " if (y==x.last)
puts y
end

Only if all of your elements are unique…

x=%w(1 2 1 3 4 3)

On Fri, 12 Jun 2009, Pieter V. wrote:

That won’t actually work if your array contains duplicate elements…

True – however, given the scenario that the querent provided, it should
work…

Hmmm… could also set a flag, I guess, if you’re that opposed to using
index.

Matt

That won’t actually work if your array contains duplicate elements…

Kendall G.:

Hi there, is there any recognized ruby idiomatic way for detecting
when you’re on the first and/or last iteration over a collection? […]

genres = %w{ classical punk rock jazz }
genres.each_with_index do |genre, i|
desc = “one of my favorites”
desc = “my absolute favorite” if i.zero?
desc = “my least fav. of all my favorites” if i + 1 == genres.length
puts “#{genre} is #{desc}”
end

In general, #equal? should check object identity:

genres = %w{ classical punk rock jazz }
genres.each do |genre|
case
when genre.equal?(genres.first) then puts “#{genre} is my absolute
favorite”
when genre.equal?(genres.last) then puts “#{genre} is my least fav.
of all my favorites”
else puts “#{genre} is one of my
favorites”
end
end

— Shot

On Jun 11, 2009, at 12:43 PM, Kendall G. wrote:

Hi there, is there any recognized ruby idiomatic way for detecting
when you’re on the first and/or last iteration over a collection? Or,
if not, anyone know of any tricky ways?

The following solution uses each_with_index internally but doesn’t
depend on #first, #last, or #length. On a single item collection
it will report ‘last’ but not ‘first’. You’ll have to decide if
that works in your case. Obviously you could change it to report
‘both’ for a single item.

Gary W.

module Enumerable
def each_with_position
previous = sigil = Object.new
each_with_index { |next_item, index|
case index
when 0
# do nothing
when 1
yield(previous, ‘first’)
else
yield(previous, ‘middle’)
end
previous = next_item
}
yield(previous, ‘last’) unless previous == sigil
end
end

[1,2,3,4].each_with_position { |*x| p x }
[1, “first”]
[2, “middle”]
[3, “middle”]
[4, “last”]
=> nil

[].each_with_position { |*x| p x }
=> nil

[1].each_with_position { |*x| p x }
[1, “last”]
=> nil

On Jun 11, 2009, at 12:43 PM, Kendall G. wrote:

Hi there, is there any recognized ruby idiomatic way for detecting
when you’re on the first and/or last iteration over a collection? Or,
if not, anyone know of any tricky ways?

I don’t see how you can do what you want without some sort of “index”.
In October.2008 I was thinking about how to detect the last iteration
when you didn’t know it was the last iteration, and devised something
which - unsurprisingly - was very similar to Gary W.'s code.

(In Gary W.'s code, is there a reason for using Object.new.
instead of, say, the following minor changes?

 previous = sigil = Object.new                     ##-
 previous = nil                                    ##+

 yield(previous, 'last') unless previous == sigil  ##-
 yield(previous, 'last') if previous               ##+    )

Based on various possibilities for what you may want/need,
I’ve made some minor changes to my Enumerable#each_with_index!

If it is called with no argument, or with nil or false,
it acts (or should act!) exactly like each_with_index.
But if it is called with an argument which is not nil or false,
then it indicates the last iteration by returning index -1,
(by analogy with -1 meaning the last element of an array, etc),
unless the last iteration is also the first iteration.
If the iteration is in the middle, then it returns index > 0.
If the iteration is the first iteration then it returns index 0,
unless the first iteration will also be the last iteration,
in which case it returns whatever is the argument you supplied.

module Enumerable

This is similar to each_with index, except that it also tells you

whether you are at the end of the enumeration.

The index (2nd) item in the yield is an integer >= 0 unless:

each_with_index! is called with an argument which is not nil or

false,

and it is the last iteration.

In this case the “index” item yielded is -1 unless the last

iteration

is also the first iteration, in which case the “index” value is

whatever

was the (not nil or false) argument to Enumerable#each_with_index!.

This allows you to decide where there is only one iteration

whether you want to treat this as being “last” (use argument -1),

or “first” (use argument 0), or something else to specifically

indicate

this is both the first and last iteration.

Because of the implementation of this, the “next” object is

“available”,

so there is an option to have that as a 3rd item in the yield.

(For example, you could use it as an each “pair” iteration,

which you can “break” at index (2nd item in yield) == -1"

or, maybe better,at index < 0, depending on the arguments to the

method.)

If as well as the possibly negative (or something else!) index,

you also want an index which doesn’t suddenly become negative

(or something else!) you can do, for example:

kk = -1

object.each_with_index!( -1 ) do | obj, ii | kk += 1

# code

end

or

kk = nil

object.each_with_index!( -1 ) do | obj, ii |

if kk then kk += 1 else kk = 0 end

# code

end

(?? Maybe use each_with_index!(*args) to

allow more sophisticated arguments ??)

def each_with_index!( qtype = nil, next_obj = false )
obj = ii = nil
self.each do | obje |
if ii then
if next_obj then yield obj, ii, obje
else yield obj, ii
end
ii += 1
else ii = 0
end
obj = obje
end
if ii then # last, which is also first if ii == 0
if qtype then
if ii == 0 then yield obj, qtype
else yield obj, -1
end
else yield obj, ii
end
end
return nil
end

end

########################### examples ############

Applied to your problem, here are some examples.

For 3 or more iterations they all behave in the same way.

If there are only two iterations, then the 2nd and 3rd examples

behave diifferently.

If there is only 1 iteration:

* the first three display: “my only favorite”

* the fourth displays: “my absolute favorite”

* the fifth displays: “my least fav. of all my favorites”

genres = %w{ classical punk rock jazz }
5.times do | n |
genres[ -1, 1 ] = [] if n > 0
puts ; puts "*genres == " + genres.inspect

puts
genres.each_with_index!(true) do | genre, ii |
if ii == 0 then # proper first
desc = “my absolute favorite”
elsif ii == true then # first and last
desc = “my only favorite”
elsif ii > 0 then # proper middle
desc = “one of my favorites”
else # proper last
desc = “my least fav. of all my favorites”
end
puts “#{genre} is #{desc}”
end

or:

puts
kk = -1
genres.each_with_index!(-1) do | genre, ii | kk += 1
if kk == 0 then # first
if ii == 0 then # proper first
desc = “my absolute favorite”
else # first and last
desc = “my only favorite”
end
elsif ii > 0 then # proper middle
desc = “one of my favorites”
else # proper last
if kk == 1 then
desc = “my second favorite”
else
desc = “my least fav. of all my favorites”
end
end
puts “#{genre} is #{desc}”
end

or the less readable (but faster?):

puts
kk = -1
genres.each_with_index!(-1) do | genre, ii | kk += 1
if ii > 0 then # proper middle
desc = “one of my favorites”
elsif ii == 0 then # proper first
desc = “my absolute favorite”
elsif kk == 0 then # first and last
desc = “my only favorite”
else # proper last
if kk == 1 then
desc = “my second favorite”
else
desc = “my least fav. of all my favorites”
end
end
puts “#{genre} is #{desc}”
end

or:

puts
genres.each_with_index!(0) do | genre, ii |
if ii == 0 then # first, and maybe also last
desc = “my absolute favorite”
elsif ii > 0 then # proper middle
desc = “one of my favorites”
else # proper last
desc = “my least fav. of all my favorites”
end
puts “#{genre} is #{desc}”
end

or (and so on):

puts
genres.each_with_index!(-1) do | genre, ii |
if ii == 0 then # proper first
desc = “my absolute favorite”
elsif ii > 0 then # proper middle
desc = “one of my favorites”
else # last, and maybe also first
desc = “my least fav. of all my favorites”
end
puts “#{genre} is #{desc}”
end

example of next object as well

puts
genres.each_with_index!(-1, true) do | genre, ii, next_obj |
p [ genre, ii, next_obj ]
end

example of next object as well, but otherwise like each_with_index

puts
genres.each_with_index!(nil, true) do | genre, ii, next_obj |
p [ genre, ii, next_obj ]
end

end

2009/6/11 Gary W. [email protected]:

it will report ‘last’ but not ‘first’. You’ll have to decide if
case index
end

[1].each_with_position { |*x| p x }
[1, “last”]
=> nil

Turns out we think along similar lines, although my solution is a bit
more wasteful:

module Enumerable

will yield elements along with

a symbol :first, :last, :middle or :only

def each_special
last = nil

each do |x|
  if last
    yield *last
    last = [x, :middle]
  else
    # first iter
    last = [x, :first]
  end
end

yield last.first, last.last == :middle ? :last : :only if last
self

end
end

[
[],
[1],
[1,2],
[1,2,3],
[1,2,3,4,5],
].each do |en|
p en

en.each_special do |x,t|
printf “%-10p %p\n”, t, x
end

puts “end”
end

Kind regards

robert

I just want to know if there is a clean, idiomatic way,
specifically in ruby (not rails library code) to do something similar,
detecting the first/last iteration of a collection.

Is detection really what you want? Doesn’t that imply that you test
each iteration? Since the array is ordered by definition, it seems
(needs tested!) that creating three entities might be faster.

in helper:
def first_also_ran_last(records)

first_record=records.shift
last_record=records.pop

return [first_record, records, last_record]
end

in view:
<% parsed_records = first_also_ran_last(@whatever)
<%=“Really like #{parsed_records[0]}”%>
<% parsed_records[1].each do |also_ran|%> #(if any)
<%= “Sorta like #{also_ran}”%>
<%end -%>
<%unless parsed_records[2].nil?-%>
<%= “Don’t much like #{parsed_records[2]}”%>
<%end->

Don’t know if it’s faster, or more idiomatic, but perhaps it’s more
semantic?

Ron

On Jun 11, 12:43 pm, Kendall G. [email protected] wrote:

desc = “my absolute favorite” if i.zero?
desc = “my least fav. of all my favorites” if i + 1 == genres.length
puts “#{genre} is #{desc}”
end

I just don’t like having to compare an index value (especially as when
detecting the final iteration) and hate even having it as a loop
parameter unless absolutely necessary. I know, I’m picky. Just wanted
to beat the bushes and see if any cool alternatives pop up.

I’ve experimented with something along these lines.

= Iteration

class It
attr_reader :index, :value, :prior, :after
def initialize(array)
@array = array
@index = 0
@value = array[0]
@prior = []
@after = array[1…-1]
end
def first? ; @index == 0 ; end
def last?
if Enumerable === self
nil
else
@index == @array.length
end
end
private
def next_iteration
@index += 1
@prior << @value
@value = @after.shift
end
end

class Array
# Iterate over each element of array using an iteration object.
#
# [1,2,3].each_iteration do |it|
# p it.index
# p it.value
# p it.first?
# p it.last?
# p it.prior
# p it.after
# end
#
# on each successive iteration produces:
#
# 0 1 2
# 1 2 3
# true false false
# false false true
# [] [1] [1,2]
# [2,3] [3] []
#
# CREDIT: Trans

def each_iteration
  if block_given?
    it = It.new(self)
    each do |e|
      yield(it)
      it.send(:next_iteration)
    end
  else
    return Enumerable::Enumerator.new(self, :each_iteration)
  end
end

end

T.

On 12 Jun 2009, at 12:45, paron wrote:

return [first_record, records, last_record]
<%end->

Don’t know if it’s faster, or more idiomatic, but perhaps it’s more
semantic?

With a bit of tidying you can reduce the view further:

def x records, message = “Nothing Relevant” # I can’t think of a
good name for the method off-hand
first = records.shift || message
last = records.pop || message
[first, records.empty? ? [message] || records, last]
end

<% track_listing = x records, “I don’t have one!!!” %>
<%= "My favourite track: #{track_listing.shift} %>
<% track_listing.shift.each do |track| -%>
<%= "Another track I like: #{track} %>
<% end -%>
<%= "The track I like least: #{track_listing.shift} %>

Ellie

Eleanor McHugh
Games With Brains
http://slides.games-with-brains.net

raise ArgumentError unless @reality.responds_to? :reason

On Jun 12, 2009, at 1:37 AM, Colin B. wrote:

(In Gary W.'s code, is there a reason for using Object.new.
instead of, say, the following minor changes?

previous = sigil = Object.new                     ##-
previous = nil                                    ##+

yield(previous, 'last') unless previous == sigil  ##-
yield(previous, 'last') if previous               ##+    )

Because you need an object that is guaranteed to not be
in the enumeration. You can’t use nil because that could
be the last item in the collection, which in my code would
prevent the last nil from being yielded to the block.

Gary W.

On Fri, Jun 12, 2009 at 1:43 AM, Kendall G.[email protected]
wrote:

desc = “my absolute favorite” if i.zero?
desc = “my least fav. of all my favorites” if i + 1 == genres.length
puts “#{genre} is #{desc}”
end

I just don’t like having to compare an index value (especially as when
detecting the final iteration) and hate even having it as a loop
parameter unless absolutely necessary. I know, I’m picky. Just wanted
to beat the bushes and see if any cool alternatives pop up.

This is similar to some other idea already posted but I did not
understand what you did not like about it.

If you just want to focus on the first and last, not the third or
something else, what is wrong with this?

af,lf = genres.shift, genres.pop
puts “#{af} is my absolute favorite”
genres.each {|genre| puts “#{genre} is one of my favorites”}
puts “#{lf} is my least fav. of all my favorites”

Harry

On Jun 12, 2009, at 7:45 AM, paron wrote:

first_record=records.shift
last_record=records.pop

You can’t use ‘shift’ and ‘pop’ if you want the
solution to work with any enumerable object.

It just depends how general a solution you are
looking for.

On Fri, Jun 12, 2009 at 1:43 AM, Kendall G.[email protected]
wrote:

desc = “my absolute favorite” if i.zero?
desc = “my least fav. of all my favorites” if i + 1 == genres.length
puts “#{genre} is #{desc}”
end

I just don’t like having to compare an index value (especially as when
detecting the final iteration) and hate even having it as a loop
parameter unless absolutely necessary. I know, I’m picky. Just wanted
to beat the bushes and see if any cool alternatives pop up.

Or how about something like this?

fav = Hash.new(" is one of my favorites")
fav[0] = " is my absolute favorite"
fav[genres.length-1] = " is my least fav. of all my favorites"

puts genres.zip(Array.new(genres.length){|i| fav[i]}).map{|x| x.join}

Harry

On Thu, Jun 11, 2009 at 6:43 PM, Kendall G.[email protected]
wrote:

desc = “my absolute favorite” if i.zero?
desc = “my least fav. of all my favorites” if i + 1 == genres.length
puts “#{genre} is #{desc}”
end

I just don’t like having to compare an index value (especially as when
detecting the final iteration) and hate even having it as a loop
parameter unless absolutely necessary. I know, I’m picky. Just wanted
to beat the bushes and see if any cool alternatives pop up.

Kendall

streams might give you the abstraction you need

--------------- 8< ------------------------
require ‘lab419/functional/streams/core’
require ‘lab419/functional/streams/as-enums’

x = %w{a b c a b}.to_stream

x.each do | s |
p [ s.head, s.tail == EmptyStream ]
end
----------------- >8 ----------------

I might implement SimpleStream#first?, SimpleStream#last? and
SimpleStream#middle? if I find some spare time, and if anybody needs
it :wink:

I admit however that there is still a little price to pay, you might
have noticed that s is a stream and not an element of the stream, but
that is just the way streams work.

HTH
Robert


Toutes les grandes personnes ont d’abord été des enfants, mais peu
d’entre elles s’en souviennent.

All adults have been children first, but not many remember.

[Antoine de Saint-Exupéry]

On Jun 12, 8:41 am, Gary W. [email protected] wrote:

On Jun 12, 2009, at 7:45 AM, paron wrote:

first_record=records.shift
last_record=records.pop

You can’t use ‘shift’ and ‘pop’ if you want the
solution to work with any enumerable object.

It just depends how general a solution you are
looking for.

Good point; I’d forgotten that case.

There’s really no inherently meaningful “first” or “last” with a Hash,
so I ignored it. In fact, couldn’t you consider “first” and “last”
meaningless for any Enumerable that isn’t indexed? Maybe the question
shouldn’t be about Enumerable.

Anyway, good catch.

Ron