Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remove next elements in array with ruby

Tags:

arrays

ruby

Given an array containing numbers the following rules apply:

  • a 0 removes all previous numbers and all subsequent adjacent even numbers.
  • a 1 removes all previous numbers and all subsequent adjacent odd numbers.
  • if the first element of the array is 1 it can be removed

I 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.

like image 207
coorasse Avatar asked Sep 23 '26 21:09

coorasse


1 Answers

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]
like image 85
engineersmnky Avatar answered Sep 26 '26 16:09

engineersmnky