Last active
December 14, 2015 14:08
-
-
Save padde/5098369 to your computer and use it in GitHub Desktop.
Enumerable#each_with_position (with other? and other{ ... })
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
Author
Author
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 lastHere 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
Author
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) }
endResults
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
Example