Array multiplication - what is the best algorithm? - ruby

I have to make a multiplication of n arrays.
Example :
input = ["a", "b", "c"] * ["1", "2"] * ["&", "(", "$"]
output = ["a1&", "a1(", "a1$", "a2&", "a2(", "a2$", "b1&", "b1(", "b1$", "b2&", "b2(", "b2$, "c1&, "c1(, "c1$, "c2&", "c2(", "c2$"]
I have created an algorithm to do that, it works good.
# input
entries = ["$var1$", "$var2$", "$var3$"]
data = [["a", "b", "c"], ["1", "2"], ["&", "(", "$"]]
num_combinaison = 1
data.each { |item| num_combinaison = num_combinaison * item.length }
result = []
for i in 1..num_combinaison do
result.push entries.join()
end
num_repetition = num_combinaison
data.each_index do |index|
item = Array.new(data[index])
num_repetition = num_repetition / item.length
for i in 1..num_combinaison do
result[i-1].gsub!(entries[index], item[0])
if i % num_repetition == 0
item.shift
item = Array.new(data[index]) if item.length == 0
end
end
end
I'm sure there is a best way to do that, but I don't find it. I have tried to use product or flatten function without success.
Somebody see a best solution ?
Thanks for your help.
Eric

class Array
def * other; product(other).map(&:join) end
end
["a", "b", "c"] * ["1", "2"] * ["&", "(", "$"]
# =>
# ["a1&", "a1(", "a1$", "a2&", "a2(", "a2$", "b1&", "b1(", "b1$", "b2&",
# "b2(", "b2$", "c1&", "c1(", "c1$", "c2&", "c2(", "c2$"]

The best algorithm you can use is implemented by the Array#product method:
data = [["a", "b", "c"], ["1", "2"], ["&", "(", "$"]]
data.first.product(*entries.drop(1)).map(&:join)
# => ["a1&", "a1(", "a1$", "a2&", "a2(", "a2$", ...
Update
A safer alternative, my first solution raises a NoMethodError if data is emtpy:
data.reduce { |result, ary| result.product(ary).map(&:join) }
# => ["a1&", "a1(", "a1$", "a2&", "a2(", "a2$", ...
[].reduce { |r, a| r.product(a).map(&:join) }
# => nil

Related

Ruby non consistent results with scanned string's length

I may not be having the whole picture here but I am getting inconsistent results with a calculation: I am trying to solve the run length encoding problem so that if you get an input string like "AAABBAAACCCAA" the encoding will be: "3A2B3A3C2A" so the functions is:
def encode(input)
res = ""
input.scan(/(.)\1*/i) do |match|
res << input[/(?<bes>#{match}+)/, "bes"].length.to_s << match[0].to_s
end
res
end
The results I am getting are:
irb(main):049:0> input = "AAABBBCCCDDD"
=> "AAABBBCCCDDD"
irb(main):050:0> encode(input)
(a) => "3A3B3C3D"
irb(main):051:0> input = "AAABBBCCCAAA"
=> "AAABBBCCCAAA"
irb(main):052:0> encode(input)
(b) => "3A3B3C3A"
irb(main):053:0> input = "AAABBBCCAAA"
=> "AAABBBCCAAA"
irb(main):054:0> encode(input)
(c) => "3A3B2C3A"
irb(main):055:0> input = "AAABBBCCAAAA"
=> "AAABBBCCAAAA"
irb(main):056:0> encode(input)
(d) => "3A3B2C3A"
irb(main):057:0> input = 'WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB'
=> "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB"
irb(main):058:0> encode(input)
(e) => "12W1B12W1B12W1B"
As you can see, results (a) through (c) are correct, but results (d) and (e) are missing some repetitions and the resulting code is several letters short, can you give a hint as to where to check, please? (I am learning to use 'pry' right now)
Regular expressions are great, but they're not the golden hammer for every problem.
str = "AAABBAAACCCAA"
str.chars.chunk_while { |i, j| i == j }.map { |a| "#{a.size}#{a.first}" }.join
Breaking down what it does:
str = "AAABBAAACCCAA"
str.chars # => ["A", "A", "A", "B", "B", "A", "A", "A", "C", "C", "C", "A", "A"]
.chunk_while { |i, j| i == j } # => #<Enumerator: #<Enumerator::Generator:0x007fc1998ac020>:each>
.to_a # => [["A", "A", "A"], ["B", "B"], ["A", "A", "A"], ["C", "C", "C"], ["A", "A"]]
.map { |a| "#{a.size}#{a.first}" } # => ["3A", "2B", "3A", "3C", "2A"]
.join # => "3A2B3A3C2A"
to_a is there for illustration, but isn't necessary:
str = "AAABBAAACCCAA"
str.chars
.chunk_while { |i, j| i == j }
.map { |a| "#{a.size}#{a.first}" }
.join # => "3A2B3A3C2A"
how do you get to know such methods as Array#chunk_while? I am using Ruby 2.3.1 but cannot find it in the API docs, I mean, where is the compendium list of all the methods available? certainly not here ruby-doc.org/core-2.3.1/Array.html
Well, this is off-topic to the question but it's useful information to know:
Remember that Array includes the Enumerable module, which contains chunk_while. Use the search functionality of http://ruby-doc.org to find where things live. Also, get familiar with using ri at the command line, and try running gem server at the command-line to get the help for all the gems you've installed.
If you look at the Array documentation page, on the left you can see that Array has a parent class of Object, so it'll have the methods from Object, and that it also inherits from Enumerable, so it'll also pull in whatever is implemented in Enumerable.
You only get the count of the matched symbol repetitions that occur first. You need to perform a replacement within a gsub and pass the match object to a block where you can perform the necessary manipulations:
def encode(input)
input.gsub(/(.)\1*/) { |m| m.length.to_s << m[0] }
end
See the online Ruby test.
Results:
"AAABBBCCCDDD" => 3A3B3C3D
"AAABBBCCCAAA" => 3A3B3C3A
"AAABBBCCAAA" => 3A3B2C3A
"AAABBBCCAAAA" => 3A3B2C4A
"WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB" => 12W1B12W3B24W1B

Multiple sub-hashes out of one hash

I have a hash:
hash = {"a_1_a" => "1", "a_1_b" => "2", "a_1_c" => "3", "a_2_a" => "3",
"a_2_b" => "4", "a_2_c" => "4"}
What's the best way to get the following sub-hashes:
[{"a_1_a" => "1", "a_1_b" => "2", "a_1_c" => "3"},
{"a_2_a" => "3", "a_2_b" => "4", "a_2_c" => "4"}]
I want them grouped by the key, based on the regexp /^a_(\d+)/. I'll have 50+ key/value pairs in the original hash, so something dynamic would work best, if anyone has any suggestions.
If you're only concerned about the middle component you can use group_by to get you most of the way there:
hash.group_by do |k,v|
k.split('_')[1]
end.values.map do |list|
Hash[list]
end
# => [{"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3"}, {"a_2_a"=>"3", "a_2_b"=>"4", "a_2_c"=>"4"}]
The final step is extracting the grouped lists and combining those back into the required hashes.
Code
def partition_hash(hash)
hash.each_with_object({}) do |(k,v), h|
key = k[/(?<=_).+(?=_)/]
h[key] = (h[key] || {}).merge(k=>v)
end.values
end
Example
hash = {"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3", "a_2_a"=>"3", "a_2_b"=>"4", "a_2_c"=>"4"}
partition_hash(hash)
#=> [{"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3"},
# {"a_2_a"=>"3", "a_2_b"=>"4", "a_2_c"=>"4"}]
Explanation
The steps are as follows.
enum = hash.each_with_object({})
#=> #<Enumerator: {"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3", "a_2_a"=>"3",
# "a_2_b"=>"4", "a_2_c"=>"4"}:each_with_object({})>
The first element of this enumerator is generated and passed to the block, and the block variables are computed using parallel assignment.
(k,v), h = enum.next
#=> [["a_1_a", "1"], {}]
k #=> "a_1_a"
v #=> "1"
h #=> {}
and the block calculation is performed.
key = k[/(?<=_).+(?=_)/]
#=> "1"
h[key] = (h[key] || {}).merge(k=>v)
#=> h["1"] = (h["1"] || {}).merge("a_1_a"=>"1")
#=> h["1"] = (nil || {}).merge("a_1_a"=>"1")
#=> h["1"] = {}.merge("a_1_a"=>"1")
#=> h["1"] = {"a_1_a"=>"1"}
so now
h #=> {"1"=>{"a_1_a"=>"1"}}
The next value of enum is now generated and passed to the block, and the following calculations are performed.
(k,v), h = enum.next
#=> [["a_1_b", "2"], {"1"=>{"a_1_a"=>"1"}}]
k #=> "a_1_b"
v #=> "2"
h #=> {"1"=>{"a_1_a"=>"1"}}
key = k[/(?<=_).+(?=_)/]
#=> "1"
h[key] = (h[key] || {}).merge(k=>v)
#=> h["1"] = (h["1"] || {}).merge("a_1_b"=>"2")
#=> h["1"] = ({"a_1_a"=>"1"}} || {}).merge("a_1_b"=>"2")
#=> h["1"] = {"a_1_a"=>"1"}}.merge("a_1_b"=>"2")
#=> h["1"] = {"a_1_a"=>"1", "a_1_b"=>"2"}
After the remaining four elements of enum have been passed to the block the following has is returned.
h #=> {"1"=>{"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3"},
# "2"=>{"a_2_a"=>"3", "a_2_b"=>"4", "a_2_c"=>"4"}}
The final step is simply to extract the values.
h.values
#=> [{"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3"},
# {"a_2_a"=>"3", "a_2_b"=>"4", "a_2_c"=>"4"}]

Delete contents of array based on a set of indexes

delete_at only takes a single index. What's a good way to achieve this using built-in methods?
Doesn't have to be a set, can be an array of indexes as well.
arr = ["a", "b", "c"]
set = Set.new [1, 2]
arr.delete_at set
# => arr = ["a"]
One-liner:
arr.delete_if.with_index { |_, index| set.include? index }
Re-open the Array class and add a new method for this.
class Array
def delete_at_multi(arr)
arr = arr.sort.reverse # delete highest indexes first.
arr.each do |i|
self.delete_at i
end
self
end
end
arr = ["a", "b", "c"]
set = [1, 2]
arr.delete_at_multi(set)
arr # => ["a"]
This could of course be written as a stand-alone method if you don't want to re-open the class. Making sure the indexes are in reverse order is very important, otherwise you change the position of elements later in the array that are supposed to be deleted.
Try this:
arr.reject { |item| set.include? arr.index(item) } # => [a]
It's a bit ugly, I think ;) Maybe someone suggest a better solution?
Functional approach:
class Array
def except_values_at(*indexes)
([-1] + indexes + [self.size]).sort.each_cons(2).flat_map do |idx1, idx2|
self[idx1+1...idx2] || []
end
end
end
>> ["a", "b", "c", "d", "e"].except_values_at(1, 3)
=> ["a", "c", "e"]

Hash invert in Ruby?

I've got a hash of the format:
{key1 => [a, b, c], key2 => [d, e, f]}
and I want to end up with:
{ a => key1, b => key1, c => key1, d => key2 ... }
What's the easiest way of achieving this?
I'm using Ruby on Rails.
UPDATE
OK I managed to extract the real object from the server log, it is being pushed via AJAX.
Parameters: {"status"=>{"1"=>["1", "14"], "2"=>["7", "12", "8", "13"]}}
hash = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]}
first variant
hash.map{|k, v| v.map{|f| {f => k}}}.flatten
#=> [{"a"=>:key1}, {"b"=>:key1}, {"c"=>:key1}, {"d"=>:key2}, {"e"=>:key2}, {"f"=>:key2}]
or
hash.inject({}){|h, (k,v)| v.map{|f| h[f] = k}; h}
#=> {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}
UPD
ok, your hash is:
hash = {"status"=>{"1"=>["1", "14"], "2"=>["7", "12", "8", "13"]}}
hash["status"].inject({}){|h, (k,v)| v.map{|f| h[f] = k}; h}
#=> {"12"=>"2", "7"=>"2", "13"=>"2", "8"=>"2", "14"=>"1", "1"=>"1"}
Lots of other good answers. Just wanted to toss this one in too for Ruby 2.0 and 1.9.3:
hash = {apple: [1, 14], orange: [7, 12, 8, 13]}
Hash[hash.flat_map{ |k, v| v.map{ |i| [i, k] } }]
# => {1=>:apple, 14=>:apple, 7=>:orange, 12=>:orange, 8=>:orange, 13=>:orange}
This is leveraging: Hash::[] and Enumerable#flat_map
Also in these new versions there is Enumerable::each_with_object which is very similar to Enumerable::inject/Enumerable::reduce:
hash.each_with_object(Hash.new){ |(k, v), inverse|
v.each{ |e| inverse[e] = k }
}
Performing a quick benchmark (Ruby 2.0.0p0; 2012 Macbook Air) using an original hash with 100 keys, each with 100 distinct values:
Hash::[] w/ Enumerable#flat_map
155.7 (±9.0%) i/s - 780 in 5.066286s
Enumerable#each_with_object w/ Enumerable#each
199.7 (±21.0%) i/s - 940 in 5.068926s
Shows that the each_with_object variant is faster for that data set.
Ok, let's guess. You say you have an array but I agree with Benoit that what you probably have is a hash. A functional approach:
h = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]}
h.map { |k, vs| Hash[vs.map { |v| [v, k] }] }.inject(:merge)
#=> {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}
Also:
h.map { |k, vs| Hash[vs.product([k])] }.inject(:merge)
#=> {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}
In the case where a value corresponds to more than one key, like "c" in this example...
{ :key1 => ["a", "b", "c"], :key2 => ["c", "d", "e"]}
...some of the other answers will not give the expected result. We will need the reversed hash to store the keys in arrays, like so:
{ "a" => [:key1], "b" => [:key1], "c" => [:key1, :key2], "d" => [:key2], "e" => [:key2] }
This should do the trick:
reverse = {}
hash.each{ |k,vs|
vs.each{ |v|
reverse[v] ||= []
reverse[v] << k
}
}
This was my use case, and I would have defined my problem much the same way as the OP (in fact, a search for a similar phrase got me here), so I suspect this answer may help other searchers.
If you're looking to reverse a hash formatted like this, the following may help you:
a = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]}
a.inject({}) do |memo, (key, values)|
values.each {|value| memo[value] = key }
memo
end
this returns:
{"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}
new_hash={}
hash = {"key1" => ['a', 'b', 'c'], "key2" => ['d','e','f']}
hash.each_pair{|key, val|val.each{|v| new_hash[v] = key }}
This gives
new_hash # {"a"=>"key1", "b"=>"key1", "c"=>"key1", "d"=>"key2", "e"=>"key2", "f"=>"key2"}
If you want to correctly deal with duplicate values, then you should use the Hash#inverse
from Facets of Ruby
Hash#inverse preserves duplicate values,
e.g. it ensures that hash.inverse.inverse == hash
either:
use Hash#inverse from here: http://www.unixgods.org/Ruby/invert_hash.html
use Hash#inverse from FacetsOfRuby library 'facets'
usage like this:
require 'facets'
h = {:key1 => [:a, :b, :c], :key2 => [:d, :e, :f]}
=> {:key1=>[:a, :b, :c], :key2=>[:d, :e, :f]}
h.inverse
=> {:a=>:key1, :b=>:key1, :c=>:key1, :d=>:key2, :e=>:key2, :f=>:key2}
The code looks like this:
# this doesn't looks quite as elegant as the other solutions here,
# but if you call inverse twice, it will preserve the elements of the original hash
# true inversion of Ruby Hash / preserves all elements in original hash
# e.g. hash.inverse.inverse ~ h
class Hash
def inverse
i = Hash.new
self.each_pair{ |k,v|
if (v.class == Array)
v.each{ |x|
i[x] = i.has_key?(x) ? [k,i[x]].flatten : k
}
else
i[v] = i.has_key?(v) ? [k,i[v]].flatten : k
end
}
return i
end
end
h = {:key1 => [:a, :b, :c], :key2 => [:d, :e, :f]}
=> {:key1=>[:a, :b, :c], :key2=>[:d, :e, :f]}
h.inverse
=> {:a=>:key1, :b=>:key1, :c=>:key1, :d=>:key2, :e=>:key2, :f=>:key2}
One way to achieve what you're looking for:
arr = [{["k1"] => ["a", "b", "c"]}, {["k2"] => ["d", "e", "f"]}]
results_arr = []
arr.each do |hsh|
hsh.values.flatten.each do |val|
results_arr << { [val] => hsh.keys.first }···
end
end
Result: [{["a"]=>["k1"]}, {["b"]=>["k1"]}, {["c"]=>["k1"]}, {["d"]=>["k2"]}, {["e"]=>["k2"]}, {["f"]=>["k2"]}]

Join common keys in hash one-liner

I have this array of pairs:
[{"a"=>"1"}, {"b"=>"2"}, {"a"=>"3"}, {"b"=>"4"}, {"a"=>"5"}]
I would like a method to merge the keys in common with multiple values to:
[{"a"=>["1","3","5"]}, {"b"=>["2","4"]}]
Improved following Marc-Andre's suggestion.
array = [{"a"=>"1"}, {"b"=>"2"}, {"a"=>"3"}, {"b"=>"4"}, {"a"=>"5"}]
array.group_by(&:keys).map{|k, v| {k.first => v.flat_map(&:values)}}
Or
array.group_by{|h| h.keys.first}.each_value{|a| a.map!{|h| h.values.first}}
Haven't tried it yet, but something like that should also works
a.each_with_object( Hash.new{ |h,k| h[k] = [] } ) do |x, hash|
hash[x.keys.first] << x.values.first
end
edit: For a one liner, and the same output :
[a.each_with_object( Hash.new{ |h,k| h[k] = [] } ) { |x, hash| hash[x.keys.first] << x.values.first }]
A solution to your problem:
array.map(&:first).group_by(&:first).map{|k, v| {k => v.map(&:last)}}
I'm curious as to why you start and end with hashes containing only one key-pair. Arrays would be better suited. E.g.:
other = [["a", "1"], ["b", "2"], ["a", "3"], ["b", "4"], ["a", "5"]]
r = other.group_by(&:first).map{|k, v| [k => v.map(&:last)]}
r # => [["a", ["1", "3", "5"]], ["b", ["2", "4"]]]
Hash[r] # => {"a"=>["1", "3", "5"], "b"=>["2", "4"]}
array = [{"a"=>"1"}, {"b"=>"2"}, {"a"=>"3"}, {"b"=>"4"}, {"a"=>"5"}]
{}.tap{ |r| array.each{ |h| h.each{ |k,v| (r[k]||=[]) << v } } }
Not sure if you're giving out awards for brevity, but I like this way. The merge function with a block is ideally suited for this:
new = {}
array.each {|p| new.merge!(p) {|k,l,r| [l,r].flatten }}

Resources