array x array matrix in ruby - ruby

I'd like to convert from
{'key1' => (1..10) ,
'key2' => (11..20) ,
'key3' => (21..30)}
to
[{'key1' => 1, 'key2' => 11, 'key3' => 21},
{'key1' => 1, 'key2' => 11, 'key3' => 22},...
.
.
{'key1' => 10, 'key2' => 20, 'key3' => 30}]
How to solve it?

Here it is :
hsh = {'key1' => (1..10) ,'key2' => (11..20) , 'key3' => (21..30)}
keys = hsh.keys
hsh['key1'].to_a.product(hsh['key2'].to_a,hsh['key3'].to_a).map{|a|Hash[keys.zip(a)]}
# => [{'key1' => 1, 'key2' => 11, 'key3' => 21},
# {'key1' => 1, 'key2' => 11, 'key3' => 22},...
# .
# .
# {'key1' => 10, 'key2' => 20, 'key3' => 30}]
You could also write the above as below,when you have more number of keys:
hsh = {'key1' => (1..10) ,'key2' => (11..20) , 'key3' => (21..30)}
keys = hsh.keys
array = hsh.values_at(*keys[1..-1]).map(&:to_a)
hsh['key1'].to_a.product(*array).map{|a|Hash[keys.zip(a)]}

So many ways... A kiss answer (edited to extend to any number of keys):
s = {'key1' => (1..10), 'key2' => (11..20), 'key3' => (21..30)}
r = []
s.each {|k,v| a = []; (v.to_a).each {|i| a << {k=>i}}; r << a}
result = r.shift
r.each {|e| result = result.product(e).map(&:flatten)}
result

h = {
'key1' => (1..10),
'key2' => (11..20),
'key3' => (21..30)
}
h.map { |k,v| [k].product(v.to_a) }.transpose.map { |e| Hash[e] }
#=> [{"key1"=>1, "key2"=>11, "key3"=>21},
# {"key1"=>2, "key2"=>12, "key3"=>22},
# {"key1"=>3, "key2"=>13, "key3"=>23},
# {"key1"=>4, "key2"=>14, "key3"=>24},
# {"key1"=>5, "key2"=>15, "key3"=>25},
# {"key1"=>6, "key2"=>16, "key3"=>26},
# {"key1"=>7, "key2"=>17, "key3"=>27},
# {"key1"=>8, "key2"=>18, "key3"=>28},
# {"key1"=>9, "key2"=>19, "key3"=>29},
# {"key1"=>10, "key2"=>20, "key3"=>30}]

h = {'key1' => (1..10) ,
'key2' => (11..20) ,
'key3' => (21..30)}
arrays = h.values.map(&:to_a).transpose
p arrays.map{|ar| Hash[h.keys.zip(ar)] }
#=> [{"key1"=>1, "key2"=>11, "key3"=>21}, {"key1"=>2, "key2"=>12, "key3"=>22},...

h = {'key1' => (1..10), 'key2' => (11..20), 'key3' => (21..30)}
Edit 1: made some changes, principally the use of inject({}):
f,*r = h.map {|k,v| [k].product(v.to_a)}
f.zip(*r).map {|e| e.inject({}) {|h,a| h[a.first] = a.last; h}}
Edit 2: After seeing the use of Hash[] in #Phrogz's answer to another question:
f,*r = h.map {|k,v| [k].product(v.to_a)}
f.zip(*r).map {|e| Hash[*e.flatten]}

Lazier way of doing the same:
h = {
'key1' => (1..10),
'key2' => (11..20),
'key3' => (21..30)
}
result = ( 0...h.values.map( &:to_a ).map( &:size ).max ).map do |i|
Hash.new { |hsh, k| hsh[k] = h[k].to_a[i] }
end
result[1]['key3'] #=> 22

Related

Most performant way to group/summarise two hashes?

I have two hashes with some data that I need to aggregate. The first one is a mapping of which ids (id_1, id_2, id_3, id_4) belong under what category (a, b, c):
hash_1 = {'a' => ['id_1','id_2'], 'b' => ['id_3'], 'c' => ['id_4']}
The second hash holds values of how many events happened per id for a given date (date_1, date_2, date_3):
hash_2 = {
'id_1' => {'date_1' => 5, 'date_2' => 6, 'date_3' => 8},
'id_2' => {'date_1' => 0, 'date_3' => 6},
'id_3' => {'date_1' => 0, 'date_2' => nil, 'date_3' => 1},
'id_4' => {'date_1' => 10, 'date_2' => 1}
}
What I want is to get the total event per category (a,b,c). For the above example, the result would look something like:
hash_3 = {'a' => (5+6+8+0+6), 'b' => (0+0+1), 'c' => (10+1)}
My problem is, that there are about 5000 categories, each pointing to typically 1 to 3 ids, and each ID having event counts for 30 dates or more. So this takes quite a bit of computation. What will be the most performant (time effective) way to do this grouping in Ruby?
update
This is what I tried so far (took like 6-8 seconds!, horribly slow):
def total_clicks_per_category
{}.tap do |res|
hash_1.each do |cat, ids|
res[cat] = total_event_per_ids(ids)
end
end
end
def total_event_per_ids(ids)
ids.reduce(0) do |memo, id|
events = hash_2.fetch(id, {})
memo + (events.values.reduce(:+) || 0)
end
end
P.S. I’m using Ruby 2.3.
I'm writing this on a phone so I cannot test right now, but it looks OK.
g = hash_2.each_with_object({}) { |(k,v),g| g[k] = v.values.compact.sum }
hash_3 = hash_1.each_with_object({}) { |(k,v),h| h[k] = g.values_at(*v).sum }
First, create an intermediate hash that holds the sum of hash_2:
hash_4 = hash_2.map{|k, v| [k, v.values.inject(:+)]}.to_h
# => {"id_1"=>19, "id_2"=>6, "id_3"=>1, "id_4"=>11}
Then do the final summation:
hash_3 = hash_1.map{|k, v| [k, v.map{|k| hash_4[k]}.inject(:+)]}.to_h
# => {"a"=>25, "b"=>1, "c"=>11}
Theory
5000*3*30 isn't that many. Ruby probably will need a second at most for this kind of job.
Hash lookup is fast by default, you won't be able to optimize much.
You could pre-calculate hash_2_sum, though :
hash_2_sum = {
'id_1' => 5+6+8,
'id_2' => 0+6,
'id_3' => 0+0+1,
'id_4' => 10+1
}
A loop on hash1 with hash_2_sum lookup, and you're done.
Code
Your example has been updated with some nil values. You need to remove them with compact, and make sure the sum is 0 when no element is found with inject(0, :+):
hash_1 = {'a' => ['id_1','id_2'], 'b' => ['id_3'], 'c' => ['id_4']}
hash_2 = {
'id_1' => { 'date_1' => 5, 'date_2' => 6, 'date_3' => 8 },
'id_2' => { 'date_1' => 0, 'date_3' => 6 },
'id_3' => { 'date_1' => 0, 'date_2' => nil, 'date_3' => 1 },
'id_4' => { 'date_1' => 10, 'date_2' => 1 }
}
hash_2_sum = hash_2.each_with_object({}) do |(key, dates), sum|
sum[key] = dates.values.compact.inject(0, :+)
end
hash_3 = hash_1.each_with_object({}) do |(key, ids), sum|
sum[key] = hash_2_sum.values_at(*ids).inject(0, :+)
end
# {"a"=>25, "b"=>1, "c"=>11}
Note
{}.tap do |res|
hash_1.each do |cat, ids|
res[cat] = total_event_per_ids(ids)
end
end
isn't very readable IMHO.
You can either use each_with_object or Array#to_h :
result = [1, 2, 3].each_with_object({}) do |i, hash|
hash[i] = i * i
end
#=> {1=>1, 2=>4, 3=>9}
result = [1, 2, 3].map { |i| [i, i * i] }.to_h
#=> {1=>1, 2=>4, 3=>9}

Create a combination of a array of hash

I have an array like:
hasharray=[{"a" => "b" } , {"c" => "d"} , {"e" => "f"}]
I want to create all combinations of this array hash of length min to max.
For instance, with min=0 and max=2, the code should return:
resultarray=[
{},
{"a" => "b" },
{"c" => "d"},
{"e" => "f"},
{"a" => "b" } , {"c" => "d"},
{"c" => "d"} , {"e" => "f"},
{"a" => "b" },{"e" => "f"}
]
How do I do it?
min = 0
max = 2
min.upto(max).flat_map {|n| hasharray.combination(n).to_a }
# => [
# [],
# [{"a"=>"b"}], [{"c"=>"d"}], [{"e"=>"f"}],
# [{"a"=>"b"}, {"c"=>"d"}], [{"a"=>"b"}, {"e"=>"f"}], [{"c"=>"d"}, {"e"=>"f"}]
# ]

select and delete from hash in ruby

In a method I get a list of options passed in. Some are related to a particular scope.
I want to store those special keys in another hash to be able to pass it to a different method, and delete them from the original hash.
(I'm actually writing a rails simple_form custom input, but that doesn't matter)
I have the following code:
all_options = { :key1 => 1, :key2 => 2, :something_else => 42 }
my_keys = [:key1, :key2, :key3, :key4]
my_options = all_options.select {|k,v| my_keys.include?(k)}
all_options.delete_if {|k,v| my_keys.include?(k)}
# expecting
my_options == { :key1 => 1, :key2 => 2 }
all_options == { :something_else => 42 }
Now my question is there a better, i.e. smarter way of doing it?
Maybe it's just sugar, but I want to know.
all_options = { :key1 => 1, :key2 => 2, :something_else => 42 }
my_keys = [:key1, :key2, :key3, :key4]
my_options = my_keys.inject({}) {|h,k| h[k] = all_options.delete(k) if all_options.key?(k);h}
all_options
# => {:something_else=>42}
my_options
# => {:key1=>1, :key2=>2}
here's a way to improve Ju Liu's answer:
all_options = { :key1 => 1, :key2 => 2, :something_else => 42 }
my_keys = [:key1, :key2, :key3, :key4]
my_options = all_options.extract!(*my_keys).keep_if {|k,v| v}
all_options
# => {:something_else=>42}
my_options
# => {:key1=>1, :key2=>2}
however you'll lose your options if any key in a all_options hash has an actual value of nil or false (don't know if you need to keep them):
all_options = { :key1 => 1, :key2 => nil, :something_else => 42 }
here's a way to keep false's
my_options = all_options.extract!(*my_keys).keep_if {|k,v| !v.nil?}
p.s. it would be possible to keep all values including nils if you store the keys from all_options:
all_options = { :key1 => 1, :key2 => 2, :something_else => 42 }
all_keys = all_options.keys
my_keys = [:key1, :key2, :key3, :key4]
my_options = all_options.extract!(*my_keys).keep_if {|k,v| all_keys.include?(k)}
all_options
# => {:something_else=>42}
my_options
# => {:key1=>1, :key2=>2}
Maybe the extract! method in active_support could work?
I know only Ruby. So here my Ruby approach :
all_options = { :key1 => 1, :key2 => 2, :something_else => 42 }
my_keys = [:key1, :key2, :key3, :key4]
#below statement is your my_options
Hash[my_keys.map{|i| [i,all_options.delete(i)] if all_options.has_key? i }.compact]
# => {:key1=>1, :key2=>2}
all_options
# => {:something_else=>42}
all_options = { key1: 1, key2: 2, something_else: 42 }
my_keys = [:key1, :key2, :key3, :key4]
my_options = my_keys.each_with_object({}) do |key, hash|
hash[key] = all_options.delete(key) if all_options.key?(key)
end

Convert SQL result to hash with ID keys with Ruby

I have the following array (SQL result):
[
{:id => 1, :field1 => "one", :field2 => "two"},
{:id => 2, :field1 => "one", :field2 => "two"},
...
]
What I want is:
{
1 => {:field1 => "one", :field2 => "two"},
2 => {:field1 => "one", :field2 => "two"},
...
}
Now I do like the following:
data = {}
result.each do |row|
data[row[:id]] = {:field1 => row[:field1], :field2 => row[:field2]}
end
I'm absolutely sure that's wrong way. What is the best way to do it with Ruby? Is there are any snippet like map or something else?
Hash[arr.map { |h| [h.delete(:id), h] }]
One line :)
hash = arr.clone.each_with_object({}) { |e,res| res[e.delete(:id)] = e }
clone is for not destroying arr variable
Something like this, maybe?
arr = [
{:id => 1, :field1 => "one", :field2 => "two"},
{:id => 2, :field1 => "one", :field2 => "two"}
]
hash = arr.each_with_object({}) do |el, memo|
id = el.delete(:id)
memo[id] = el
end
hash # => {1=>{:field1=>"one", :field2=>"two"}, 2=>{:field1=>"one", :field2=>"two"}}

How do I create a diff of hashes with a correction factor?

I want to compare hashes inside an array:
h_array = [
{:name => "John", :age => 23, :eye_color => "blue"},
{:name => "John", :age => 22, :eye_color => "green"},
{:name => "John", :age => 22, :eye_color => "black"}
]
get_diff(h_array, correct_factor = 2)
# should return [{:eye_color => "blue"}, {:eye_color => "green"}, {:eye_color => "black"}]
get_diff(h_array, correct_factor = 3)
# should return
# [[{:age => 23}, {:age => 22}, {:age => 22}],
# [{:eye_color => "blue"}, {:eye_color => "green"}, {:eye_color => "black"}]]
I want to diff the hashes contained in the h_array. It looks like a recursive call/method because the h_array can have multiple hashes but with the same number of keys and values. How can I implement the get_diff method?
def get_diff h_array, correct_factor
h_array.first.keys.reject{|k|
h_array.map{|h| h[k]}.sort.chunk{|e| e}.map{|_,e| e.size}.max >= correct_factor
}.map{|k|
h_array.map{|hash| hash.select{|key,_| k == key}}
}
end
class Array
def find_ndups # also returns the number of items
uniq.map { |v| diff = (self.size - (self-[v]).size); (diff > 1) ? [v, diff] : nil}.compact
end
end
h_array = [
{:name => "John", :age => 22, :eye_color => "blue", :hair => "black"},
{:name => "John", :age => 33, :eye_color => "orange", :hair => "green"},
{:name => "John", :age => 22, :eye_color => "black", :hair => "yello"}
]
def get_diff(h_array, correct_factor)
temp = h_array.inject([]){|result, element| result << element.to_a}
master_array = []
unmatched_arr = []
matched_arr = []
temp = temp.transpose
temp.each_with_index do |arr, index|
ee = arr.find_ndups
if ee.length == 0
unmatched_arr << temp[index].inject([]){|result, arr| result << {arr.first => arr.last} }
elsif ee.length > 0 && ee[0][1] != correct_factor && ee[0][1] < correct_factor
return_arr << temp[index].inject([]){|result, arr| result << {arr.first => arr.last} }
elsif ee[0][1] = correct_factor
matched_arr << temp[index].inject([]){|result, arr| result << {arr.first => arr.last} }
end
end
return [matched_arr, unmatched_arr]
end
puts get_diff(h_array, 2).inspect
hope it helps
found this ActiveSupport::CoreExtensions::Hash::Diff module.
ActiveSupport 2.3.2 and 2.3.4 has a built in Hash::Diff module which returns a hash that represents the difference between two hashes.

Resources