sartra
sartra

Reputation: 1

Boolean Array change in Ruby

I am stuck on this code problem - I need to iterate through an array of booleans 100 times. Instructions:

Cats in hats

You have 100 cats. Your rules are simple: whenever you visit a cat, you toggle it's hat status (if it already has a hat, you remove it.. if it does not have a hat, you put one on). All of the cats start hat-less. You cycle through 100 rounds of visiting cats. In the 1st round, you visit every cat. In the second round, you visit every other cat. In the nth round, you visit every nth cat.. until the 100th round, in which you only visit the 100th cat. At the end of 100 rounds, which cats have hats?

New to Ruby from JS and stuck on how to implement this. I think my logic is right, I just can't get it to change the value of the booleans in the array.

Any advice much appreciated!

Thanks

def cats_in_hats

 cats = []

 i = 0
 while i< 100
 cats << false 
 i = i + 1 
 end 

 puts cats 


 round = 1;

 while round < 10

  # 
  cats = cats.each_with_index do |cat, idx|
    if idx % round == 0
      puts "toggle index #{idx} divisible by round #{round}"
      cat = !cat
      puts cat 
    end
  end

  round = round + 1 

 end 

puts cats 

end

puts "------Cats in Hats------"
puts cats_in_hats == [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

Upvotes: 0

Views: 899

Answers (3)

engineersmnky
engineersmnky

Reputation: 29478

Welcome to ruby. Someone nicely provided an explanation of what the issues were with your code. I thought I would give you a quick run down on the power of objects in ruby instead.

Let's start with Cat. What can it do? Well:

  • It can have a hat
  • It starts out without a hat
  • It can put on and take off the hat

Okay so lets make an Object for that:

class Cat
  def initialize
    @hat = false # start out without a hat
  end
  def toggle_hat
    !@hat   # in case you just want to tip your hat in salutation to JörgWMittag
  end
  def toggle_hat!
    @hat = toggle_hat # @hat equals not(@hat) e.g. @hat = not(true) to take off the hat
  end
  def has_hat?
    @hat # does it have a hat true or false
  end
end

Perfect there is a Cat. Now let's look at the problem.

We need to loop through 100 cats. Okay

cats = Array.new(100) { Cat.new }

Now we have 100 cats. Let's start looping

#loop from 1 up to 100 providing the loop number (n)
1.upto(100).each do |n| 
  # for each n we skip over the preceding n and step by n
  (n..100).step(n) do |idx| 
    # pick the appropriate cat (ruby indexes start at 0) 
    # toggle his hat
    cats[idx - 1].toggle_hat!
  end
end 

Great all our Cats have been looped through and toggled now lets count the ones with hats on

# will count each cat where has_hat? is true
cats.count {|cat| cat.has_hat? }
#=> 10 

This would generally be expressed more idiomatically as cats.count(&:has_hat?). This uses ruby's Symbol#to_proc sugar and means call the method has_hat? on each element yielded to the block as shown more explicitly above.

We can also collect them

cats.map.with_index {|cat,idx| idx + 1 if cat.has_hat? }.compact
#=> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

Tada Objects are amazing and make for fantastically readable simplistic code. Full Example

Upvotes: 3

Max
Max

Reputation: 22355

cats = cats.each_with_index do |cat, idx|
  # ...
  cat = !cat
end

This is the problem. All objects in Ruby are represented in memory by pointers, and these pointers are copied around to pass objects to methods/blocks/etc. So when you do each_with_index, Ruby copies the pointer of each element into your block-local variable cat one at a time.

Now when you say cat = !cat, this is what Ruby does

  1. Find the ! method for the object pointed to by cat and call it. This returns a pointer to the object true or the object false
  2. Overwrite the block-local variable cat with that new pointer

At no point does that new pointer end up in your cats array!

I can think of two main solutions to this problem:

  1. Instead of storing Boolean objects (which are immutable), store mutable objects in cats. Then you can call a method on each cat that tells it to mutate its state (this is what @engineersmnky's code does)
  2. Instead of using your block-local variable cat, tell Ruby to copy the new pointer directly into the array: cats[idx] = !cat

Upvotes: 1

larz
larz

Reputation: 5766

def cats_in_hats
  cats = []

  i = 0
  while i < 100
    cats << false 
    i = i + 1 
  end 

  p cats 

  round = 1;

  while round < 100

  cats = cats.collect!.with_index do |cat, idx|
    idx % round == 0 ? !cat : cat
  end

  round = round + 1 

  end 

  p cats 

end

puts "------Cats in Hats------"
puts cats_in_hats

The problem with your initial code is that you were never "doing" anything in your each_with_index block. when you used cat = !cat you were only changing that item within the scope of the loop and never creating a new array. Using .collect!.with_indexmap/collect!.with_index(to get the index) you can perform an operation on each item in an array and return a new array with every iteration. This is similar to how JavaScript's map function works. Let me know if you need further explanation.

Upvotes: 0

Related Questions