Phrogz
12/19/2007 12:02:00 AM
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