Skip to content

Instantly share code, notes, and snippets.

@havenwood
Last active June 4, 2018 22:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save havenwood/501b7a7e57f512ec95cfcb7f9a44f0d0 to your computer and use it in GitHub Desktop.
Save havenwood/501b7a7e57f512ec95cfcb7f9a44f0d0 to your computer and use it in GitHub Desktop.
An Example Implementation of Enumerable#step in Pure Ruby
require 'minitest/autorun'
module Enumerable
def step step = 1
raise TypeError, 'no implicit conversion of Object into Integer' unless step.respond_to? :to_int
step_count = step.to_int
raise TypeError, "can't convert #{step.class} to Integer (#{step.class}#to_int gives #{step_count.class})" unless step_count.instance_of? Integer
raise TypeError, "step can't be 0" if step_count.zero?
raise TypeError, "step can't be negative" if step_count.negative?
if block_given?
each_slice step_count do |this_step, *_rest|
yield this_step
end
self
else
lazy_size = size.fdiv(step_count).ceil if size
Enumerator.new lazy_size do |yielder|
each_slice step_count do |this_step, *_rest|
yielder << this_step
end
end
end
end
end
class ImplicitConversionExample
def to_int
4
end
end
describe 'Enumerable#step' do
describe 'when no block given' do
it 'returns an enumerator' do
enum = 1.upto(10).step(4)
assert_instance_of Enumerator, enum
end
it 'returns the correct enumerator' do
enum = 1.upto(10).step(4)
assert_equal [1, 5, 9], enum.to_a
end
it 'returns a sized enumerator when possible' do
assert_equal 10, 1.upto(10).step(1).size
assert_equal 5, 1.upto(10).step(2).size
assert_equal 4, 1.upto(10).step(3).size
assert_equal 3, 1.upto(10).step(4).size
assert_equal 2, 1.upto(10).step(5).size
assert_equal 2, 1.upto(10).step(6).size
assert_equal 1, 1.upto(10).step(10).size
assert_equal 1, 1.upto(10).step(11).size
end
it 'works with unsized enumerators' do
enum = ('a'..'g').to_enum.step(2)
assert_equal %w[a c e g], enum.to_a
assert_nil enum.size
end
it 'raises TypeError if unsteppable' do
assert_raises TypeError do
1.upto(10).step(Object.new)
end
end
it 'raises a TypeError if #to_int does not return an Integer' do
assert_raises TypeError do
1.upto(2).step(Object.new)
end
end
it 'calls #to_int to coerce step to an Integer' do
obj = ImplicitConversionExample.new
assert_equal [1, 5, 9], 1.upto(10).step(obj).to_a
end
it 'raises an ArgumentError if step is 0' do
assert_raises TypeError do
-1.upto(1).step(0)
end
end
it 'raises an ArgumentError if step is 0.0' do
assert_raises TypeError do
-1.upto(1).step(0.0)
end
end
it 'raises an ArgumentError if step is negative' do
assert_raises TypeError do
-1.upto(1).step(-2)
end
end
end
describe 'when a block is given' do
it 'returns self' do
enum = 1.upto(2)
assert_equal enum.to_a, enum.step { }.to_a
end
it 'enumerates correctly' do
a = []
1.upto(2) { |n| a << n }
assert_equal [1, 2], a
end
it 'raises TypeError if unsteppable' do
assert_raises TypeError do
1.upto(10).step(Object.new) { }
end
end
it 'raises a TypeError if #to_int does not return an Integer' do
assert_raises TypeError do
1.upto(2).step(Object.new) { }
end
end
it 'calls #to_int to coerce step to an Integer' do
obj = ImplicitConversionExample.new
a = []
1.upto(10).step(obj) { |n| a << n }
assert_equal [1, 5, 9], a
end
it 'raises an ArgumentError if step is 0' do
assert_raises TypeError do
-1.upto(1).step(0) { }
end
end
it 'raises an ArgumentError if step is 0.0' do
assert_raises TypeError do
-1.upto(1).step(0.0) { }
end
end
it 'raises an ArgumentError if step is negative' do
assert_raises TypeError do
-1.upto(1).step(-2) { }
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment