Reputation: 1
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
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:
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 Cat
s 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 Object
s are amazing and make for fantastically readable simplistic code. Full Example
Upvotes: 3
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
!
method for the object pointed to by cat
and call it. This returns a pointer to the object true
or the object false
cat
with that new pointerAt no point does that new pointer end up in your cats
array!
I can think of two main solutions to this problem:
cats
. Then you can call a method on each cat
that tells it to mutate its state (this is what @engineersmnky's code does)cat
, tell Ruby to copy the new pointer directly into the array: cats[idx] = !cat
Upvotes: 1
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_index
map/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