Given an array containing numbers the following rules apply:
0 removes all previous numbers and all subsequent adjacent even numbers.1 removes all previous numbers and all subsequent adjacent odd numbers.1 it can be removedI am trying to write an algorithm to reduce the array but I could come up only with a bad looking solution:
def compress(array)
zero_or_one_index = array.rindex { |element| [0,1].include? element }
array.slice!(0, zero_or_one_index) if zero_or_one_index
deleting = true
while deleting
deleting = false
array.each_with_index do |element, index|
next if index.zero?
previous_element = array[index - 1]
if (previous_element == 0 && element.even?) ||
(previous_element == 1 && element.odd?)
array.delete_at(index)
deleting = true
break
end
end
end
array.shift if array[0] == 1
end
The problem is that delete_if and similar, start messing up the result, if I delete elements while iterating on the array, therefore I am forced to use a while loop.
Examples:
compress([3, 2, 0]) #=> [0]
compress([2, 0, 4, 6, 7]) #=> [0,7]
compress([2, 0, 4, 1, 3, 6]) #=> [6]
compress([3, 2, 0, 4, 1, 3, 6, 8, 5]) #=> [6,8,5]
This problem arises in the context of some refactorings I am performing on cancancan to optimize the rules definition.
Here is how I would solve the problem:
def compress(arr)
return arr unless idx = arr.rindex {|e| e == 0 || e == 1}
value = arr[idx]
method_options = [:even?,:odd?]
arr[idx..-1].drop_while do |n|
n.public_send(method_options[value])
end.tap {|a| a.unshift(value) if value.zero? }
end
First we find index of the last occurrence of 0 or 1 using Array#rindex. If none then return the Array.
Then we get the value at that index.
Then we use Array#[] to slice off the tail end of the Array starting at the index.
Then drop all the consecutive adjacent :even? or :odd? numbers respective to the value (0 or 1) using Array#drop_while.
Finally if the value is 0 we place it back into the front of the Array before returning.
Examples
compress([3, 2, 0])
#=> [0]
compress([2, 0, 4, 6, 7])
#=> [0,7]
compress([2, 0, 4, 1, 3, 6])
#=> [6]
compress([3, 2, 0, 4, 1, 3, 6, 8, 5])
#=> [6,8,5]
compress([4, 5, 6])
#=> [4,5,6]
compress([0])
#=> [0]
compress([1])
#=> []
If your goal was to be mutative, as your question and gist seem to suggest, I honestly would not change what I have but rather go with:
def compress!(arr)
arr.replace(compress(arr))
end
For example
a = [3, 2, 0, 4, 1, 3, 6, 8, 5]
a == compress!(a)
#=> true
a
#=> [6,8,5]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With