If I have an array:
%w(a b c d e)
=> ["a","b","c","d","e"]
I can get some combinations with
irb(main):071:0> %w(a b c d e).combination(3).to_a
=> [["a", "b", "c"], ["a", "b", "d"], ["a", "b", "e"], ["a", "c", "d"], ["a", "c", "e"], ["a", "d", "e"], ["b", "c", "d"], ["b", "c", "e"], ["b", "d", "e"], ["c", "d", "e"]]
However that is not ALL the combinations, just the unique ones, e.g. ["e", "a", "b"] is missing
When I similarly try with s smaller array I only get one result:
irb(main):059:0> %w(a b c).combination(3).to_a
=> [["a", "b", "c"]]
How can I get all 6 combinations, i.e. for ['a', 'b', 'c'] I want to get
[['a','b','c'], ['a','c','b'], ['b', 'a', 'c'], ['b', 'c', 'a'], ['c', 'a', 'b'], ['c', 'b', 'a']
Similarly for [1,2,3,4] if I want all the 3 digit combo I should get
irb(main):074:0> [[1,2,3],[1,2,4],[1,3,2],[1,3,4],[1,4,2],[1,4,3], [2,1,3],[2,1,4],[2,3,4],[2,3,1],[2,4,1],[2,4,2], [3,1,2],[3,1,4],[3,2,3],[3,2,4],[3,4,2],[3,4,1]]
?
You are looking for permutation instead of combination.
In combinations, we do not care about the order of the elements, and only care about the presence of all the elements in the set.
[1,2,3,4].permutation(3).to_a
#=> [[1, 2, 3], [1, 2, 4], [1, 3, 2], [1, 3, 4], [1, 4, 2], [1, 4, 3], [2, 1, 3], [2, 1, 4], [2, 3, 1], [2, 3, 4], [2, 4, 1], [2, 4, 3], [3, 1, 2], [3, 1, 4], [3, 2, 1], [3, 2, 4], [3, 4, 1], [3, 4, 2], [4, 1, 2], [4, 1, 3], [4, 2, 1], [4, 2, 3], [4, 3, 1], [4, 3, 2]]
Related
I am working in Ruby 2.7.
I have an Array, whose elements are one of three things: either a String or an Integer, or occasionally another Array with a pair of elements (this time, only Strings or Integers). There are no further levels of nesting for Arrays, by the definition of my problem.
Examples:
w = [1, 2, 'b', 4]
x = [[2, 'r'],[2, 'g']]
y = [[2, 'w']]
z = ['u', 6, [2, 'r']]
The context of the problem is that the inner arrays represent selectable alternatives for use in the outer array. I am looking for all possible ways to choose an element from each inner array, if present, and substitute that element into the outer array.
Examples:
f(w) = [ [1, 2, 'b', 4] ]
f(x) = [ [2, 2], [2, 'g'], ['r', 2], ['r', 'g'] ]
f(y) = [ [2], ['w'] ]
f(z) = [ ['u', 6, 2], ['u', 6, 'r'] ]
Order of elements in each selection (inner array of each solution) does not matter. Order of solutions in output also does not matter.
I can brute force an answer selecting/rejecting, transforming, and using Array#product; but I am looking for an approach that is as brief and elegant as possible.
The rules given in the question are unprecise and incomplete, so I've done my best to infer what they are.
Code
def doit(arr)
if arr.size == 1
e = arr.first
return e.is_a?(Array) ? (arr.map { |f| [f] }) : arr
end
(first, *rest), other = arr.partition { |e| e.is_a?(Array) }
return [arr] if first.nil?
prod = first.product(*rest)
return prod if other.empty?
prod.flat_map { |a| other.map { |e| [e, *a] } }
end
See Enumerable#partition, Array#product and Enumerable#flat_map.
Examples
doit [1, 2, 'b', 4]
#=> [[1, 2, "b", 4]]
doit [[2, 'r'],[2, 'g']]
#=> [[2, 2], [2, "g"], ["r", 2], ["r", "g"]]
doit [:a, [2, 'r'], :b, [2, 'g']]
#=> [[:a, 2, 2], [:b, 2, 2], [:a, 2, "g"], [:b, 2, "g"],
# [:a, "r", 2], [:b, "r", 2], [:a, "r", "g"], [:b, "r", "g"]]
doit [[2, 'w']]
#=> [[[2, "w"]]]
doit ['u', 6, [2, 'r']]
#=> [["u", 2], [6, 2], ["u", "r"], [6, "r"]]
doit [:a, [2, 3], :b, :c, [4, 5], :d, [6, 7]]
#=> [[:a, 2, 4, 6], [:b, 2, 4, 6], [:c, 2, 4, 6], [:d, 2, 4, 6],
# [:a, 2, 4, 7], [:b, 2, 4, 7], [:c, 2, 4, 7], [:d, 2, 4, 7],
# [:a, 2, 5, 6], [:b, 2, 5, 6], [:c, 2, 5, 6], [:d, 2, 5, 6],
# [:a, 2, 5, 7], [:b, 2, 5, 7], [:c, 2, 5, 7], [:d, 2, 5, 7],
# [:a, 3, 4, 6], [:b, 3, 4, 6], [:c, 3, 4, 6], [:d, 3, 4, 6],
# [:a, 3, 4, 7], [:b, 3, 4, 7], [:c, 3, 4, 7], [:d, 3, 4, 7],
# [:a, 3, 5, 6], [:b, 3, 5, 6], [:c, 3, 5, 6], [:d, 3, 5, 6],
# [:a, 3, 5, 7], [:b, 3, 5, 7], [:c, 3, 5, 7], [:d, 3, 5, 7]]
Note that the return value for doit [[2, 'w']] is different than that given in the question.
Explanation
The steps are as follows.
arr = [:a, [2, 'r'], :b, [2, 'g']]
(first, *rest), other = arr.partition { |e| e.is_a?(Array) }
#=> [[[2, "r"], [2, "g"]], [:a, :b]]
Ruby applies array decomposition to the above expression to obtain values for first, rest and other:
first
#=> [2, "r"]
rest
#=> [[2, "g"]]
other
#=> [:a, :b]
Continuing, because first.nil? #=> false in return [arr] if first.nil?, we do not return. Next:
prod = first.product(*rest)
#=> [[2, 2], [2, "g"], ["r", 2], ["r", "g"]]
other.empty? #=> false in return prod if other.empty? so we do not return.
I can best explain the remaining calculations by inserting puts statement in the code and running it.
prod.flat_map do |a|
puts "a = #{a}"
other.map do |e|
puts " e = :#{e}"
puts " [e, *a] = #{[e, *a]}"
[e, *a]
end
end
#=> [[:a, 2, 2], [:b, 2, 2], [:a, 2, "g"], [:b, 2, "g"],
# [:a, "r", 2], [:b, "r", 2], [:a, "r", "g"], [:b, "r", "g"]]
The following is displayed.
a = [2, 2]
e = :a
[e, *a] = [:a, 2, 2]
e = :b
[e, *a] = [:b, 2, 2]
a = [2, "g"]
e = :a
[e, *a] = [:a, 2, "g"]
e = :b
[e, *a] = [:b, 2, "g"]
a = ["r", 2]
e = :a
[e, *a] = [:a, "r", 2]
e = :b
[e, *a] = [:b, "r", 2]
a = ["r", "g"]
e = :a
[e, *a] = [:a, "r", "g"]
e = :b
[e, *a] = [:b, "r", "g"]
I have:
stuff = [1, 2, "a", "b", "c", "d", 4, 5, "z", "l", "m", "l", 5, 4, 4, 77]
Numbers come in groups of multiples of two, and letters come in groups of multiples of four.
I want to group numbers in twos and letters in fours like this:
stuff_processed = [
[1, 2],
["a", "b", "c", "d"],
[4, 5],
["z", "l", "m", "l"],
[5, 4],
[4, 77]
]
The order inside of an array that holds numbers or letters is important, the order between the arrays I do not care about.
I know stuff.each_slice(2).to_a will take me part of the way. I can't figure out how to get all the way to what I need though.
stuff
.chunk(&:class)
.flat_map{|klass, a| a.each_slice(klass == Fixnum ? 2 : 4).to_a}
# => [[1, 2], ["a", "b", "c", "d"], [4, 5], ["z", "l", "m", "l"], [5, 4], [4, 77]]
arr = [1, 2, "a", "b", "c", "d", 4, 5, "z", "l", "m", "l", "s", "t",
"u", "v", 5, 4, 4, 77, 91, 65]
H = { Fixnum=>1, String=>3 }
count = 0
arr.slice_when do |a,b|
if a.class == b.class && count < H[a.class]
count += 1
false
else
count = 0
true
end
end.to_a
# => [[1, 2], ["a", "b", "c", "d"], [4, 5], ["z", "l", "m", "l"],
# ["s", "t", "u", "v"], [5, 4], [4, 77], [91, 65]]
See Enumerable#slice_when, which first appeared in Ruby v2.2.
This Array#conditional_slice method accepts a Block and returns an Enumerator :
stuff = [1, 2, "a", "b", "c", "d", 4, 5, "z", "l", "m", "l", 5, 4, 4, 77]
class Array
def conditional_slice(&block)
clone = self.dup
Enumerator.new do |yielder|
until clone.empty? do
yielder << clone.shift(block_given? ? block.call(clone.first) : 1)
end
end
end
end
sliced_stuff = stuff.conditional_slice{|x| x.is_a?(Numeric) ? 2 : 4}
puts sliced_stuff.to_a.inspect
# => [[1, 2], ["a", "b", "c", "d"], [4, 5], ["z", "l", "m", "l"], [5, 4], [4, 77]]
How would you go about sorting two arrays the same way?
hey = %w[e c f a d b g]
hoo = [1,2,3,4,5,6,7]
hey.sort #=> [a,b,c,d,e,f,g]
hoo.same_sort #=> [4,6,2,5,1,3,7]
Have a try:
hey.zip(hoo).sort
=> [["a", 4], ["b", 6], ["c", 2], ["d", 5], ["e", 1], ["f", 3], ["g", 7]]
hey.zip(hoo).sort.transpose
=> [["a", "b", "c", "d", "e", "f", "g"], [4, 6, 2, 5, 1, 3, 7]]
You can do this with a single sort, using Enumerable#sort_by and Array#values_at:
sorted_indices = hey.each_index.sort_by { |i| hey[i] }
#=> [3, 5, 1, 4, 0, 2, 6]
hey.values_at(*sorted_indices)
#=> ["a", "b", "c", "d", "e", "f", "g"]
hoo.values_at(*sorted_indices)
#=> [4, 6, 2, 5, 1, 3, 7]
Given:
a = [[1,"a"],[2,"b"]]
b = [[3,"c"],[4,"d"]]
I want to turn a into [[1,"a"],[2,"b"][3,"c"],[4,"d"]]. How can do this without +? It creates a new array, which I want to avoid.
(a << b).flatten(1)
# => [1, "a", 2, "b", [3, "c"], [4, "d"]]
a.concat(b)
...............................
> b.inject(a, :<<)
#=> [[1, "a"], [2, "b"], [3, "c"], [4, "d"]]
a = [[1,"a"],[2,"b"]]
b = [[3,"c"],[4,"d"]]
a[a.length, 0] = b
a
# > [[1, "a"], [2, "b"], [3, "c"], [4, "d"]]
concat is the answer, but you could do this:
a.object_id #=> 70223889895340
a.replace(a+b) #=> [[1, "a"], [2, "b"], [3, "c"], [4, "d"]]
a #=> [[1, "a"], [2, "b"], [3, "c"], [4, "d"]]
a.object_id #=> 70223889895340
What about?
a.push(b.shift) while b.any?
How about this?
a + b
=> [[1, "a"], [2, "b"], [3, "c"], [4, "d"]]
Is there an elegant way to turn a nested array of the form
[["a", 1], ["a", 2], [nil, 3], [nil, 4], ["b", 6], ["b", 8]]
into a hash of the form
{"a" => [1,2], nil => [3,4], "b" => [6,8]}
This is one way:
arr = [["a", 1], ["a", 2], [nil, 3], [nil, 4], ["b", 6], ["b", 8]]
h = Hash.new {|hash, key| hash[key] = []}
arr.each {|e| h[e[0]] << e[1]}
p h #=> {"a"=>[1, 2], nil=>[3, 4], "b"=>[6, 8]}
ary = [['a', 1], ['a', 2], [nil, 3], [nil, 4], ['b', 6], ['b', 8]]
ary.group_by(&:first).
# => { 'a' => [['a', 1], ['a', 2]], nil => [[nil, 3], [nil, 4]], 'b' => [['b', 6], ['b', 8]] }
map {|k, v| [k, v.map(&:last)] }.
# => [['a', [1, 2]], [nil, [3, 4]], ['b', [6, 8]]]
to_h
# => { 'a' => [1, 2], nil => [3, 4], 'b' => [6, 8] }
One way could be:
array = [['a', 1], ['a', 2], [nil, 3], [nil, 4], ['b', 6], ['b', 8]]
array.each_with_object(Hash.new{|h,k| h[k] = []}) {|a, obj| obj[a.first] << a.last }
# => {"a"=>[1, 2], nil=>[3, 4], "b"=>[6, 8]}
array.each_with_object({}){|a, h| (h[a.first]||=[] )<< a.last }
ary = [['a', 1], ['a', 2], [nil, 3], [nil, 4], ['b', 6], ['b', 8]]
ary.group_by(&:first).map {|k, v| {k => v.map(&:last)} }