I have a multidimensional array in Ruby which looks like this:
[[1,12], [1,5], [1,6], [5,12], [6,12], [12,5], [12,6]]
I need to combine it into a new multidimensional array, grouping values together, to form valid combinations of (n) positions (n being the size of each element in the original array):
[
[[1,5,6], [12]], # [1,5,6].product([12]) #=> [[1, 12], [5, 12], [6, 12]]
[[1,12], [5,6]] # [1,12].product([5,6]) #=> [[1, 5], [1, 6], [12, 5], [12, 6]]
]
The purpose of this is to take an array of n-place permutations, and generate the smallest possible multidimensional array that lists the valid numbers in each place (while not including combinations that aren't present in the original array).
How can a multidimensional array containing n-place permutations be reduced down to the array described above? The built-in (and brilliant) array methods in Ruby don't seem to include a function for this, and short of generating every possible combination and then testing them against the original permutations, I'm not sure how to get it right.
I'm not quite sure if I correctly understand the problem, but given this array:
a = [[1, 12], [1, 5], [1, 6], [5, 12], [6, 12], [12, 5], [12, 6]]
I think you can group the left-hand items by the right-hand items:
h1 = Hash.new { |h, k| h[k] = [] }
a.each { |k, v| h1[v] << k }
h1
#=> {12=>[1, 5, 6], 5=>[1, 12], 6=>[1, 12]}
And apply the same transformation again:
h2 = Hash.new { |h, k| h[k] = [] }
h1.each { |k, v| h2[v] << k }
h2
#=> {[1, 5, 6]=>[12], [1, 12]=>[5, 6]}
This gives:
h2.to_a
#=> [
# [[1, 5, 6], [12]],
# [[1, 12], [5, 6]]
# ]
In recent versions of Ruby you could write it as:
a.group_by(&:last).transform_values { |v| v.map(&:first) }
.group_by(&:last).transform_values { |v| v.map(&:first) }
.to_a
#=> [
# [[1, 5, 6], [12]],
# [[1, 12], [5, 6]]
# ]
The condensed array can be expanded via:
[[[1, 5, 6], [12]], [[1, 12], [5, 6]]].flat_map { |a, b| a.product(b) }
#=> [[1, 12], [5, 12], [6, 12], [1, 5], [1, 6], [12, 5], [12, 6]]
Note that this attempt only works for sub-arrays with two elements, but it should get you started.
Related
I have two nested arrays with equal size:
Array1 =[[1, 2], [], [2, 3]]
Array2= [[1, 4], [8, 11], [3, 6]]
I need to merge them in one array, like this:
Array = [[1,2,1,4], [8,11], [2,3,3,6]],
so each elements of new Array[x] = Array1[x] + Array2[x]
I understand how to do it with for(each) cycle, but I am sure Ruby has an elegant solution for that. It is also possible that the solution will produce by changing Array1.
Array1.each_index.map { |i| Array1[i] + Array2[i] }
#=> [[1,2,1,4], [8,11], [2,3,3,6]]
This has the advantage that it avoids the creation of a temporary array [Array1, Array2].transpose or Array1.zip(Array2).
[Array1, Array2].transpose.map(&:flatten)
=> [[1, 2, 1, 4], [8, 11], [2, 3, 3, 6]]
RubyGuides: "Turn Rows Into Columns With The Ruby Transpose Method"
Each step explained:
[Array1, Array2]
=> [[[1, 2], [], [2, 3]],
[[1, 4], [8, 11], [3, 6]]]
Create a grid like array.
[Array1, Array2].transpose
=> [[[1, 2], [1, 4]], [[], [8, 11]], [[2, 3], [3, 6]]]
transpose switches rows and columns (close to what we want)
[Array1, Array2].transpose.map(&:flatten)
=> [[1, 2, 1, 4], [8, 11], [2, 3, 3, 6]]
flatten gets rid of the unnecessary nested arrays (here combined with map to access nested arrays)
I would do something like:
array1 =[[1, 2], [], [2, 3]]
array2= [[1, 4], [8, 11], [3, 6]]
array1.zip(array2).map(&:flatten)
# => [[1, 2, 1, 4], [8, 11], [2, 3, 3, 6]]
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]]
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.
An array consists of 1, 2, and 0s. I am trying to identify the maximum repetition and its starting index within the array.
Example:
2 2 1 0 2 2 2 0 1 1
The method should accept an integer arguement, which can be one of the numbers 1 or 2
If we demonstrate these inputs on above array, the outputs would be:
find_duplicates(2)
=> 3,4
find_duplicates(1)
=> 2,8
where the first number indicates the size of the duplication, and second is the starting index of it.
I tried looping through the array and compare with arr[i+1] or arr[-1], but this is not the correct approach. Any help will be greatly appreciated.
Edit:
I had not pasted what I had tried at the time I asked the question, this is not something I would do if I could feel some confidence on the way I followed:
def find_status(arr,participant)
status = Array.new
#arr is a two dimensional array
for i in 0...arr.length do
current_line=arr[i]
cons=0
for j in 0...current_line.length do
#I worked on lots of if/else/case statements here, this is just one of them
if current_line[j] == participant
cons+=1 #count consecutive
if current_line[j]!=participant
cons=0
end
end
status[i] = cons
end
end
return status
end
def max_run(arr, target)
_,b = arr.each_with_index.
chunk { |n,_| n==target }.
select { |tf,_| tf==true }.
max_by { |_,a| a.size }
b ? [b.size, b.first.last] : nil
end
arr = [1,1,2,2,2,3,1,1,1,1,2,2,2,2,3,3]
max_run(arr,1) #=> [4, 6]
max_run(arr,2) #=> [4, 10]
max_run(arr,3) #=> [2, 14]
max_run(arr,4) #=> nil
For target = 2, the steps are as follows:
enum0 = arr.each_with_index
#=> #<Enumerator: [1, 1, 2, 2, 2, 3, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3]
# :each_with_index>
We can see the elements that will be generated by this enumerator by converting it to an array:
enum0.to_a
#=> [[1, 0], [1, 1], [2, 2], [2, 3], [2, 4], [3, 5], [1, 6], [1, 7], [1, 8],
# [1, 9], [2, 10], [2, 11], [2, 12], [2, 13], [3, 14], [3, 15]]
Continuing,
enum1 = enum0.chunk { |n,_| n==target }
#=> #<Enumerator: #<Enumerator::Generator:0x007f9beb9b0850>:each>
Carefully examine the return value here. You can think of enum1 as a "compound enumerator". It will generate the following values:
enum1.to_a
#=> [[false, [[1, 0], [1, 1]]], [true, [[2, 2], [2, 3], [2, 4]]],
# [false, [[3, 5], [1, 6], [1, 7], [1, 8], [1, 9]]],
# [true, [[2, 10], [2, 11], [2, 12], [2, 13]]], [false, [[3, 14], [3, 15]]]]
Continuing,
c = enum1.select { |tf,_| tf==true }
#=> [[true, [[2, 2], [2, 3], [2, 4]]],
# [true, [[2, 10], [2, 11], [2, 12], [2, 13]]]]
_,b = c.max_by { |_,a| a.size }
#=> [true, [[2, 10], [2, 11], [2, 12], [2, 13]]]
b #=> [[2, 10], [2, 11], [2, 12], [2, 13]]
b ? [b.size, b.first.last] : nil
#=> [[2, 10], [2, 11], [2, 12], [2, 13]] ? [4, [2,10].last]
#=> [4, 10]
a = [2, 2, 1, 0, 2, 2, 2, 0, 1, 1]
longest_sequence =
a.each_index.select{|i| a[i] == 2}.chunk_while{|i, j| i.next == j}.max_by(&:length)
# => [4, 5, 6]
[longest_sequence.length, longest_sequence.first] # => [3, 4]
The solution below is likely most efficient since it is O(N). It walks through an array, collecting the chunks:
arr.each.with_index.reduce({idx:-1, i: -1, len: 0}) do |memo, (e, i)|
memo[:i] = i if memo[:i] == -1 && e == 2 # at the beginning of chunk
memo[:len], memo[:idx] = [i - memo[:i], memo[:i]] \
if memo[:i] >= 0 && i - memo[:i] > memo[:len] # save values if needed
memo[:i] = -1 unless e == 2 # reset index counter
memo
end.reject { |k, _| k == :i } # reject temporary index value
#⇒ {
# :idx => 4,
# :len => 3
# }
To use it as method, accepting a parameter; just wrap the code above with def find_duplicates number and substitute 2 with number in the code above. Yes, it returns hash instead of an array.
I want to do the following:
array1 = [[1, 10], [2, 20], [3, 10], [4, 30]]
array2 = [[1, 10], [2, 10], [3, 5], [4, 10]]
I want to add two arrays in such a way that the second element of each subarray will be added. I want the following output.
result = [[1,20],[2,30],[3,15],[4,40]]
[array1, array2].transpose.map{|(k, v1), (_, v2)| [k, v1 + v2]}
# => [[1, 20], [2, 30], [3, 15], [4, 40]]
Another approach as below :
array1 = [[1,10],[2,20],[3,10],[4,30]]
array2 = [[1,10],[2,10],[3,5],[4,10]]
Hash[array1].merge(Hash[array2]) { |key,old,new| old + new }.to_a
# => [[1, 20], [2, 30], [3, 15], [4, 40]]
Taking the help of merge(other_hash){|key, oldval, newval| block} .
This can be achieved with a combination of Array#zip and Array#map:
result = array1.zip(array2).map { |l, r| [l[0], l[1] + r[1]] }
#=> [[1, 20], [2, 30], [3, 15], [4, 40]]
However, key-value pairs are often best treated as a Hash. Among other operations, this will allow you to #merge:
hash1
#=> {1=>10, 2=>20, 3=>10, 4=>30}
hash2
#=> {1=>10, 2=>10, 3=>5, 4=>10}
result = hash1.merge(hash2) { |_, l, r| l + r }
#=> {1=>20, 2=>30, 3=>15, 4=>40}