3

I have an array of similar objects, with an attribute a which can have values b or c. The array can be considered as a collection of rows, where each pair of items in the array represent one row. I've just listed the values of attribute a for simplicity's sake, Example:

array = [c, b, b, c, c, c, b]
# array[0], array[1] is one row (c, b)
# array[2], array[3] is another (b, c)
# ...

There can be no row of just (b, b), and if that is the case then one of the b values must be swapped for the closest c value in the array. If there are no more c values, then the array is valid as long as the b values are left at the end of the array.

The final row of the array can consist of just one value, i. e. (b, ).

Example:

 array = [b, c, b, b, c, b, b, b, c, b, b, c, c]
 # becomes
 array = [b, c, b, c, b, b, b, b, c, b, b, c, c]
 array = [b, c, b, c, b, c, b, b, b, b, b, c, c]
 array = [b, c, b, c, b, c, b, c, b, b, b, b, c]
 array = [b, c, b, c, b, c, b, c, b, c, b, b, b]
 # rows: (b, c), (b, c), (b, c), (b, c), (b, c), (b, b,), (b, )    

This is the solution I came up with, which I don't really like (because it's very imperative and verbose)

while true do
   cand = nil
   array.each_slice(2) do |item, nxt|
     return if nxt.nil?
     # pseudo-code: assume b? returns true for a == b         
     next unless item.b? && nxt.b?
     cand = nxt
     break
   end
   swap_cand = array.slice(array.index(cand), array.length).reject{ |item| item.popular? }.first
   return if swap_cand.nil?
   old_index, new_index = array.index(cand), array.index(swap_cand)
   array[old_index], array[new_index] = array[new_index], array[old_index]
end

A problem I kept running into was that I couldn't mutate an array while iterating over it, necessitating two loops.

edit Cleaned up some of the break statements per the suggestions from @7stud.

11
  • return done = true if nxt.nil? Huh? Do you know what a LocalJumpError is? If the code you posted is actually inside a def, then what does setting done=true do for you? When you return from a def, there's no more loop--no more nothing. Commented Sep 22, 2014 at 19:15
  • I assumed that this would simply set the done variable and subsequently exit the each_slice loop. This is indeed inside a function definition, and as specified by the until loop this outer loop exits when done is true. Commented Sep 22, 2014 at 19:21
  • A problem I kept running into was that I couldn't mutate an array while iterating over it. `results = []; temp = []; arr.each do |obj| results << obj #or temp << obj. Use as many array as you need and shuffle things back and forth between them in your each loop. Commented Sep 22, 2014 at 19:22
  • @7stud Yes, I did that as well, but that looked just as ugly or even uglier than the result I eventually arrived at. If you have a suggestion which looks better I would greatly appreciate you showing it. Commented Sep 22, 2014 at 19:23
  • @7stud you do realise that there are two loops, correct? Commented Sep 22, 2014 at 19:24

1 Answer 1

1

Enumerable#chunk is well-suited for this problem.

Code

def valid?(arr, b)
  arr.chunk { |e| e }
     .map(&:last)[0..-2]
     .select { |e| e.first == b }
     .max_by(&:size)
     .size <= 2
end

Example

b = 0
c = 1
valid?([c, b, b, c, b, b, b], b) #=> true
valid?([c, b, b, b, c, c, b], b) #=> false

Explanation

b = 0
c = 1
arr = [c, b, b, c, b, b, b]
  #=> [1, 0, 0, 1, 0, 0, 0]
enum = arr.chunk { |e| e }
  #=> #<Enumerator: #<Enumerator::Generator:0x0000010205aa70>:each>
enum.to_a # Let's examine the elements of `enum`
  #=> [[1, [1]], [0, [0, 0]], [1, [1]], [0, [0, 0, 0]]]
a = enum.map(&:last)
  #=> [[1], [0, 0], [1], [0, 0, 0]]
d = a[0..-2] # disregard last value, which may or may not be an array of `b`'s
  #=> [[1], [0, 0], [1]]
e = d.select { |e| e.first == b }
  #=> [[0, 0]]
f = e.max_by(&:size)
  #=> [0, 0]
g = f.size
  #=> 2
g <= 2
  #=> true
Sign up to request clarification or add additional context in comments.

7 Comments

Thanks for sharing, would you mind explaining how this approach swaps the values and transforms the array?
I interpreted the question as merely to determine if the array is "valid", without reference to any swapping. Is that correct? Incidentally, I was preparing the explanation when you left your comment.
Sorry if it was unclear! This is from the question (guess it was kinda hidden away) If there is an occurrence of three b values in a row, one of the b values need to be swapped for the closest c value in order for the array to adhere to the rules if one exists.
OK, but I don't think "closest" is unambiguous. If, for example, arr = [b,c,b,b,c,b,b,b,c,b,b,c,c], what would be the desired result?
Good point, I've updated my question to hopefully make it less confusing!
|

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.