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

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

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]}>

How to recursively find permutations of two dimensional array in Ruby

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]]

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.

Finding index and size of consecutive repeated elements in an array

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.

A method for an array

What is the most concise and explicit way to write a method for this?
Given an array a of numbers and a number n, find the n consecutive elements of a whose sum is the largest.
Return the largest sum and the index of the first element in the group.
For example, with a = [1, 1, 1, 1, 1, 1, 1, 2] and n = 2, the
result would be a sum 3 and position 6.
arr = [1,3,2,4,3,5,2,1,3,4,2,5,1]
size = 3
Inefficient but pretty
arr.each_cons(size).with_index.map { |a,i| [a.inject(:+), i] }.max_by(&:first)
#=> [12, 3]
Efficient but whupped with an ugly stick1
tot = arr[0,size].inject(:+)
(1..arr.size-size).each_with_object([tot, 0]) do |i, best|
tot += arr[i+size-1] - arr[i-1]
best.replace([tot, i]) if tot > best.first
end
#=> [12, 3]
Steps performed by the pretty one
enum0 = arr.each_cons(size)
#=> #<Enumerator: [1, 3, 2, 4, 3, 5, 2, 1, 3, 4, 2, 5, 1]:each_cons(3)>
enum1 = enum0.with_index
#=> #<Enumerator: #<Enumerator: [1, 3, 2, 4, 3, 5, 2, 1, 3, 4, 2, 5, 1]:
# each_cons(3)>:with_index>
Carefully examine the above return value for enum1. You will see it is effectively a "compound" enumerator. We can see the values that enum1 will generate and pass to map by converting it to an array:
enum1.to_a
#=> [[[1, 3, 2], 0], [[3, 2, 4], 1], [[2, 4, 3], 2], [[4, 3, 5], 3],
# [[3, 5, 2], 4], [[5, 2, 1], 5], [[2, 1, 3], 6], [[1, 3, 4], 7],
# [[3, 4, 2], 8], [[4, 2, 5], 9], [[2, 5, 1], 10]]
Continuing:
b = enum1.map { |a,i| [a.inject(:+), i] }
#=> [[6, 0], [9, 1], [9, 2], [12, 3], [10, 4], [8, 5],
# [6, 6], [8, 7], [9, 8], [11, 9], [8, 10]]
Note the since the first element of enum1 that map passes to the block is [[1, 3, 2], 0], the two block variables are assigned as follows (using parallel or multiple assignment):
a, i = [[1, 3, 2], 0]
#=> [[1, 3, 2], 0]
a #=> [1, 3, 2]
i #=> 0
and the block calculation is performed:
[a.inject(:+), i]
#=> [6, 0]
Lastly,
b.max_by(&:first)
#=> [12, 3]
Enumerable#max_by determines the largest value among
b.map(&:first)
#=> [6, 9, 9, 12, 10, 8, 6, 8, 9, 11, 8]
Steps performed by the less pretty one
a = arr[0,size]
#=> [1, 3, 2]
tot = a.inject(:+)
#=> 6
enum = (1..arr.size-size).each_with_object([tot, 0])
#=> (1..13-3).each_with_object([6, 0])
#=> #<Enumerator: 1..10:each_with_object([6, 0])>
enum.to_a
#=> [[1, [6, 0]], [2, [6, 0]], [3, [6, 0]], [4, [6, 0]], [5, [6, 0]],
# [6, [6, 0]], [7, [6, 0]], [8, [6, 0]], [9, [6, 0]], [10, [6, 0]]]
enum.each do |i, best|
tot += arr[i+size-1] - arr[i-1]
best.replace([tot, i]) if tot > best.first
end
#=> [12, 3]
The first element of enum, [1, [6, 0]], is passed to the block, assigned to the block variables and the block calculation is performed:
i, best = [1, [6, 0]]
#=> [1, [6, 0]]
i #=> 1
best
#=> [6, 0]
tot += arr[i+size-1] - arr[i-1]
# tot = 6 + arr[1+3-1] - arr[1-1]
# = 6 + 4 - 1
# = 9
best.replace([tot, i]) if tot > best.first
#=> best.replace([9, 1]) if 9 > 6
#=> [9, 1]
best
#=> [9, 1]
The remaining calculations are similar.
1 Credit to Bo Diddley (at 2:51)

Resources