Merge two hashes with alternation in Ruby (2.x.x) - ruby

I have two hashes:
h1 = {'a' => 33, 'b' => 4, 'c' => 6}
h2 = {'d' => 10, 'e' => 1, 'f' => 12}
Now they should be merged into one, with alternation, so the final hash should be like:
{'a' => 33, 'd' => 10, 'b' => 4, 'e' = 1, 'c' => 6, 'f' => 12}
What's the best way to do that. Probably a single liner?
Thanks!

Here is my try
Hash[*[h1.to_a, h2.to_a].transpose.flatten]
# => {"a"=>33, "d"=>10, "b"=>4, "e"=>1, "c"=>6, "f"=>12}
# or
Hash[*h1.to_a.zip(h2.to_a).flatten]
# => {"a"=>33, "d"=>10, "b"=>4, "e"=>1, "c"=>6, "f"=>12}
Even with more STARS. :-)
Hash[*[*h1, *h2].transpose.flatten]
# => {"a"=>"b", "c"=>"d", "e"=>"f", 33=>4, 6=>10, 1=>12}

No stars!
h1 = {'a' => 33, 'b' => 4, 'c' => 6}
h2 = {'d' => 10, 'e' => 1, 'f' => 12}
h1.to_a.zip(h2.to_a).flatten(1).to_h
#=> {"a"=>33, "d"=>10, "b"=>4, "e"=>1, "c"=>6, "f"=>12}
For Ruby versions < 2.0:
Hash[h1.to_a.zip(h2.to_a).flatten(1)]
Note Array#transpose and Enumerable#zip are always interchangeable when manipulating arrays.
For Ruby versions < 1.9, the ordering of key/value pairs in hashes was not specified. For those versions, the closest you could come to answering your question would be to provide a desired ordering of keys:
keys_in_order = ['a', 'd', 'b', 'e', 'c', 'f']
retrieve the associated values:
values = h1.merge(h2).values_at(*keys_in_order)
#=> [33, 10, 4, 1, 6, 12]
and pair these with zip:
keys_in_order.zip(values)
#=> [["a", 33], ["d", 10], ["b", 4], ["e", 1], ["c", 6], ["f", 12]]

Related

Use a list/set/array of values as a hash key

I'm trying to implement a hypergraph. I'd like to use a (frozen) set as the key to a hash. I'd like to do the following or something like it.
set_a = Set.new(["a","b","c"])
set_b = Set.new(["a","b","d"])
set_c = Set.new(["a","b"])
set_d = Set.new(["a","b","e","f"])
set_a.freeze
set_b.freeze
set_c.freeze
set_d.freeze
data = {
set_a => [list of vertices],
set_b.freeze => [list of vertices],
set_c.freeze => [list of vertices],
set_d.freeze => [list of vertices]
}
However, it's not quite working. Doing:
data[some_set.freeze]
seems to cause errors.
And as long as you don't expose or share the data array there is not reason to freeze it.
You could just use instances of Array as keys. But you will have to ensure that the arrays are all sorted in the same way:
data = {
['a', 'b', 'c'] => [1, 2, 3],
['a', 'b', 'd'] => [1, 2, 4],
['a', 'b'] => [1, 2],
['a', 'b', 'e', 'f'] => [1, 2, 5, 6]
}
data[['a', 'b']]
#=> [1, 2]
Or as sawa mentioned in the comments: It might make sense to use Set to avoid ordering the arrays before using with the data hash. With Set your implementation might look like:
require 'set'
data = {
Set.new(['a', 'b', 'c']) => [1, 2, 3],
Set.new(['a', 'b', 'd']) => [1, 2, 4],
Set.new(['a', 'b']) => [1, 2],
Set.new(['a', 'b', 'e', 'f']) => [1, 2, 5, 6]
}
data[Set.new(['b', 'a'])] # Note that the order doesn't match
#=> [1, 2]

In Ruby how do I sort a hash by its key values in alphabetical order?

Suppose I have a hash,
{"c": 1, "b": 2, "a": 3}
How do I sort the hash so the elements are in order of the key value?
myh = {"c" => 1, "b" => 2, "a" => 3}
myh.sort
=> [["a", 3], ["b", 2], ["c", 1]]
{"c" => 1, "b" => 2, "a" => 3}.sort.to_h

Joining multiple ordered arrays in Ruby

Lets say I have this:
a = [1, 2, 3, 4, 5]
b = ['a', 'b', 'c', 'd', 'e']
c = ['ABC', 'DEF', 'GHI', 'JKL', 'MNO']
And I want this:
d = [[1, 'a', 'ABC'], [2, 'b', 'DEF'], ...]
How can I accomplish this in Ruby?
I tried with .zip
r = []
r.zip(a, b, c)
puts r
But didn't work.
You need to do as below :-
a = [1, 2, 3, 4, 5]
b = ['a', 'b', 'c', 'd', 'e']
c = ['ABC', 'DEF', 'GHI', 'JKL', 'MNO']
a.zip(b,c)
# => [[1, "a", "ABC"], [2, "b", "DEF"], [3, "c", "GHI"], [4, "d", "JKL"], [5, "e", "MNO"]]
One thing to remember here - Array#zip returns an array of size, equal to the size of the receiver array object.
# returns an array of size 2, as the same as receiver array size.
[1,2].zip([1,5,7]) # => [[1, 1], [2, 5]]
# below returns empty array, as the receiver array object is also empty.
[].zip([1,2,3,4,5]) # => []
For the same reason as I explained above r.zip(a, b, c) returns [].
[a,b,c].reduce(:zip).map(&:flatten)
d = [a,b,c].transpose
[[1, "a", "ABC"], [2, "b", "DEF"], [3, "c", "GHI"], [4, "d", "JKL"], [5, "e", "MNO"]]

Ruby - extracting the unique values per key from an array of hashes

From a hash like the below one, need to extract the unique values per key
array_of_hashes = [ {'a' => 1, 'b' => 2 , 'c' => 3} ,
{'a' => 4, 'b' => 5 , 'c' => 3},
{'a' => 6, 'b' => 5 , 'c' => 3} ]
Need to extract the unique values per key in an array
unique values for 'a' should give
[1,4,6]
unique values for 'b' should give
[2,5]
unique values for 'c' should give
[3]
Thoughts ?
Use Array#uniq:
array_of_hashes = [ {'a' => 1, 'b' => 2 , 'c' => 3} ,
{'a' => 4, 'b' => 5 , 'c' => 3},
{'a' => 6, 'b' => 5 , 'c' => 3} ]
array_of_hashes.map { |h| h['a'] }.uniq # => [1, 4, 6]
array_of_hashes.map { |h| h['b'] }.uniq # => [2, 5]
array_of_hashes.map { |h| h['c'] }.uniq # => [3]
This is more generic:
options = {}
distinct_keys = array_of_hashes.map(&:keys).flatten.uniq
distinct_keys.each do |k|
options[k] = array_of_hashes.map {|o| o[k]}.uniq
end

How can I check if a Ruby array includes one of several values?

I have two Ruby arrays, and I need to see if they have any values in common. I could just loop through each of the values in one array and do include?() on the other, but I'm sure there's a better way. What is it? (The arrays both hold strings.)
Thanks.
Set intersect them:
a1 & a2
Here's an example:
> a1 = [ 'foo', 'bar' ]
> a2 = [ 'bar', 'baz' ]
> a1 & a2
=> ["bar"]
> !(a1 & a2).empty? # Returns true if there are any elements in common
=> true
Any value in common ? you can use the intersection operator : &
[ 1, 1, 3, 5 ] & [ 1, 2, 3 ] #=> [ 1, 3 ]
If you are looking for a full intersection however (with duplicates) the problem is more complex there is already a stack overflow here : How to return a Ruby array intersection with duplicate elements? (problem with bigrams in Dice Coefficient)
Or a quick snippet which defines "real_intersection" and validates the following test
class ArrayIntersectionTests < Test::Unit::TestCase
def test_real_array_intersection
assert_equal [2], [2, 2, 2, 3, 7, 13, 49] & [2, 2, 2, 5, 11, 107]
assert_equal [2, 2, 2], [2, 2, 2, 3, 7, 13, 49].real_intersection([2, 2, 2, 5, 11, 107])
assert_equal ['a', 'c'], ['a', 'b', 'a', 'c'] & ['a', 'c', 'a', 'd']
assert_equal ['a', 'a', 'c'], ['a', 'b', 'a', 'c'].real_intersection(['a', 'c', 'a', 'd'])
end
end
Using intersection looks nice, but it is inefficient. I would use "any?" on the first array (so that iteration stops when one of the elements is found in the second array). Also, using a Set on the second array will make membership checks fast. i.e.:
a = [:a, :b, :c, :d]
b = Set.new([:c, :d, :e, :f])
c = [:a, :b, :g, :h]
# Do a and b have at least a common value?
a.any? {|item| b.include? item}
# true
# Do c and b have at least a common value?
c.any? {|item| b.include? item}
#false
Array#intersect? (Ruby 3.1+)
Starting from Ruby 3.1, there is a new Array#intersect? method,
which checks whether two arrays have at least one element in common.
Here is an example:
a = [1, 2, 3]
b = [3, 4, 5]
c = [7, 8, 9]
# 3 is the common element
a.intersect?(b)
# => true
# No common elements
a.intersect?(c)
# => false
Also, Array#intersect? can be much faster than alternatives since it avoids creating an intermediate array, returns true as soon as it finds a common element, it is implemented in C.
Sources:
Ruby 3.1 adds Array#intersect?.
Pull Request.
Discussion.
Source code.
Try this
a1 = [ 'foo', 'bar' ]
a2 = [ 'bar', 'baz' ]
a1-a2 != a1
true

Resources