I wanted a method like Hash#update, but that preserved the values from
both the original and argument Hash. A little searching failed to find
it. (I did find that someone somewhere wrote a Hash#collate that’s in
my ri docs, but who knows where it came from. Its description appears
not to do at all what I wanted, anyhow.)
So, I wrote my own. Comments welcome. Efficiency patches particularly
welcome. Under a different name, perhaps Trans might consider it for
inclusion in Facets.
class Hash
Merge the values of this hash with those from another, setting all
values
to be arrays representing the values from both hashes.
{ :a=>1, :b=>2 }.collate :a=>3, :b=>4, :c=>5
#=> { :a=>[1,3], :b=>[2,4], :c=>[5] }
The ‘uniq’ option allows you to ensure all values are unique:
{ :a=>1, :b=>2 }.collate( { :a=>1, :b=>3 }, :uniq=>true )
#=> { :a=>[1], :b=>[2,3] }
By default, array values in either side are merged:
foo = { :a=>[1,2], :b=>[3] }
bar = { :a=>[4,5], :c=>[6,7] }
foo.collate( bar )
#=> { :a=>[1,2,4,5], :b=>[3], :c=>[6,7] }
Use the ‘preserve_arrays’ option to prevent them from being
merged:
foo = { :a=>[1,2], :b=>[3] }
bar = { :a=>[4,5], :c=>[6,7] }
foo.collate( bar, :preserve_arrays=>true )
#=> { :a=>[[1,2],[4,5]], :b=>[[3]], :c=>[[6,7]] }
Note that, as shown above, preserving arrays will cause array
values
to be wrapped up in another array.
def collate( other_hash, options={} )
dup.collate!( other_hash, options )
end
The same as #collate, but modifies the receiver in place.
def collate!( other_hash, options={} )
# Prepare, ensuring every existing key is already an Array
each{ |key, value|
if value.is_a?( Array ) && !options[ :preserve_arrays ]
self[key] = value
else
self[key] = [ value ]
end
}
# Collate with values from other_hash
other_hash.each{ |key, value|
if self[ key ]
if value.is_a?( Array ) && !options[ :preserve_arrays ]
self[ key ].concat( value )
else
self[ key ] << value
end
elsif value.is_a?( Array ) && !options[ :preserve_arrays ]
self[ key ] = value
else
self[ key ] = [ value ]
end
}
each{ |key, value| value.uniq! } if options[ :uniq ]
self
end
end
if FILE == $0
require ‘test/unit’
class TestHashCollation < Test::Unit::TestCase
def setup
$a = { :a=>1, :b=>2, :z=>26, :all=>%w|a b z|, :stuff1=>%w|foo
bar|, :whee=>%w|a b| }
$b = { :a=>1, :b=>4, :c=>9, :all=>%w|a b c|, :stuff2=>%w|jim
jam|, :whee=>%w|a b| }
$c = { :a=>1, :b=>8, :c=>27 }
end
def test1_defaults
collated = $a.collate( $b )
assert_equal( 8, collated.keys.length, “There are 7 unique
keys” )
assert_equal( [1,1], collated[ :a ] )
assert_equal( [2,4], collated[ :b ] )
assert_equal( [9], collated[ :c ] )
assert_equal( [26], collated[ :z ] )
assert_equal( %w|a b z a b c|, collated[ :all ], “Arrays are
merged by default.” )
assert_equal( %w|foo bar|, collated[ :stuff1 ] )
assert_equal( %w|jim jam|, collated[ :stuff2 ] )
assert_equal( %w|a b a b|, collated[ :whee ] )
end
def test2_uniq
collated = $a.collate( $b, :uniq=>true )
assert_equal( 8, collated.keys.length, “There are 7 unique
keys” )
assert_equal( [1], collated[ :a ] )
assert_equal( [2,4], collated[ :b ] )
assert_equal( [9], collated[ :c ] )
assert_equal( [26], collated[ :z ] )
assert_equal( %w|a b z c|, collated[ :all ], “Arrays are merged
by default.” )
assert_equal( %w|foo bar|, collated[ :stuff1 ] )
assert_equal( %w|jim jam|, collated[ :stuff2 ] )
assert_equal( %w|a b|, collated[ :whee ] )
end
def test3_preserve_arrays
collated = $a.collate( $b, :preserve_arrays=>true )
assert_equal( 8, collated.keys.length, “There are 7 unique
keys” )
assert_equal( [1,1], collated[ :a ] )
assert_equal( [2,4], collated[ :b ] )
assert_equal( [9], collated[ :c ] )
assert_equal( [26], collated[ :z ] )
assert_equal( [ %w|a b z|, %w|a b c|], collated[ :all ], “Two
arrays are not merged.” )
assert_equal( [%w|foo bar|], collated[ :stuff1 ],
“Arrays unique to one side are wrapped” )
assert_equal( [%w|jim jam|], collated[ :stuff2 ],
“Arrays unique to one side are wrapped” )
assert_equal( [%w|a b|, %w|a b|], collated[ :whee ] )
end
def test4_preserve_and_uniq
collated = $a.collate( $b, :preserve_arrays=>true, :uniq=>true )
assert_equal( 8, collated.keys.length, “There are 7 unique
keys” )
assert_equal( [1], collated[ :a ] )
assert_equal( [2,4], collated[ :b ] )
assert_equal( [9], collated[ :c ] )
assert_equal( [26], collated[ :z ] )
assert_equal( [ %w|a b z|, %w|a b c|], collated[ :all ], “Two
arrays are not merged.” )
assert_equal( [%w|foo bar|], collated[ :stuff1 ],
“Arrays unique to one side are wrapped” )
assert_equal( [%w|jim jam|], collated[ :stuff2 ],
“Arrays unique to one side are wrapped” )
assert_equal( [%w|a b|], collated[ :whee ], “Preserve arrays +
uniq == duplicate arrays are removed” )
end
def test5_multi_collate
collated = $a.collate( $b ).collate( $c )
assert_equal( [1,1,1], collated[ :a ] )
assert_equal( [2,4,8], collated[ :b ] )
assert_equal( [9,27], collated[ :c ] )
end
def test6_multi_collate_with_preserve
collated = $a.collate( $b, :preserve_arrays=>1 ).collate( $c )
assert_equal( [1,1,1], collated[ :a ] )
assert_equal( [2,4,8], collated[ :b ] )
assert_equal( [9,27], collated[ :c ] )
collated = $a.collate( $b ).collate( $c, :preserve_arrays=>1 )
assert_equal( [[1,1],1], collated[ :a ] )
assert_equal( [[2,4],8], collated[ :b ] )
assert_equal( [[9],27], collated[ :c ] )
collated =
$a.collate( $b, :preserve_arrays=>1 ).collate( $c, :preserve_arrays=>1 )
assert_equal( [[1,1],1], collated[ :a ] )
assert_equal( [[2,4],8], collated[ :b ] )
assert_equal( [[9],27], collated[ :c ] )
end
end
end