Skip to content

Instantly share code, notes, and snippets.

@padde
Last active December 14, 2015 14:08
Show Gist options
  • Select an option

  • Save padde/5098369 to your computer and use it in GitHub Desktop.

Select an option

Save padde/5098369 to your computer and use it in GitHub Desktop.
Enumerable#each_with_position (with other? and other{ ... })
module Enumerable
class Position
def initialize ary
@pos = 1
@max = ary.size
@cache = Hash.new
@warn_other = false
end
def advance
@pos += 1
@cache.clear
@warn_other = false
end
def inside? range
warn 'Warning: position method called after other? or other{ ... }, expect errors!' if @warn_other
return @cache[range] unless @cache[range].nil?
@cache[range] = range.include? @pos
end
def nth? i
inside? i..i
end
def first?
nth? 1
end
def last?
nth? @max
end
def middle?
not ( first? or last? )
end
def other?
@warn_other = false
@warn_other = other_no_warn?
end
def other_no_warn?
not( @cache.keys.any? &method(:inside?) )
end
%w{inside nth first middle last other other_no_warn}.each do |pos_name|
define_method pos_name do |*args, &block|
block.call if block and send :"#{pos_name}?", *args, &block
end
end
end
def each_with_position
pos = Position.new self
each do |args|
yield [*args, pos]
pos.advance
end
end
end
@padde
Copy link
Author

padde commented Mar 6, 2013

Example

[1,2,3,4,5,6].each_with_position do |x,pos|
  puts "#{x} is third"  if pos.nth? 3
  puts "#{x} is last"   if pos.last?
  puts "#{x} is other"  if pos.other?
  puts
end

#1 is other
# 
#2 is other
# 
#3 is third
# 
#4 is other
# 
#5 is other
# 
#6 is last

@padde
Copy link
Author

padde commented Mar 6, 2013

Caveat

Calling other? or other{ ... } before using the other position methods is bad, because other? can only check for the ranges that have been written to the cache before it is invoked:

[1,2,3,4,5,6].each_with_position do |x,pos|
  puts "#{x} is third"  if pos.nth? 3
  puts "#{x} is other"  if pos.other?       # this is bad
  puts "#{x} is last"   if pos.last?
  puts
end

#1 is other
# Warning: position method called after other? or other{ ... }, expect errors!
# 
#2 is other
# Warning: position method called after other? or other{ ... }, expect errors!
# 
#3 is third
# 
#4 is other
# Warning: position method called after other? or other{ ... }, expect errors!
# 
#5 is other
# Warning: position method called after other? or other{ ... }, expect errors!
# 
#6 is other
# Warning: position method called after other? or other{ ... }, expect errors!
#6 is last

Here we can see that for example 6 is last and other, which is probably not expected. Therefore, a warning is issued. This warning can however be switched off by using other_no_warn?:

[1,2,3,4,5,6].each_with_position do |x,pos|
  puts "#{x} is third"  if pos.nth? 3
  puts "#{x} is other"  if pos.other_no_warn?
  puts "#{x} is last"   if pos.last?
  puts
end

#1 is other
# 
#2 is other
# 
#3 is third
# 
#4 is other
# 
#5 is other
# 
#6 is other
#6 is last

@padde
Copy link
Author

padde commented Mar 6, 2013

Benchmark

crappy benchmark, i must admit... Nevertheless, here it is:

require 'fruity'

def with_nth( arr )
  arr.each_with_position do |x,pos|
    pos.nth? 42
  end
end

def with_middle( arr )
  arr.each_with_position do |x,pos|
    pos.middle?
  end
end

def with_other( arr )
  arr.each_with_position do |x,pos|
    pos.other?
  end
end

def with_all( arr )
  arr.each_with_position do |x,pos|
    pos.first?
    pos.last?
    pos.other?
  end
end

def with_plain_each( arr )
  arr.each do |x|
  end
end

arr = [*1..1000]

compare do
  nth        { with_nth(arr)        }
  middle     { with_middle(arr)     }
  other      { with_other(arr)      }
  all        { with_all(arr)        }
  plain_each { with_plain_each(arr) }
end

Results

Running each test 64 times. Test will take about 26 seconds.
plain_each is faster than other by 45x ± 1.0
other is similar to nth
nth is faster than middle by 2x ± 0.1
middle is faster than all by 2x ± 0.1

Implications

Do not use this with big Arrays ;-) It will slow you down by a factor of roughly 50. Ouch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment