3

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.

1

1 Answer 1

3

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]
Sign up to request clarification or add additional context in comments.

1 Comment

I didn't know about drop_while. Nice solution!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.