How to recursively find permutations of two dimensional array in Ruby - arrays

I have an array with elements that are arrays of varying sizes. For example:
[[3],[11,2],[11,2],[3]]
I would like to find permutations of all of the individual items in the nested arrays. For the array above, I'd like a return value of:
[
[3, 11, 11, 3],
[3, 11, 2, 3],
[3, 2, 11, 3],
[3, 2, 2, 3]
]
I have a solution that works, but it seems particularly long-winded:
array = [[3],[11,2],[11,2],[3]]
array.product(*array).map { |e| e.drop(1) }.uniq
How should I implement a recursive approach to this, and how would that work? I am having trouble wrapping my head around this.

The conventional way of solving this problem is to use the methods Array#product and Array#drop.
arr = [[3], [11,2], [11,2,7], [4]]
arr.first.product(*arr.drop(1))
#=> [[3, 11, 11, 4], [3, 11, 2, 4], [3, 11, 7, 4],
# [3, 2, 11, 4], [3, 2, 2, 4], [3, 2, 7, 4]]
If any element of arr contains duplicates the return value will also contain duplicates. If duplicates are not wanted, use
arr.map(&:uniq).first.product(*arr.drop(1))
The asker has, however, requested a recursive solution. That could be written as follows:
def prod(arr)
return arr if arr.size == 1
t = prod(arr.drop(1))
arr.first.flat_map { |x| t.map { |a| [x] + a } }
end
prod arr
#=> [[3, 11, 11, 4], [3, 11, 2, 4], [3, 11, 7, 4],
# [3, 2, 11, 4], [3, 2, 2, 4], [3, 2, 7, 4]]

Initialization:
#arr = [[3],[11,2],[11,2],[3]]
#perms = []
Function Definition:
def recursion(idx, temp = [])
if (idx == #arr.size) then #perms.push(temp.clone); return end
#arr[idx].each { |x| recursion(idx+1, temp << x); temp.pop }
end
Call :
recursion(0)
p #perms
=> [[3, 11, 11, 3], [3, 11, 2, 3], [3, 2, 11, 3], [3, 2, 2, 3]]

Related

Finding all permutations of numbers plucked from an array which sum to 16

I would like to find all the permutations of plucking 3, 4 or 5 numbers from [2,3,4,5,6,7,8], repeats allowed, such that their sum is 16. So [8,5,3], [8,3,5] and [4,3,3,3,3] are valid permutations. Also circular permutations should be removed so [3,3,3,3,4] wouldn't also be added to the answer.
I can do this in Ruby without allowing repeats like this:
d = [2,3,4,5,6,7,8]
number_of_divisions = [3,4,5]
number_of_divisions.collect do |n|
d.permutation(n).to_a.reject do |p|
p[0..n].inject(0) { |sum,x| sum + x } != 16
end
end
How could I allow repeats so that [3,3,3,3,4] was included?
For all permutations, including duplicates, one might use Array#repeated_permutation:
d = [2,3,4,5,6,7,8]
number_of_divisions = [3,4,5]
number_of_divisions.flat_map do |n|
d.repeated_permutation(n).reject do |p| # no need `to_a`
p.inject(:+) != 16
end
end
or, even better with Array#repeated_combination:
number_of_divisions.flat_map do |n|
d.repeated_combination(n).reject do |p| # no need `to_a`
p.inject(:+) != 16
end
end
There are far fewer repeated combinations than repeated permutations, so let's find the repeated combinations that sum to the given value, then permute each of those. Moreover, by applying uniq at each of several steps of the calculation we can significantly reduce the number of repeated combinations and permutations considered.
Code
require 'set'
def rep_perms_for_all(arr, n_arr, tot)
n_arr.flat_map { |n| rep_perms_for_1(arr, n, tot) }
end
def rep_perms_for_1(arr, n, tot)
rep_combs_to_rep_perms(rep_combs_for_1(arr, n, tot)).uniq
end
def rep_combs_for_1(arr, n, tot)
arr.repeated_combination(n).uniq.select { |c| c.sum == tot }
end
def rep_combs_to_rep_perms(combs)
combs.flat_map { |c| comb_to_perms(c) }.uniq
end
def comb_to_perms(comb)
comb.permutation(comb.size).uniq.uniq do |p|
p.size.times.with_object(Set.new) { |i,s| s << p.rotate(i) }
end
end
Examples
rep_perms_for_all([2,3,4,5], [3], 12)
#=> [[2, 5, 5], [3, 4, 5], [3, 5, 4], [4, 4, 4]]
rep_perms_for_all([2,3,4,5,6,7,8], [3,4,5], 16).size
#=> 93
rep_perms_for_all([2,3,4,5,6,7,8], [3,4,5], 16)
#=> [[2, 6, 8], [2, 8, 6], [2, 7, 7], [3, 5, 8], [3, 8, 5], [3, 6, 7],
# [3, 7, 6], [4, 4, 8], [4, 5, 7], [4, 7, 5], [4, 6, 6], [5, 5, 6],
# [2, 2, 4, 8], [2, 2, 8, 4], [2, 4, 2, 8], [2, 2, 5, 7], [2, 2, 7, 5],
# [2, 5, 2, 7], [2, 2, 6, 6], [2, 6, 2, 6], [2, 3, 3, 8], [2, 3, 8, 3],
# ...
# [3, 3, 3, 7], [3, 3, 4, 6], [3, 3, 6, 4], [3, 4, 3, 6], [3, 3, 5, 5],
# [3, 5, 3, 5], [3, 4, 4, 5], [3, 4, 5, 4], [3, 5, 4, 4], [4, 4, 4, 4],
# ...
# [2, 2, 4, 5, 3], [2, 2, 5, 3, 4], [2, 2, 5, 4, 3], [2, 3, 2, 4, 5],
# [2, 3, 2, 5, 4], [2, 3, 4, 2, 5], [2, 3, 5, 2, 4], [2, 4, 2, 5, 3],
# ...
# [2, 5, 3, 3, 3], [2, 3, 3, 4, 4], [2, 3, 4, 3, 4], [2, 3, 4, 4, 3],
# [2, 4, 3, 3, 4], [2, 4, 3, 4, 3], [2, 4, 4, 3, 3], [3, 3, 3, 3, 4]]
Explanation
rep_combs_for_1 uses the method Enumerable#sum, which made its debut in Ruby v2.4. For earlier versions, use c.reduce(:0) == tot.
In comb_to_perms, the first uniq simply removes duplicates. The second uniq, with a block, removes all but one of the p.size elements (arrays) that can be obtained by rotating any of the other p-1 elements. For example,
p = [1,2,3]
p.size.times.with_object(Set.new) { |i,s| s << p.rotate(i) }
#=> #<Set: {[1, 2, 3], [2, 3, 1], [3, 1, 2]}>

Ruby Program for finding local maxima doesn't pass one test

I have a program for finding peaks (local maxima) in ruby that passes all but one test. Personally, I think my program is ok, but maybe I'm not taking into account an assumed neighborhood size that isn't specified in the problem, but was suggested by someone else who also attempted it.
Here's what I have so far.
def pick_peaks(arr)
pos = []
peaks =[]
peak_set = {pos: [], peaks: []}
for i in 1..arr.length-2
if arr[i-1] < arr[i] && arr[i] >= arr[i+1]
unless edge_plateau?(arr, i)
peak_set[:pos] << i
peak_set[:peaks] << arr[i]
end
end
end
peak_set_alt = peak_set.collect{|k,v| [k.to_s, v]}.to_h
peak_set_alt
end
def edge_plateau?(array, position)
edge_plateau_left = true
edge_plateau_right = true
i = 1
until i == position
edge_plateau_left = false if array[0] != array[i]
i += 1
end
i = array.length-2
until i == position
edge_plateau_right = false if array[i] != array.last
i -= 1
end
edge_plateau_left or edge_plateau_right
end
Here's the test that it needs to pass but I don't know the original array, so that's a bit of a challenge.
Expected: {"pos"=>[2, 7, 14, 20], "peaks"=>[5, 6, 5, 5]}, instead got: {"pos"=>[2, 7, 11, 14, 20], "peaks"=>[5, 6, 3, 5, 5]}
I'm getting an extra peak in the middle but that should be ok if it's a local maxima, right?
UPDATE
Thanks to a suggestion I found the test array
[1, 2, 5, 4, 3, 2, 3, 6, 4, 1, 2, 3, 3, 4, 5, 3, 2, 1, 2, 3, 5, 5, 4, 3]
This is a more Ruby-like way to find the local maxima.
Code
def locale_maxima(arr)
last_idx = arr.size - 1
peaks, pos =
([[-Float::INFINITY, nil]] +
arr.each_with_index.reject { |v,i| i < last_idx && v == arr[i+1] } +
[[-Float::INFINITY, nil]]
).each_cons(3).
select { |(n1,_), (n2,_), (n3,_)| n1 < n2 && n2 > n3 }.
map { |_,max_pair,_| max_pair }.
transpose
{ pos: pos, peaks: peaks }
end
Example
arr = [1, 2, 5, 4, 3, 2, 3, 6, 4, 1, 2, 3, 3, 4, 5, 3, 2, 1, 2, 3, 5, 5, 4, 3]
locale_maxima arr
#=> { :pos =>[2, 7, 14, 21],
# :peaks=>[5, 6, 5, 5] }
Explanation
The steps are as follows.
last_idx = arr.size - 1
#=> 23
Where there are consecutive equal values, which may represent inflection points (a complication), remove all but the last. To report the indices of the local maxima we therefore need to save indices before removing the duplicates.
b = arr.each_with_index.reject { |v,i| i < last_idx && v == arr[i+1] }
#=> [[1, 0], [2, 1], [5, 2], [4, 3], [3, 4], [2, 5], [3, 6], [6, 7],
# [4, 8], [1, 9], [2, 10], [3, 12], [4, 13], [5, 14], [3, 15],
# [2, 16], [1, 17], [2, 18], [3, 19], [5, 21], [4, 22], [3, 23]]
Notice that [3,11] and [5, 20] have been removed.
Tack on pairs at the beginning and end that cannot be local maxima (nil is arbitrary).
c = [[-Float::INFINITY, nil]] + b + [[-Float::INFINITY, nil]]
#=> [[-Infinity, nil], [1, 0], [2, 1], [5, 2], [4, 3], [3, 4], [2, 5], [3, 6],
# [6, 7], [4, 8], [1, 9], [2, 10], [3, 12], [4, 13], [5, 14], [3, 15], [2, 16],
# [1, 17], [2, 18], [3, 19], [5, 21], [4, 22], [3, 23], [-Infinity, nil]]
Use Enumerable#each_cons to produce an enumerator that will be used to identify the local maxima.
d = c.each_cons(3)
#=> #<Enumerator:
# [[-Infinity, nil], [1, 0], [2, 1], [5, 2], [4, 3], [3, 4], [2, 5],
# [3, 6], [6, 7], [4, 8], [1, 9], [2, 10], [3, 12], [4, 13], [5, 14],
# [3, 15], [2, 16], [1, 17], [2, 18], [3, 19], [5, 21], [4, 22],
# [3, 23], [-Infinity, nil]]:each_cons(3)>
e = d.select { |(n1,_), (n2,_), (n3,_)| n1 < n2 && n2 > n3 }
#=> [[[2, 1], [5, 2], [4, 3]],
# [[3, 6], [6, 7], [4, 8]],
# [[4, 13], [5, 14], [3, 15]],
# [[3, 19], [5, 21], [4, 22]]]
f = e.map { |_,max_pair,_| max_pair }
#=> [[5, 2], [6, 7], [5, 14], [5, 21]]
peaks, pos = f.transpose
#=> [[5, 6, 5, 5], [2, 7, 14, 21]]
{ pos: pos, peaks: peaks }
#=> {:pos=>[2, 7, 14, 21], :peaks=>[5, 6, 5, 5]}
Well there are more than one bug in your code.
Try your code on following data
pick_peaks([0,1,10,1,2,2,3,1,10,1,0])
You'll get
{"pos"=>[2, 4, 6, 8], "peaks"=>[10, 2, 3, 10]}
Obviously 2 here is a bug. So the source of the bug here is arr[i] >= arr[i+1]
Also unless it is shomehow explicitly stated in the task, you seem to handle edges wrong. Consider
pick_peaks([0,0,10,1,2,2,3,1,10,0,0])
You'll get
{"pos"=>[4, 6], "peaks"=>[2, 3]}
missing both 10 on the left and on the right.
Without exact task it is hard to say definitely but at the first glance it seems that your algorithm is to complicated both in terms of code and Big-O. Why you don't just go once through the array and track whether your are climbing up, staying plain or going down?
Update
Here is a piece of code to illustrate my last suggestion. I'm not good at Ruby so my example code would be in JavaScript so you can run it in your browser console:
function pick_peaks(arr) {
var prevUp = true; //let the start be a peak
var peaks = [];
var lastUpInd = 0;
for(var i = 0; i < arr.length-1; i++) {
if (arr[i] < arr[i+1]) {
prevUp = true;
lastUpInd = i + 1;
}
else if (arr[i] > arr[i+1]) {
if(prevUp) {
for(var j = lastUpInd; j <= i; j++) {
peaks.push([j, arr[j]]);
}
}
prevUp = false;
}
}
// additionally handle the end to let it be a peak
if(prevUp)
{
for(var j = lastUpInd; j <= i; j++) {
peaks.push([j, arr[j]]);
}
}
return peaks;
}
If you don't want the ends to be possible peaks just init prevUp with false and remove last if with inner for after a comment

Ruby - collect same numbers from array into array of arrays

I have created a very ugly script to collect same numbers from an array. I don't think this is a very Ruby way :) Anyone could provide a more clean solution?
ar = [5, 5, 2, 2, 2, 6, 6]
collections = []
collect_same = []
while ar.length > 0
first = ar.values_at(0).join.to_i
second = ar.values_at(1).join.to_i
if ar.length == 1
collect_same << ar[0]
collections << collect_same
break
else
sum = ar.values_at(0, 1).inject {|a,b| a + b}
if second == first
p collect_same << ar[0]
ar.shift
else
collect_same << ar[0]
collections << collect_same
collect_same = []
ar.shift
end
end
end
p collections
The output:
=> [[5, 5], [2, 2, 2], [6, 6]]
Note, that in primary array same numbers always goes one after another.
So I wouldn't have primary array like this - ar = [1, 2, 1, 2]
Using chunk_while:
[5, 5, 2, 2, 2, 6, 6].chunk_while(&:==).to_a
#=> [[5, 5], [2, 2, 2], [6, 6]]
Ruby prior to 2.3:
[5, 5, 2, 2, 2, 6, 6].each_with_object([]) do |e, acc|
acc.last && acc.last.last == e ? acc.last << e : acc << [e]
end
#=> [[5, 5], [2, 2, 2], [6, 6]]
In case if you want to do it without order:
ar.group_by(&:itself).values
=> [[5, 5], [2, 2, 2], [6, 6]]
[5, 5, 2, 2, 2, 6, 6].slice_when(&:!=).to_a
#=> [[5, 5], [2, 2, 2], [6, 6]]
One could perhaps say that Enumerable#chunk_while and Enumerable#slice_when are ying and yang.
Prior to Ruby v2.3, one might write
[5, 5, 2, 2, 2, 6, 6].chunk(&:itself).map(&:last)
and prior to v2.2,
[5, 5, 2, 2, 2, 6, 6].chunk { |n| n }.map(&:last)
just another oneliner
arr = [5, 5, 2, 2, 2, 6, 6]
arr.uniq.map {|e| [e]*arr.count(e) }
# => [[5, 5], [2, 2, 2], [6, 6]]

Ruby: match first, second, this etc elements from a dimensional array

I have an array of arrays. I want to concatenate the first, second, third elements of arrays.
Example arrays:
a = [[4, 5, 6], [1, 2, 3], [8, 9, 10]]
a1 = [[1, 2, 3], [8, 9, 10]]
a2 = [[4, 5, 6], [1, 2, 3], [8, 9, 10], [11, 21, 31]]
Output:
out of a: [[4,1,8],[5,2,9],[6,3,10]]
out of a1: [[1,8],[2,9],[3,10]]
out of a2: [[4,1,8,11],[5,2,9,21],[6,3,10,31]]
Use transpose method
a.transpose
=> [[4, 1, 8], [5, 2, 9], [6, 3, 10]]
Array#transpose:
[a, a1, a2].map(&:transpose)
# [
# [[4, 1, 8], [5, 2, 9], [6, 3, 10]],
# [[1, 8], [2, 9], [3, 10]],
# [[4, 1, 8, 11], [5, 2, 9, 21], [6, 3, 10, 31]]
# ]
Whenever Array#transpose can be used so can Enumerable#zip.
a.first.zip *a.drop(1)
#=> [[4,1,8],[5,2,9],[6,3,10]]

Reverse a mult dimensional array - functional programming style

I was stumped coming up with a functional way to reverse a multi-dimmensional (even dimensions) array in Ruby.
input: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
output: [[7, 4, 1], [8, 5, 2], [9, 6, 3]]
This iterative solution works.
def reverse(arr)
size = arr.length
output = Array.new(size) { Array.new(size,0) }
arr.reverse.each_with_index do |a, i|
a.each_with_index do |a, j|
output[j][i] = a
end
end
output
end
Anyone have any insight into how to do using more of functional programming style and without referring to an explicit index?
If array is your input, then it is as simple as
result = array.transpose.map(&:reverse)
if I understand your desired output correctly. ;)
To elaborate a bit: Array#transpose basically "mirrors" the 2D array along the main diagonal:
transposed = array.transpose #=> [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
You seem to want that only with all the rows reversed, which is handled by the call to map:
result = transposed.map(&:reverse) #=> [[7, 4, 1], [8, 5, 2], [9, 6, 3]]
The map(&:reverse) syntax is only shorthand for map { |a| a.reverse } and is enabled by this method.
Doing it by hand
After my initial answer it turned out in the comments that the OP is actually after a functional implementation of transpose. Here is what I came up with:
def transpose(a)
(0...a[0].length).map { |i|
(0...a.length).map { |j| a[j][i] }
}
end
Although this does refer to explicit indices, it is a pure function composed of other pure functions, so it at least meets my definition of functional. ;)
ar = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
ar.reverse.transpose # => [[7, 4, 1], [8, 5, 2], [9, 6, 3]]
arr = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
arr_rev = arr.reverse
#=> [[7, 8, 9], [4, 5, 6], [1, 2, 3]]
arr_rev.first.zip *arr_rev[1..-1]
#=> [[7, 4, 1], [8, 5, 2], [9, 6, 3]]
I believe this satisfies the requirements of functional programming.
The steps:
arr_rev = arr.reverse
#=> [[7, 8, 9], [4, 5, 6], [1, 2, 3]]
arr_rev.first.zip(arr_rev[1..-1])
#=> [7, 8, 9].zip(*[[4, 5, 6], [1, 2, 3]])
#. [7, 8, 9].zip([4, 5, 6], [1, 2, 3])
#. [[7, 4, 1], [8, 5, 2], [9, 6, 3]]
See Enumerable#zip.

Resources