Limit number of duplicate elements within an array to a given value - arrays

In Ruby, if I have an array with duplicates in it and a given value n:
array = [1,56,1,245,56,1,56,1,56,1,56]
n = 2
How could I limit the number of duplicates within the array to n whilst also retaining the positioning of the remaining elements to produce this?
array = [1,56,1,245,56]

def ndup(list, n)
cnts = {}
list.select do |item|
cnts[item] ||= 0
cnts[item] += 1
cnts[item] <= n
end
end
ndup([1,56,1,245,56,1,56,1,56,1,56], 2)
#=> [1, 56, 1, 245, 56]

def ff(array, n)
occurances = Hash.new(0)
array.uniq do |element|
occurances[element] = occurances[element] < n ? occurances[element].next : n
[element, occurances[element]]
end
end

Using a counting hash is the way to go here, but here's an alternative:
array.each_with_index.
group_by(&:first).
values.
flat_map { |a| a.first(n) }.
sort_by(&:last).
map(&:first)
#=> [1, 56, 1, 245, 56]
The steps are as follows.
enum = array.each_with_index
#=> #<Enumerator: [1, 56, 1, 245, 56, 1, 56, 1, 56, 1, 56]:each_with_index>
h = enum.group_by(&:first)
#=> { 1=>[[1, 0], [1, 2], [1, 5], [1, 7], [1, 9]],
# 56=>[[56, 1], [56, 4], [56, 6], [56, 8], [56, 10]],
# 245=>[[245, 3]]}
a = h.values
#=> [[[1, 0], [1, 2], [1, 5], [1, 7], [1, 9]],
# [[56, 1], [56, 4], [56, 6], [56, 8], [56, 10]],
# [[245, 3]]]
b = a.flat_map { |a| a.first(n) }
#=> [[1, 0], [1, 2], [56, 1], [56, 4], [245, 3]]
c = b.sort_by(&:last)
#=> [[1, 0], [56, 1], [1, 2], [245, 3], [56, 4]]
c.map(&:first)
#=> [1, 56, 1, 245, 56]

Related

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

Group every n elements

I have an array:
a = [1,2,3,4,5,6]
I want an array of arrays taking the elements up to a certain amount like:
b = group(a,4) = [[1,2,3,4],[2,3,4,5],[3,4,5,6],[4,5,6],[5,6],[6]]
I tried:
a.each_cons(4) {|x1,x2,x3,x4| b[a.index(x1) = [x1,x2,x3,x4]}
but the resulting array doesn't have the last three elements:
[[1, 2, 3, 4],[2, 3, 4, 5],[3, 4, 5, 6]]
a = [1,2,3,4,5,6]
a.map.with_index { |e, i| a.slice(i, 4) }
#⇒ [1, 2, 3, 4]
#⇒ [2, 3, 4, 5]
#⇒ [3, 4, 5, 6]
#⇒ [4, 5, 6]
#⇒ [5, 6]
#⇒ [6]
a = [1,2,3,4,5,6]
asize = a.size
#=> 6
a.each_index.map { |i| a[i, [4, asize-i].min] }
#=> [[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6], [4, 5, 6], [5, 6], [6]]
The steps are as follows.
asize = a.size
#=> 6
enum0 = a.each_index
#=> #<Enumerator: [1, 2, 3, 4, 5, 6]:each_index>
enum1 = enum0.map
#=> #<Enumerator: #<Enumerator: [1, 2, 3, 4, 5, 6]:each_index>:map>
Have a close look at the return value for enum.map. You will see that it can be thought of as a compound enumerator.
We can see the elements that will be generated by enum1 by converting it to an array.
enum1.to_a
#=> [0, 1, 2, 3, 4, 5]
We can now pass each element generated by enum1 to the block for the mapping.
i = enum1.next
#=> 0
a[i, [4, asize-i].min]
#=> a[0, [4, 6].min]
#=> a[0, 4]
#=> [1,2,3,4]
so the first element of a, 1, is mapped to [1,2,3,4].
The remaining steps are as follows.
i = enum1.next #=> 1
a[i, [4, asize-i].min] #=> [2, 3, 4, 5]
i = enum1.next #=> 2
a[i, [4, asize-i].min] #=> [3, 4, 5, 6]
i = enum1.next #=> 3
a[i, [4, asize-i].min] #=> [4, 5, 6]
i = enum1.next #=> 4
a[i, [4, asize-i].min] #=> [5, 6]
i = enum1.next #=> 5
a[i, [4, asize-i].min] #=> [6]
i = enum1.next #=> StopIteration: iteration reached an end

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)

How to add two multidimensional arrays

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}

Resources