Nested Loops Ruby

2020-08-09 05:16发布

问题:

Having Difficulty understanding this nested loop problem:

You have 10 pebbles (numbered 1-10). They are by default black. You must alter them by painting them white if they are black or painting them black if they are white. There are 10 rounds. Every round, you must alter the pebbles that are multiples of the current round. The pebbles are by default black.

  • 1st round, you alter every pebble (paint them all white).
  • 2nd round, you alter every other pebble(you paint pebbles #2,4,6,8,10 black).
  • 3rd round, you alter pebbles #3,6,9.
  • 4th round you alter pebbles #4,8.
  • ...
  • ...
  • 10th round, you alter pebble #10.

After the 10th round, which pebbles are painted black and which are painted white?

My solution which doesn't run is below (I attempt to do so by making an array of numbers(turned to strings) and adding "w" if painted white and deleting "w" if painted black.

(I have tried editing it to make it run, however I am new to nested loops and I am just not grasping this concept). I would greatly appreciate it if someone could explain to me what I am doing wrong and give a better solution.

pebbles = (1..10).map {|element| element.to_s}
pebble_colors = (1..10).map {|element| element.to_s}

(1..10).each do |round|
  pebbles_to_paint = []
  pebbles.each_with_index {|element, index| pebbles_to_paint << index if element % round == 0}
  pebbles_to_paint.each do |pebble_number|
    if pebble_color[pebble_number].include?("w")
      pebble_color[pebble_number].delete!("w")
    else
      pebble_color[pebble_number] << "w"
    end
  end
end

回答1:

Your main problem appears to be in the deciding of which pebbles to paint. The following is not right:

element % round == 0

It should be:

(index+1) % round

You want to compare the pebble's index rather than the current value of the pebble. As well, you need to remember that indexes are 0-based (ie they start counting from 0). You need to have the indexes be 1-based (hence the adding of 1) otherwise the first element would always change and the others would be off by 1.

There was also a typo for pebble_color, which should be pebble_colors.

You could definitely re-factor the code to make it shorter, but the following appears to work (just making the minimal changes mentioned above):

pebbles = (1..10).map {|element| element.to_s}
pebble_colors = (1..10).map {|element| element.to_s}

(1..10).each do |round|
  pebbles_to_paint = []
  pebbles.each_with_index {|element, index| pebbles_to_paint << index if (index+1) % round == 0}
  pebbles_to_paint.each do |pebble_number|
    if pebble_colors[pebble_number].include?("w")
      pebble_colors[pebble_number].delete!("w")
    else
      pebble_colors[pebble_number] << "w"
    end
  end
  p pebble_colors
end

The output is:

["1w", "2w", "3w", "4w", "5w", "6w", "7w", "8w", "9w", "10w"]
["1w", "2", "3w", "4", "5w", "6", "7w", "8", "9w", "10"]
["1w", "2", "3", "4", "5w", "6w", "7w", "8", "9", "10"]
["1w", "2", "3", "4w", "5w", "6w", "7w", "8w", "9", "10"]
["1w", "2", "3", "4w", "5", "6w", "7w", "8w", "9", "10w"]
["1w", "2", "3", "4w", "5", "6", "7w", "8w", "9", "10w"]
["1w", "2", "3", "4w", "5", "6", "7", "8w", "9", "10w"]
["1w", "2", "3", "4w", "5", "6", "7", "8", "9", "10w"]
["1w", "2", "3", "4w", "5", "6", "7", "8", "9w", "10w"]
["1w", "2", "3", "4w", "5", "6", "7", "8", "9w", "10"]


回答2:

Because this is about nested loops, I just wanted to add that you don't necessarily have to iterate through all pebbles on each round. (that's 100 iterations for 10 pebbles!)

Instead you can use Range#step to iterate over each nth element, starting with the round's index:

(1..10).each { |r|
  print "%2d:" % r
  (r..10).step(r) { |i|
    print " #{i}"
  }
  puts
}

produces:

 1: 1 2 3 4 5 6 7 8 9 10
 2: 2 4 6 8 10
 3: 3 6 9
 4: 4 8
 5: 5 10
 6: 6
 7: 7
 8: 8
 9: 9
10: 10

That's only 27 iterations. A nice side effect is that you don't have to calculate the remainder any more.

Full example:

pebbles = Hash[(1..10).map{|i| [i, :black]}]
toggle = {:black => :white, :white => :black}

(1..10).each { |r|
  (r..10).step(r) { |i|
    pebbles[i] = toggle[pebbles[i]]
  }
}
p pebbles
#=> {1=>:white, 2=>:black, 3=>:black, 4=>:white, 5=>:black, 6=>:black, 7=>:black, 8=>:black, 9=>:white, 10=>:black}


回答3:

There are a couple of problems in your code.

  • You use element % round instead of (index + 1) % round

  • You modify array pebble_color instead of pebble_colors

Fixing these two problems, the program leaves pebble_colors with the value

["1w", "2", "3", "4w", "5", "6", "7", "8", "9w", "10"]

although I think that isn't quite what you had in mind!

You have three arrays where one will do. All you need is an array of ten colours, starting all black. I would code it like this

pebbles = Array.new(10, :black)

(1..10).each do |round|
  pebbles.each_with_index do |pebble, i|
    if (i + 1).remainder(round) == 0
      pebbles[i] = pebble == :black ? :white : :black
    end
  end
end

p pebbles

output

[:white, :black, :black, :white, :black, :black, :black, :black, :white, :black]


回答4:

For make the problem simple, I think it is better calculate all round multiples before update the pebble.

pebbles = Array.new(10)  

10.times do |i|
  # calculate all multiples for each index
  multiples = Array(1..10).select {|j| j % (i + 1) == 0}

  # observer that we must have to sub 1 since Ruby use 0 indexed arrays
  multiples.map! {|j| j - 1}

  # update each pebble
  multiples.each do |j|
    pebbles[j] = pebbles[j] == 'b' ? 'w' : 'b'
  end
end

puts "Black pebbles: #{pebbles.select {|p| p == 'b'}.size}"