Skip to content

Instantly share code, notes, and snippets.

@mediocretes
Created February 10, 2012 22:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mediocretes/1793402 to your computer and use it in GitHub Desktop.
Save mediocretes/1793402 to your computer and use it in GitHub Desktop.
class Hash
def self.nmerge(*hashes, &block)
keys = hashes.map(&:keys).flatten.uniq
rhashes = hashes.reverse
keys.inject({ }) do |output, key|
if block
output[key] = block.call(key, hashes.map{ |x| x[key]})
else
rhashes.each do |hash|
if hash[key]
output[key] = hash[key]
break
end
end
end
output
end
end
end
@janxious
Copy link

one   = {:one   => 1, :two   => 1}
two   = {:two   => 2, :three => 2}
three = {:three => 3, :four  => 3}

actual   = Hash.nmerge(one,two,three)
expected = {:one => 1, :two => 2, :three => 3}

assert_equal expected, actual  # => fails

@mediocretes
Copy link
Author

one   = {:one   => 1, :two   => 1}
two   = {:two   => 2, :three => 2}
three = {:three => 3, :four  => 3}

actual   = Hash.nmerge(one,two,three)
expected = {:one => 1, :two => 2, :three => 3, :four => 3}

assert_equal expected, actual  # => passes

@janxious
Copy link

DERP!

@mediocretes
Copy link
Author

I know what you're thinking, "how often do you really do this?" The answer is, a few thousand times a minute, and usually in the 8-to-12-way range.

@janxious
Copy link

Also, is this a class method because you never start with a single hash?

@mediocretes
Copy link
Author

pretty much. I always have one hash forever or many hashes all at the same time of which none is more important than the others.

@janxious
Copy link

If you want super speed for the non-block version:

Hash[hashes.map(&:to_a).flatten(1)]

@janxious
Copy link

class Hash
  def self.nmerge(*hashes, &block)
    keys = hashes.map(&:keys).flatten.uniq
    rhashes = hashes.reverse
    keys.inject({ }) do |output, key|
      if block
        output[key] = block.call(key, hashes.map{ |x| x[key]})
      else
        rhashes.each do |hash|
          if hash[key]
            output[key] = hash[key]
            break
          end
        end
      end
      output
    end
  end

  def self.nmerge4(*hashes)
    Hash[hashes.map(&:to_a).flatten(1)]
  end
end

one   = {:one   => 1, :two    => 1}
two   = {:two   => 2, :three  => 2}
three = {:three => 3, :four   => 3}
four  = {:four  => 4, :five   => 4}
five  = {:five  => 5, :six    => 5}
six   = {:six   => 6, :seven  => 6}

Benchmark.bm do |b|
  b.report("non-block nmerge") do
    400_000.times { Hash.nmerge(one,two,three,four,five,six) }
  end

  b.report("non-block nmerge4") do
    400_000.times { Hash.nmerge4(one,two,three,four,five,six) }
  end
end

#        user     system      total        real
# non-block nmerge  7.880000   0.020000   7.900000 (  8.078084)
# non-block nmerge4  3.620000   0.000000   3.620000 (  3.727849)

@janxious
Copy link

And the fully functioning version:

class Hash
  def self.nmerge(*hashes, &block)
    keys = hashes.map(&:keys).flatten.uniq
    rhashes = hashes.reverse
    keys.inject({ }) do |output, key|
      if block
        output[key] = block.call(key, hashes.map{ |x| x[key]})
      else
        rhashes.each do |hash|
          if hash[key]
            output[key] = hash[key]
            break
          end
        end
      end
      output
    end
  end

  def self.nmerge4(*hashes, &block)
    if block
      unique_keys = hashes.map(&:keys).flatten.uniq
      unique_keys.inject({}) do |output, key|
        output[key] = block.call(key, hashes.map{|x| x[key]})
        output
      end
    else
      Hash[hashes.map(&:to_a).flatten(1)]
    end
  end
end

one   = {:one   => 1, :two    => 1}
two   = {:two   => 2, :three  => 2}
three = {:three => 3, :four   => 3}
four  = {:four  => 4, :five   => 4}
five  = {:five  => 5, :six    => 5}
six   = {:six   => 6, :seven  => 6}
require 'benchmark'

Benchmark.bm do |b|
  b.report("non-block nmerge") do
    400_000.times { Hash.nmerge(one,two,three,four,five,six) }
  end

  b.report("non-block nmerge4") do
    400_000.times { Hash.nmerge4(one,two,three,four,five,six) }
  end

  b.report("block nmerge") do
    100_000.times {
      Hash.nmerge(one, two, three, four, five, six) do |key, values|
        values.inject(0){ |sum, item| sum + (item || 0) }
      end
    }
  end

  b.report("block nmerge4") do
    100_000.times {
      Hash.nmerge4(one, two, three, four, five, six) do |key, values|
        values.inject(0){ |sum, item| sum + (item || 0) }
      end
    }
  end
end
#         user     system      total        real
#  non-block nmerge  7.780000   0.010000   7.790000 (  7.869783)
#  non-block nmerge4  3.620000   0.010000   3.630000 (  3.681451)
#  block nmerge  3.520000   0.000000   3.520000 (  3.551300)
#  block nmerge4  3.430000   0.000000   3.430000 (  3.442584)

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