my work, life, and ideas

Implementing Ruby’s inject method

I spent a little time tinkering with some of the “newer” RSpec 2 features while re-implementing the inject method that belongs to the Enumerable module. It is a fun and simple exercise because it reinforces the understanding of blocks and the sometimes confusing nature of the inject method. Here it is.

 
require 'rubygems'
require 'rspec'
 
module Enumerable
  def inject(*args, &block)
    if(args.size == 2)
      inject_binary_operation(args[0], args[1], self)
    elsif(args.size == 1 && !block_given?)
      inject_binary_operation(self.first, args[0], self.drop(1))
    else
      the_memo = args[0] || self.first
      self.each { |item| the_memo = yield(the_memo, item) if block_given? }
      the_memo
    end
  end
  alias :reduce :inject
 
  private
 
  def inject_binary_operation(the_memo, operator, enum)
    raise TypeError.new("#{operator} is not a symbol") unless operator.is_a?(Symbol)
    enum.each { |item| the_memo = the_memo.send(operator, item) }
    the_memo
  end
end
 
shared_examples_for "an Enumerable object" do
 
  describe "inject" do
 
    describe "given there is a specified memo value" do
 
      it('should return the newly injected "memo" value if using a block') do
        enumerable.inject([3,4,5]) { |memo, enum_item| memo.include?(enum_item) ? memo : memo << enum_item }.should == [3, 4, 5, 1, 2]
      end
 
      describe "when it is a binary operation" do
 
        it('should return a TypeError because the single argument is not a Symbol') do
          lambda { enumerable.inject(2) }.should raise_error(TypeError)
        end
 
        it('should add the values') do
          enumerable.inject(2, :+).should == 8
        end
 
        it('should multiply the values') do
          enumerable.inject(2, :*).should == 12
        end
 
        it('should subtract the values') do
          enumerable.inject(2, :-).should == -4
        end
 
    end
 
    end
 
    describe "given there isn't a specified memo value" do
 
      it('should return the newly injected "memo" value if using a block') do
        enumerable.inject { |memo, enum_item| memo > enum_item ? memo : enum_item }.should == 3
      end
 
      describe "when it is a binary operation" do
 
        it('should add the values') do
          enumerable.inject(:+).should == 6
        end
 
        it('should multiply the values') do
          enumerable.inject(:*).should == 6
        end
 
        it('should subtract the values') do
          enumerable.inject(:-).should == -4
        end
      end
 
    end
  end  
end
 
describe Range do
 
  it_should_behave_like "an Enumerable object" do
    let(:enumerable) { (1..3) }
  end
 
end
 
describe Array do
 
  it_should_behave_like "an Enumerable object" do
    let(:enumerable) { [1,2,3] }
  end
 
end

I especially like the RSpec documentation format because it provides such great readability. When appropriate the “shared_examples_for” is also nice to keep your test suite DRY.

$ rspec injectable.rb --format documentation
 
Range
  it should behave like an Enumerable object
    inject
      given there is a specified memo value
        should return the newly injected "memo" value if using a block
        when it is a binary operation
          should return a TypeError because the single argument is not a Symbol
          should add the values
          should multiply the values
          should subtract the values
      given there isn't a specified memo value
        should return the newly injected "memo" value if using a block
        when it is a binary operation
          should add the values
          should multiply the values
          should subtract the values
 
Array
  it should behave like an Enumerable object
    inject
      given there is a specified memo value
        should return the newly injected "memo" value if using a block
        when it is a binary operation
          should return a TypeError because the single argument is not a Symbol
          should add the values
          should multiply the values
          should subtract the values
      given there isn't a specified memo value
        should return the newly injected "memo" value if using a block
        when it is a binary operation
          should add the values
          should multiply the values
          should subtract the values
 
Finished in 0.007 seconds
18 examples, 0 failures

And of course the gist…
https://gist.github.com/1096650

Reply