Monday, May 20, 2013

Ruby: Loops with value and index

How many way can you loop through an array in Ruby? A lot! I don't want to give a an exact count because I don't think I've learned them all. Here's just a list of the one I came across so far.

Situation:

I have two arrays, secret_code and guess.
secret_code = [2, 4, 6, 8]
guess = [8, 4, 6, 2]
I need to count how many values in the guess array that matches the values and the positions in the secret_code. So for the example above, my method should return 2 because 4 and 6 matches the values and position. 2 and 8 only match the values, but not the positions, so they don't count. This is what the test looks like.
describe MastermindRules do  
  it "counts the exact match when there exact match and value match" do
    secret_code = [2, 4, 6, 8]
    guess = [8, 4, 6, 2]
    MastermindRules.new.exact_match(secret_code, guess).should == 2
  end
end
This is the method api:
class MastermindRules
  
  def exact_match(secret_code, guess)
    
  end
  
end
So how many ways can I write the exact match method? Let's find out!

while loop

  def exact_match(secret_code, guess)
    exact_match = 0
    index = 0

    while index < secret_code.size
        exact_match += 1 if secret_code[index] == guess[index]
        index += 1
    end

    exact_match
  end

until loop

  def exact_match(secret_code, guess)
    exact_match = 0
    index = 0

    until index >= secret_code.size
        exact_match += 1 if secret_code[index] == guess[index]
        index += 1
    end
    
    exact_match
  end
Note that the condition statement is different than the while loop. In the while loop, it says, execute the loop while index is less than secret_code.size. But for the until statement, it says, execute the loop until the index is greater or equal the the code size. Which one do you choose? I prefer the one that is easier to understand. For example, if I have a method call game.over? I would use until game.over? because while sounds a little clunky, while !game.over?. On the other hand, if I have a method call game.in_progress? I would use while game.in_progress? but not until !game.in_progress?. In this case, if I have to choose between while and until, I would choose while.

loop loop

  def exact_match(secret_code, guess)
    exact_match = 0
    index = 0
    
    loop do
     break if index >= secret_code.size 
        exact_match += 1 if secret_code[index] == guess[index]
        index += 1
    end
    
    exact_match
  end
All of these feel a little clunky don't they? I have to initialize the index and manually increment it. The good thing I learned about Ruby is that if it feels clunky, there's probably a better way to write it.

for loop

  def exact_match(secret_code, guess)
    exact_match = 0

    for index in 0...secret_code.size
      exact_match += 1 if secret_code[index] == guess[index]
    end

    exact_match
  end
Note there are three periods for 0...secret_code.size. This gives me a range from 0 to secret_code.size, not including the code size which for me means 0-3. If there are two periods, then the range is 0-4. The two periods will make my test fail because secret_code[4] = nil and guess[4] = nil, which means my exact_match count will be 3. But I don't want that. So be careful with ranges. At the same time, I can also specify an array to loop through. However, this is not flexible for our case because we can't change the secret_code size on the fly.
  def exact_match(secret_code, guess)
    exact_match = 0

    for i in [0, 1, 2, 3]
      exact_match += 1 if secret_code[i] == guess[i]
    end

    exact_match
  end

each_with_index

  def exact_match(secret_code, guess)
    exact_match = 0

    secret_code.each_with_index do |value, index|
      exact_match += 1 if secret_code[index] == guess[index]
    end

    exact_match
  end

range with each

  def exact_match(secret_code, guess)
    exact_match = 0
    
    (0...secret_code.size).each do |index|
      exact_match += 1 if secret_code[index] == guess[index]
    end
    
    exact_match
  end

times

  def exact_match(secret_code, guess)
    exact_match = 0
    
    (secret_code.size).times do |index|
      exact_match += 1 if secret_code[index] == guess[index]
    end
    
    exact_match
  end

upto

  def exact_match(secret_code, guess)
    exact_match = 0
    
    0.upto(secret_code.size-1) do |index|
      exact_match += 1 if secret_code[index] == guess[index]
    end
    
    exact_match
  end

step

  def exact_match(secret_code, guess)
    exact_match = 0
    
    0.step(secret_code.size, 2) do |index|
      exact_match += 1 if secret_code[index] == guess[index]
    end
    
    exact_match
  end
As the name suggest, step is probably better when you want to skip elements in an array, but not when you need to loop through each element.
So with all these looping options, which one do you choose? It's really up to you. I prefer the one that best expresses my intent. For my problem, I chose each_with_index because when I read this, I know that the value and the index is important.
For more details, Alan has a great article on loops in ruby.

0 comments:

Post a Comment