2

How can I refactor the following code to avoid repetition?

  r = @lots.fetch @lots.keys.sample      
  until (neighbours r)
    r = @lots.fetch @lots.keys.sample
  end

I basically have a new r object that is picked randomly and I need to pick r till the selected one doesn't respond to certain criteria (neighbours r). How can I refactor it to avoid the repetition of getting r and keep getting it till a condition is reached? Thanks

pistacchio
  • 56,889
  • 107
  • 278
  • 420
  • 1
    your question could be rephrasen as "how do you write a do-while loop in Ruby?". http://stackoverflow.com/questions/136793/is-there-a-do-while-loop-in-ruby – tokland Nov 30 '11 at 13:44

2 Answers2

4
begin
  r = @lots.fetch @lots.keys.sample
end until neighbours r

Note that such code can easily lead to long running (or even infinite) loops if the condition neighbours is unlikely. In such cases better make sure you check every element just once as it is done in Michael Kohl's answer.

Community
  • 1
  • 1
undur_gongor
  • 15,657
  • 5
  • 63
  • 75
2

Personally I would shuffle @lots (turn it into a two element array if needed) and then use take_while like this:

>> h = {:a => 1, :b => 2, :c => 3} #=> {:a=>1, :b=>2, :c=>3}
>> h.to_a.shuffle.take_while { |k, v| v < 3 } #=> []
>> h.to_a.shuffle.take_while { |k, v| v < 3 } #=> [[:b, 2]]
>> h.to_a.shuffle.take_while { |k, v| v < 3 } #=> [[:a, 1]]
>> h.to_a.shuffle.take_while { |k, v| v < 3 } #=> [[:b, 2], [:a, 1]]

If you need the result as a hash again, just call Hash[] on it:

 >> Hash[h.to_a.shuffle.take_while { |k, v| v < 3 }] #=> {:a=>1}

In your case the condition for take while would be something like { |k, v| !(neighbours v)}. Also if you want the elements from the starting hash to be repeatable, you'll have to do use something else instead of shuffle.

undur_gongor
  • 15,657
  • 5
  • 63
  • 75
Michael Kohl
  • 66,324
  • 14
  • 138
  • 158
  • This is especially good, if the condition (`neighbours`) is rather improbable (+1). Otherwise, shuffling the whole hash in memory just to get a few random values might be overkill. – undur_gongor Nov 30 '11 at 10:10
  • It's just an example of collection methods available in Ruby, it's up to OP to write code that does exactly what he wants. If `take_while` isn't the right tool, do a `drop_while` with negative condition and use `first` on the leftover collection. Also OP said nothing about hash sizes or memory constraints and I always prefer easy code over premature optimizations. – Michael Kohl Nov 30 '11 at 10:17
  • Note that with this approach, the `v`/`r` fulfilling the condition is not available afterwards. – undur_gongor Nov 30 '11 at 10:20