Permutations of multiple arrays - ruby

Currently I can get all permutations of a two item array as such:
[[:a, :b], [:c, :d]].reduce(&:product)
# => [[:a, :c], [:a, :d], [:b, :c], [:b, :d]]
However when I try and do the same for a three item array I do not get the desired result:
[[:a, :b], [:c, :d], [:e, :f]].reduce(&:product)
# => [[[:a, :c], :e], [[:a, :c], :f], [[:a, :d], :e], [[:a, :d], :f]]
The expected result is:
[[:a, :c, :e], [:a, :c, :f], [:a, :d, :e], [:a, :d, :f] ...]

data = [[:a, :b], [:c, :d], [:e, :f]]
data[0].product(*data[1..-1])
# => [[:a, :c, :e], [:a, :c, :f], [:a, :d, :e], [:a, :d, :f], [:b, :c, :e], [:b, :c, :f], [:b, :d, :e], [:b, :d, :f]]
and you can extend your data with more arrays

Since each item, such as [[:a, :c], :e] is almost correct it can be flattened:
[[:a, :b], [:c, :d], [:e, :f]].reduce(&:product).map(&:flatten)
# => [[:a, :c, :e], [:a, :c, :f], [:a, :d, :e], [:a, :d, :f] ...]

Related

Dealing with many [...] in Ruby

In a pure ruby script I have this:
result = JSON.parse result.body_str
count = result && result["ke1"] && result["ke1"]["key2"] && result["ke1"]["key2"]["key3"] && result["ke1"]["key2"]["key3"]["key4"] ?
result["key1"]["key2"]["key3"]["key4"].to_i :
123
Is there any way to simplify this?
count = result["key1"]["key2"]["key3"]["key4"].to_i rescue 123
if you want to make a private method for better readability, you could do
def count(result)
result["key1"]["key2"]["key3"]["key4"].to_i
rescue NoMethodError
123
end
I add the NoMethodError to limit the errors that the rescue can swallow. Despite arguments about
using exceptions for flow control, I prefer this for readability. In a small function or one liner, it technically doesn't even change the flow, as it all remains contained in one location.
If it is used inside a tight loop with millions of records, you may want to compare with other solutions using a profiler, but you have to make that call based on the actual usage. If this is used on a bit of code that may run 5 times a day, stick with what's easier to read and maintain.
I would write it like this, and put it in a module to be included as required.
Code
def value_at_deep_key(hash, path)
path.each_with_index.reduce(hash) do |current, (segment, i) |
case c = current[segment]
when Hash then c
else (i==path.size-1) ? (current.key?(segment) ? c : :NO_MATCH) : {}
end
end
end
Examples
value_at_deep_key({a: {b: {c: "cat"}}}, [:a, :b, :c]) #=> "cat"
value_at_deep_key({a: {b: {c: false}}}, [:a, :b, :c]) #=> false
value_at_deep_key({a: {b: {c: nil}}}, [:a, :b, :c]) #=> nil
value_at_deep_key({z: {b: {c: "cat"}}}, [:a, :b, :c]) #=> :NO_MATCH
value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c]) #=> :NO_MATCH
value_at_deep_key({a: {b: {z: "cat"}}}, [:a, :b, :c]) #=> :NO_MATCH
value_at_deep_key({a: {b: {c: "cat"}}}, [:a, :b]) #=> {:c=>"cat"}
value_at_deep_key({a: {b: {c: "cat"}}}, [:a]) #=> {:b=>{:c=>"cat"}}
value_at_deep_key({z: {b: {c: "cat"}}}, []) #=> {:z=>{:b=>{:c=>"cat"}}}
value_at_deep_key({z: {b: {c: "cat"}}}, [:a, :b, :c]) #=> :NO_MATCH
value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c]) #=> :NO_MATCH
value_at_deep_key({a: {b: {z: "cat"}}}, [:a, :b, :c]) #=> :NO_MATCH
value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c, :d]) #=> :NO_MATCH
One could then write:
val = value_at_deep_key(hash, path)
(val = 123) if (val == :NO_MATCH)
If the value for the last key could not be nil,
else (i==path.size-1) ? (current.key?(segment) ? c : :NO_MATCH) : {}
could be replaced with:
else (i==path.size-1) ? c : {}
in which case nil would be returned when there is no match.
I occasionally define a method like this
def value_at_deep_key hash, path, default=nil
path.inject(hash) {|current,segment| current && current[segment]} || default
end
This uses inject to grab each level of the hash in turn.
You would use like this
value_at_deep_key(result, %w(key1 key2 key3 key4), 123)
Personally I don't like the use of rescue for this sort of thing - it can mask errors.
The accepted answer doesn't work very well:
Here is the code:
def value_at_deep_key(hash, path, default=nil)
path.inject(hash) {|current,segment| current && current[segment]} || default
end
Here are some results:
1)--------------------
h = {
'key1' => {'key2' => {'key3' => {'key4' => 3}}}
}
p value_at_deep_key(h, %w[key1 key2 key3 key4], 123)
--output:--
3
2)--------------------
h = {
'key1' => 1,
'key2' => 2,
'key3' => 3,
'key4' => 4,
}
p value_at_deep_key(h, %w[key1 key2 key3 key4], 123)
--output:--
1.rb:16:in `[]': no implicit conversion of String into Integer (TypeError)
from 1.rb:16:in `block in value_at_deep_key'
from 1.rb:16:in `each'
from 1.rb:16:in `inject'
from 1.rb:16:in `value_at_deep_key'
from 1.rb:19:in `<main>'
3)---------------------
h = {
'key1' => {'key2' => {'key3' => 4}}
}
p value_at_deep_key(h, %w[key1 key2 key3 key4], 123)
--output:--
1.rb:16:in `[]': no implicit conversion of String into Integer (TypeError)
The following answer seems to work better:
def value_at_deep_key(hash, key_sequence, default=nil)
return "No keys to lookup!" if key_sequence.empty?
value = hash
key_sequence.each do |key|
case value
when Hash
value = value[key]
else
value = nil
break
end
end
value.nil? ? default : Integer(value) #A found value of nil produces the default, which is
#also the case when one of the keys doesn't exist in the Hash.
#Because to_i() will silently convert a found string with no leading numbers to 0,
#use Integer() instead, which will throw a descriptive error when trying to convert any String(or Hash or Array) to an int.
end
--output:--
p value_at_deep_key({a: {b: {c: "cat"}}}, [:a, :b, :c], 123) #=> `Integer': invalid value for Integer(): "cat" (ArgumentError)
p value_at_deep_key({a: {b: {c: false}}}, [:a, :b, :c], 123) #=> `Integer': can't convert false into Integer (TypeError)
p value_at_deep_key({a: {b: {c: nil}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({z: {b: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {b: {z: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {b: {c: "cat"}}}, [:a, :b], 123) #=> `Integer': can't convert Hash into Integer (TypeError
p value_at_deep_key({a: {b: {c: "cat"}}}, [:a], 123) #=> `Integer': can't convert Hash into Integer (TypeError)
p value_at_deep_key({z: {b: {c: "cat"}}}, [], 123) #=> "No keys to lookup!"
p value_at_deep_key({z: {b: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {b: {z: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c, :d], 123) #=> 123
p value_at_deep_key(
{'key1' => {'key2' => {'key3' => {'key4' => "4"}}}},
%w[key1 key2 key3 key4],
default=123,
) #=> 4
p value_at_deep_key(
{ 'key1' => {'key2' => {'key3' => "4"}}},
%w[key1 key2 key3 key4],
default=123,
) #=> 123
p value_at_deep_key(
{
'key1' => "1",
'key2' => "2",
'key3' => "3",
'key4' => "4",
},
%w[key1 key2 key3 key4],
default=123,
) #=> 123
p value_at_deep_key(
{ 'key1' => {'key2' => {'key3' => {'key4' => nil}}}},
%w[key1 key2 key3 key4],
default=123,
) #=> 123
p value_at_deep_key(
{'key1' => {'key2' => {'key3' => {'key4' => 'hello'}}}},
%w[key1 key2 key3 key4],
default=123,
) #=> `Integer': invalid value for Integer(): "hello" (ArgumentError)
But maybe the following answer will suit you better:
If you must have:
A found String that looks like a number--converted to an int, or
The default
...in other words no errors, you can do this:
def value_at_deep_key(hash, key_sequence, default=nil)
value = hash
key_sequence.each do |key|
case value
when Hash
value = value[key]
else
value = hash.object_id #Some unique value to signal that the Hash lookup failed.
break
end
end
begin
value == hash.object_id ? default : Integer(value)
rescue TypeError, ArgumentError #If the Hash lookup succeeded, but the value is: nil, true/false, a String that is not all numbers, Array, Hash, an object that neither responds to to_int() nor to_i()
default
end
end
p value_at_deep_key({a: {b: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {b: {c: false}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {b: {c: nil}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({z: {b: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {b: {z: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {b: {c: "cat"}}}, [:a, :b], 123) #=> 123
p value_at_deep_key({a: {b: {c: "cat"}}}, [:a], 123) #=> 123
p value_at_deep_key({z: {b: {c: "cat"}}}, [], 123) #=> 123
p value_at_deep_key({z: {b: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {b: {z: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c, :d], 123) #=> 123
p value_at_deep_key(
{'key1' => {'key2' => {'key3' => {'key4' => "4"}}}},
%w[key1 key2 key3 key4],
default=123,
) #=> 4
p value_at_deep_key(
{ 'key1' => {'key2' => {'key3' => "4"}}},
%w[key1 key2 key3 key4],
default=123,
) #=> 123
p value_at_deep_key(
{
'key1' => "1",
'key2' => "2",
'key3' => "3",
'key4' => "4",
},
%w[key1 key2 key3 key4],
default=123,
) #=> 123
p value_at_deep_key(
{ 'key1' => {'key2' => {'key3' => {'key4' => nil}}}},
%w[key1 key2 key3 key4],
default=123,
) #=> 123
p value_at_deep_key(
{'key1' => {'key2' => {'key3' => {'key4' => [1, 2, 3] }}}},
%w[key1 key2 key3 key4],
default=123,
) #=> 123

Combination up to n

Given an array a, what is the best way to achieve its combinations up to the n-th? For example:
a = %i[a b c]
n = 2
# Expected => [[], [:a], [:b], [:c], [:a, b], [:b, :c], [:c, :a]]
Do as below :
a = %w[a b c]
n = 3
0.upto(n).flat_map { |i| a.combination(i).to_a }
# => [[], ["a"], ["b"], ["c"], ["a", "b"],
# ["a", "c"], ["b", "c"], ["a", "b", "c"]]
Another way:
def all_combis(a, n, b=[])
n.zero? ? b.unshift([]) : all_combis(a, n-1, b.unshift(*a.combination(n)))
end
all_combis(%i[a b c], 0)
#=> [[]]
all_combis(%i[a b c], 1)
#=> [[], [:a], [:b], [:c]]
all_combis(%i[a b c], 2)
#=> [[], [:a], [:b], [:c], [:a, :b], [:a, :c], [:b, :c]]
all_combis(%i[a b c], 3)
#=> [[], [:a], [:b], [:c], [:a, :b], [:a, :c], [:b, :c], [:a, :b, :c]]
If order and efficiency are unimportant, this also works:
a.repeated_combination(n).map(&:uniq) << []
%i[a b c].repeated_combination(2).map(&:uniq) << []
#=> [[:a], [:a, :b], [:a, :c], [:b], [:b, :c], [:c], []]

How to get "string-like" join functionality for arrays in Ruby

Consider the following input:
input = [:a, :b, :c]
# output = input.join_array(:x)
What is a readable and concise way to get the following output (in Ruby):
[:a, :x, :b, :x, :c]
A naive approach:
input = [:a, :b, :c]
input.flat_map{|elem| [elem, :x]}[0...-1] # => [:a, :x, :b, :x, :c]
Without cutting last element:
res = input.reduce([]) do |memo, elem|
memo << :x unless memo.empty?
memo << elem
end
res # => [:a, :x, :b, :x, :c]
Flatten a Product
You can use Array#product to distribute :x throughout your array, and then flatten the result. For example:
input = [:a, :b, :c]
input.product([:x]).flatten
#=> [:a, :x, :b, :x, :c, :x]
Trimming an Array
Assuming your desired result wasn't just a typo that accidentally excluded the last element, you can use Array#pop, Array#slice, or other similar methods to trim the last element from the array. Some examples include:
input.product([:x]).flatten[0...-1]
#=> [:a, :x, :b, :x, :c]
output = input.product([:x]).flatten
output.pop
output
#=> [:a, :x, :b, :x, :c]
What about:
input = [:a, :b, :c]
p input.zip([:x].cycle).flatten[0..-2] #=> [:a, :x, :b, :x, :c]
For fun, we could use join. Not necessarily readable or concise though!
[:a, :b, :c].join('x').chars.map(&:to_sym) # => [:a, :x, :b, :x, :c]
# Or, broken down:
input = [:a, :b, :c]
output = input.join('x') # => "axbxc"
output = output.chars # => ["a", "x", "b", "x", "c"]
output = output.map(&:to_sym) # => [:a, :x, :b, :x, :c]
How is this ?
input = [:a, :b, :c]
p input.each_with_object(:x).to_a.flatten[0..-2]
# >> [:a, :x, :b, :x, :c]

Rearranging elements

What is the easiest way to rearrange the elements of an array by referencing their indice? Something like this:
[:a, :b, :c, :d].rearrange(3, 1, 0, 2) # => [:d, :b, :a, :c]
[:a, :b, :c, :d].rearrange!(3, 1, 0, 2) # => [:d, :b, :a, :c]
Well, Ruby already has this method, only that it's called values_at:
>> [:a, :b, :c, :d].values_at(3, 1, 0, 2)
=> [:d, :b, :a, :c]
I'd turn it around a bit. A single permutation is just a function which maps an Array to another Array so this seems natural:
a = [:a, :b, :c, :d]
permuted = [3, 1, 0, 2].map { |i| a[i] }
That's easy to monkey patch into Array if necessary:
class Array
def permute(*p)
p.map { |i| self[i] }
end
end
Error checking, permute!, and further niceties are left as an exercise.

How to develop a Ruby array

I have a factorised array of n dimensions and I would like to develop it.
Here is an example:
develop([:a, :aa]) #=> [[:a, :aa]]
...which is the same as: [:a].product([:aa]).
Or, more complicated:
develop([:a, [:aa, :bb]]) #=> [[:a, :aa],
[:a, :bb]]
I'm working with Ruby 1.9. Thank you for any idea.
Edit:
Another example, with 3 levels of embedded arrays:
develop([:a, [[:b, [:ba, :bb]],
[:c, [:ca, :cb]],
[:d, [:da, :db]]]]) #=> [[:a, :b, :ba],
[:a, :b, :bb],
[:a, :c, :ca],
[:a, :c, :cb],
[:a, :d, :da],
[:a, :d, :db]]
I wonder if we could use Array's product method (http://ruby-doc.org/core-1.9.3/Array.html#method-i-product), even if we have some embedded arrays.
I'm not sure I fully understand what you are trying to do to these poor arrays, but I managed to make a function that gives the correct output for both the cases you specified. Here is the complete code:
def develop(x)
return x unless x.is_a? Array
y = []
x[1].each do |s|
d = develop(s)
d = [d] unless d.is_a? Array
d.each do |t|
t = [t] unless t.is_a? Array
y << [x.first] + t
end
end
return y
end
x = [:a,
[
[:b, [:ba, :bb]],
[:c, [:ca, :cb]],
[:d, [:da, :db]]
]
]
p develop(x)
p develop [:a, [:aa, :bb]]
The output is:
C:\Users\David\Documents\scraps\test_ruby>ruby test.rb
[[:a, :b, :ba], [:a, :b, :bb], [:a, :c, :ca], [:a, :c, :cb], [:a, :d, :da], [:a, :d, :db]]
[[:a, :aa], [:a, :bb]]
EDIT 1: Here's a shorter version that also gives the right output:
def develop(x)
return [x] unless x.is_a? Array
Array(x.last).collect do |s|
develop(s).collect do |t|
[x.first] + Array(t)
end
end.flatten 1
end

Resources