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