How do I create every combination of single elements selected from multiple arrays? - ruby

I have 5 arrays:
["A", "B", "C"]
["A", "B", "C", "D", "E"]
["A"]
["A", "B", "C", "D", "E", "F"]
["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"]
I would like to create a list of each combination as such:
["AAAAA","AAAAB","AAAAC", "AAAAD"...
"BAAAA","BAAAB","BAAAC", "BAAAD"...]

a = [
["A", "B", "C"],
["A", "B", "C", "D", "E"],
["A"],
["A", "B", "C", "D", "E", "F"],
["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"]
]
a.inject(&:product).map(&:join)
# => ["AAAAA", "AAAAB", "AAAAC", ..., "CEAFM", "CEAFN", "CEAFO"]
Thanks to bluexuemei for the improved answer. The original solution was a.shift.product(*a).map(&:join).
A More Traditional Solution
With such a convenient library, these ruby one-liners seem almost like cheating.
Here is a more traditional way to solve this common problem that can be readily coded into other programming languages:
N = a.reduce(1) { |product,list| product * list.size } # 1350
combinations = []
0.upto(N-1) do |q|
combo = []
a.reverse.each do |list|
q, r = q.divmod list.size
combo << list[r]
end
combinations.push combo.reverse.join
end
combinations
# => ["AAAAA", "AAAAB", "AAAAC", ..., "CEAFM", "CEAFN", "CEAFO"]
The basic idea is to first calculate the total number of combinations N which is just the product of the length of all the lists. Each integer from 0 to N-1 then encodes all the information needed to provide unique indices into each list to produce each combination. One way to think of it is that the index variable q can be expressed as a 5-digit number, where each digit is in a different base, where the base is the size of the corresponding list. That is, the first digit is base-3, the second digit is base-5, the 3rd is base-1 (always 0), the 4th is base-6, and the 5th is base-15. To extract these values from q, this is just taking a series of repeated integer divisions and remainders, as done in the inner loop. Naturally this requires some homework, perhaps looking at simpler examples, to fully digest.

a.reduce(&:product).map(&:join).size

Related

Add elements of an array to a specific index of each array within an array of arrays

I have an array of arrays that serves as a table of data, and am trying to add an extra array as though adding an extra column to the table.
For simplicity, suppose the first array is a
a = [["a", "b", "c"], ["e", "f", "g"], ["i", "j", "k"]]
and the second array is b
b = ["d", "h", "l"]
the desired output is:
c = [["a", "b", "c", "d"], ["e", "f", "g", "h"], ["i", "j", "k", "l"]]
I have tried using + and some attempts at using map but cannot get it
You can zip them together which will create array elements like [["a", "b", "c"], "d"] and then just flatten each element.
a.zip(b).map(&:flatten)
#=> [["a", "b", "c", "d"], ["e", "f", "g", "h"], ["i", "j", "k", "l"]]
Answer improved as per Cary's comment. I think he's done Ruby stuff before.
a.zip(b).map { |arr,e| arr + [e] }
#=> [["a", "b", "c", "d"],
# ["e", "f", "g", "h"],
# ["i", "j", "k", "l"]]
The intermediate calculation is as follows.
a.zip(b)
#=> [[["a", "b", "c"], "d"],
# [["e", "f", "g"], "h"],
# [["i", "j", "k"], "l"]]
See Array#zip.
You can use #each_with_index combined with #map to iterate over the array a and append respective elements of array b
> a.each_with_index.map{|e, i| e | [b[i]] }
=> [["a", "b", "c", "d"], ["e", "f", "g", "h"], ["i", "j", "k", "l"]]

Re-order ruby array

I have a ruby array:
["A", "C", "B", "D", "F", "E"]
User will supply an input, e.g.
input = "B"
I want to shift the values in the array, so the first item of the array equals input, and get the result of a new array:
["B", "D", "F", "E", "A", "C"]
User will be choosing from a dropdown options, so they can only choose the letters from original array.
You can use Array#rotate.
arr = ["A", "C", "B", "D", "F", "E"]
arr.rotate(arr.index('B'))
#=> ["B", "D", "F", "E", "A", "C"]

How to convert the following into array or hash?

a=b&c=d&e=f&g=h
How to extract this into [a,b,c,d,e,f,g,h]
I know I can use split, but it looks like it can only use only one delimit.
or how to convert into a hash?
split FTW (i.e. the most straightforward, simple way of doing this is):
irb(main):001:0> s = "a=b&c=d&e=f&g=h"
=> "a=b&c=d&e=f&g=h"
irb(main):002:0> s.split(/[=&]/)
=> ["a", "b", "c", "d", "e", "f", "g", "h"]
Other interesting ways of abusing Ruby:
irb(main):001:0> s = "a=b&c=d&e=f&g=h"
=> "a=b&c=d&e=f&g=h"
irb(main):002:0> s.split('=').collect{|x| x.split('&')}.flatten
=> ["a", "b", "c", "d", "e", "f", "g", "h"]
irb(main):003:0> ['=','&'].inject(s) {|t, n| t.split(n).join()}.split('')
=> ["a", "b", "c", "d", "e", "f", "g", "h"]
Also check Cary's and GamesBrainiac's answers for more alternatives :)
You can make a hash very easily with something like this:
myHash = {}
strSplit = "a=b&c=d&e=f&g=h".split("&")
for pair in strSplit
keyValueSplit = pair.split("=")
myHash[keyValueSplit[0]] = keyValueSplit[1]
end
myHash will look like this in the end {"a"=>"b", "c"=>"d", "e"=>"f", "g"=>"h"}
#Mirea's answer is best, but here's another:
s = "a=b&c=d&e=f&g=h"
s.scan /[a-z]/
#=> ["a", "b", "c", "d", "e", "f", "g", "h"]
The regex could of course be adjusted as required. For example:
"123a=b&c=d&E=f&g=h".scan /[A-Za-z0-9]/
#=> ["1", "2", "3", "a", "b", "c", "d", "E", "f", "g", "h"]
or
"1-2-3a=$b&c=d&e=f&g=h".scan /[^=&]/
#=> ["1", "-", "2", "-", "3", "a", "$", "b", "c", "d", "e", "f", "g", "h"]
and so on.
If strings of characters are desired just append + to the character class:
"123a=b&ccc=d&E=f&gg=h".scan /[A-Za-z0-9]+/
#=> ["123a", "b", "ccc", "d", "E", "f", "gg", "h"]
If the string has the alternating form shown in the example, these work as well:
(0..s.size).step(2).map { |i| s[i] }
#=> ["a", "b", "c", "d", "e", "f", "g", "h"]
s.each_char.each_slice(2).map(&:first)
#=> ["a", "b", "c", "d", "e", "f", "g", "h"]
I would use is gsub.
irb(main):001:0> s = "a=b&c=d&e=f&g=h"
=> "a=b&c=d&e=f&g=h"
irb(main):004:0> s.gsub(/[\=\&]/, " ").split()
=> ["a", "b", "c", "d", "e", "f", "g", "h"]
So, what we're doing here is replacing all occurrences of = and & with a single space. We then simply split the string.

How to reject and push the elements to an array at a time

For Example i have an array like ["a", "b", "c", "d"]. I want add "a", "e" to that array. In that time how to "a" will get reject and how to "e" will push to that existing array.
Use the union (|) operator:
["a", "b", "c", "d"] | ["a", "e"]
# => ["a", "b", "c", "d", "e"]

ruby array custom combinations

What is a good way to get from this:
['a','b','c',['d1','d2']]
to this:
[['a','b','c','d1']['a','b','c','d2']]
another example, from this:
[['a1','a2'],'b',['c1','c2']]
to this:
[['a1','b','c1'],['a1','b','c2'],['a2','b','c1'],['a2','b','c2']]
edit 1:
Sorry for the confusion and thanks for response so far, the individual contents of the array items doesn't matter but the order must be preserved. The method needs to work for both example because the nested array can be in any position of the outer array, and the nested array can have more the 2 elements.
It's sort of like a regex with multiple or conditions
ab(c|d)
expand to match abc and abd
It is a bit hard to know exactly what you want, but this produces something quite similar:
# Create a list:
a = [['a1','a2'],'b',['c1','c2']]
# Split it into sub-arrays and single values:
list, other = a.partition{|x|x.is_a? Array}
# Split the array in order to get the product:
list_first, list_rest = list.pop, list
# Get the product and add the others_values:
p list_first.product(*list_rest).map{|list| list+other}
#=> [["c1", "a1", "b"], ["c1", "a2", "b"], ["c2", "a1", "b"], ["c2", "a2", "b"]]
1st:
arr1 = ['a','b','c',['d1','d2']]
*a, b = arr1
# ["a", "b", "c", ["d1", "d2"]]
a
# ["a", "b", "c"]
b
# ["d1", "d2"]
b.map{|x| a+[x]}
# [["a", "b", "c", "d1"], ["a", "b", "c", "d2"]]
and 2nd:
a, b, c = [["a1", "a2"], "b", ["c1", "c2"] ]
a.product c
#=> [["a1", "c1"], ["a1", "c2"], ["a2", "c1"], ["a2", "c2"]]
a.product(c).map{|x| x<<b}
#=> [["a1", "c1", "b"], ["a1", "c2", "b"], ["a2", "c1", "b"], ["a2", "c2", "b"]]
#or little less readable:
a.product(c).map{|x| [ x[0], b, x[1] ]}
# [["a1", "b", "c1"], ["a1", "b", "c2"], ["a2", "b", "c1"], ["a2", "b", "c2"]]
hirolau got me really close, here is what I ended with so the order is preserved:
# given a sample list
sample = [['a','b'],'c','d',['e','f'],'g',['h','i']]
# partition out the arrays
arrs, non_arrays = sample.partition {|sample| sample.is_a? Array}
# work out all possible products
first_elem, *the_rest = arrs
products = first_elem.product(*the_rest)
# finally swap it back in to get all valid combinations with order preserved
combinations = []
products.each do |p|
combinations << sample.map {|elem| elem.is_a?(Array) ? p.shift : elem}
end
# combinations
=> [["a", "c", "d", "e", "g", "h"],
["a", "c", "d", "e", "g", "i"],
["a", "c", "d", "f", "g", "h"],
["a", "c", "d", "f", "g", "i"],
["b", "c", "d", "e", "g", "h"],
["b", "c", "d", "e", "g", "i"],
["b", "c", "d", "f", "g", "h"],
["b", "c", "d", "f", "g", "i"]]

Resources