Nested Loops Ruby - ruby

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

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}

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"]

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]

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}"

Related

check if the index of similar elements in two arrays are different

This is my first question on StackOverFlow! Please let me know if my question is not clear.
Here is what I have so far:
code = ["4", "4", "1", "1"]
guess = ["4", "4", "4", "1"]
#A = 0
#B = 0
code.each_with_index do |item, index|
if item == guess[index]
#A += 1
elsif code.include?(guess[index])
#B += 1
end
print "\nA:#{#A} B:#{#B}"
end
I would like to increase #A by 1 if a number is in both arrays and in the same position (index).
If the number is in both arrays but in different positions (index), increase #B by 1.
I should've gotten A:3 B:0 but I am getting A:3 B:1. In the code array, there is no third "4" so B shouldn't have increased by 1.
Is it because .include? doesn't work with duplicates in arrays? How can I fix this?
After each successful (individual) match between the code and guess, we must remove the matched elements from the equation.
For example, code = ['1', 2'] and guess = ['1', '1']. We get an exact match at index 0. If we remove the matched elements: code = ['2'] and guess = ['1'], we can clearly see that there is no further match.
If we had not removed the elements, then (in addition to the exact match) index 0 of code would match with index 1 of guess... which would wrongly result in #A = 1 and #B = 1. We do not want to use an already matched code element to match with a different guess element.
code = ["4", "4", "1", "1"]
guess = ["4", "4", "4", "1"]
unmatched_code = []
unmatched_guess = []
#A = 0
#B = 0
code.size.times do |i|
if code[i] == guess[i]
#A += 1
else
unmatched_code << code[i]
unmatched_guess << guess[i]
end
end
unmatched_guess.each do |g|
matched_at_index = unmatched_code.index(g)
if matched_at_index
#B += 1
unmatched_code.delete_at(matched_at_index)
end
end
This should work:
code = ["4", "4", "1", "1"]
guess = ["4", "4", "4", "1"]
visited = Array.new(guess.length) { false }
#A = 0
#B = 0
code.each_with_index do |item, index|
if item == guess[index]
#A += 1
#B -= 1 if visited[index]
visited[index] = true
elsif guess.each_index.any? { |idx| guess[idx] == item && !visited[idx] && visited[idx] = true }
#B += 1
end
end
print "\nA:#{#A} B:#{#B}"
Here, I am keeping track of already visited indexes in the guess array so that I don't count the same element in both #A and #B count.
You could start by combining code and guess pairs via zip:
code = ["4", "3", "1", "1"]
guess = ["2", "3", "1", "4"]
pairs = code.zip(guess)
#=> [["4", "2"], ["3", "3"], ["1", "1"], ["1", "4"]]
To separate the exact matches, you could use partition:
exact_matches, remaining = pairs.partition { |a, b| a == b }
exact_matches
#=> [["3", "3"], ["1", "1"]]
remaining
#=> [["4", "2"], ["1", "4"]]
Your A-value is the number of exact matches:
a = exact_matches.size
#=> 2
And if remaining is empty?, you're already done here. But it is not:
remaining.empty?
#=> false
We therefore have to determine the number of partial matches. To do so, I would start by converting the remaining pairs back into their respective arrays via transpose:
code_remaining, guess_remaining = remaining.transpose
code_remaining
#=> ["4", "1"]
guess_remaining
#=> ["2", "4"]
Based on that arrays, you could calculate the number of their entries via tally:
code_hash = code_remaining.tally
#=> {"4"=>1, "1"=>1}
guess_hash = guess_remaining.tally
#=> {"2"=>1, "4"=>1}
There's 1 "4" and 1 "2" in the guess_hash. For each of these, we can check how many are actually available in the code_hash:
code_hash["4"] #=> 1
code_hash["2"] #=> nil
Because nil means 0 here, I would use fetch instead, which allows a default value to be passed:
code_hash.fetch("4", 0) #=> 1
code_hash.fetch("2", 0) #=> 0
And because there could be more available than guessed (or vice-versa), the actual value would be the minimum of them:
[guess_hash["4"], code_hash.fetch("4", 0)].min #=> 1
[guess_hash["2"], code_hash.fetch("2", 0)].min #=> 0
To get the B-value out of this, we have to sum the above:
b = guess_hash.sum { |k, v| [v, code_hash.fetch(k, 0)].min }
#=> 1

How to split a long number into pairs of digits

I have a string of a long number 12345678 and want to convert it to an array like this :
["12", "34", "56", "78"].
I have tried array.split(//).map { |e| e.to_i } but it does
["1", "2", "3", "4", "5", "6", "7", "8"]
The simplest way would be to use String#scan with the Regexp /../, which matches any pair of characters:
n = 12345678
arr = n.to_s.scan(/../)
# => ["12", "34", "56", "78"]
If you need to handle odd numbers of digits and keep the last digit, use /..?/ instead:
n = 123456789
arr = n.to_s.scan(/..?/)
# => ["12", "34", "56", "78", "9"]
An alternative approach would be to map over the range 0...n.to_s.size using the Range#step method:
n = 123456789
str = n.to_s
arr = (0...str.size).step(2).map {|i| str[i, 2] }
# => ["12", "34", "56", "78", "9"]
You can see all three approaches in action on repl.it: https://repl.it/#jrunning/BlissfulAcclaimedStrategy
I think there is no need to use a regexp, I would do something like this:
12345678.to_s.chars.each_slice(2).map(&:join)
#=> ["12","34","56","78"]
You can use
p = 123456789.to_s
(0..p.length).each_cons(2).map {|i, j| "#{p[i]}#{p[j]}" if i%2 == 0}.compact

Changing a value in an array of random numbers in a loop

I'm trying to make a loop that when the random number matches the same index of the input the value is changed and stays that way for each following loop.
input = gets.chomp
tries_left = 12
while(tries_left > 0)
tries_left -= 1
computer = 4.times.map do rand(0..6) end.join
if computer[0] == input[0]
computer[0] = input[0]
end
end
in the code above after the first loop the value stored to input[0] resets.
computer = 4.times.map do rand(0..6) end.join
input = gets.chomp
tries_left = 12
while(tries_left > 0)
tries_left -= 1
if computer[0] == input[0]
computer[0] = input[0]
end
if I take computer out of the loop like this, it will have the same random number generated each time. Once again I need it to generate new numbers each time besides what was already a match.
If you make the computer an array of strings, you can freeze it to prevent further modifications to it, and then replace the contents in computer when it doesn't match the index:
input = gets.chomp
tries_left = 12
computer = Array.new(4) { '' }
# setting the srand to 1234, the next 48 calls to 'rand(0..6)' will always
# result in the following sequence:
# 3, 6, 5, 4, 4, 0, 1, 1, 1, 2, 6, 3, 6, 4, 4, 2, 6, 2, 0, 0, 4, 5, 0, 1,
# 6, 6, 2, 0, 3, 4, 5, 2, 6, 2, 3, 3, 0, 1, 3, 0, 3, 2, 3, 4, 1, 3, 3, 3
# this is useful for testing things are working correctly,
# but take it out for 'live' code
srand 1234
while tries_left > 0
# no need to keep iterating if we've generated all the correct values
if computer.all?(&:frozen?)
puts "won #{computer.inspect} in #{12 - tries_left} tries"
break
end
tries_left -= 1
computer.each.with_index do |random, index|
# generate a new random number here unless they guessed correctly previously
random.replace(rand(0..6).to_s) unless random.frozen?
# if they've guessed the new random number, mark the string so they we
# don't update it
random.freeze if random == input[index]
end
puts "#{computer.inspect} has #{computer.count(&:frozen?)} correct numbers"
end
and then when you run the script:
$ echo 3654 | ruby example.rb
# ["3", "6", "5", "4"] has 4 correct numbers
# won ["3", "6", "5", "4"] in 1 tries
$ echo 3644 | ruby example.rb
# ["3", "6", "5", "4"] has 3 correct numbers
# ["3", "6", "4", "4"] has 4 correct numbers
# won ["3", "6", "4", "4"] in 2 tries
$ echo 3555 | ruby example.rb
# ["3", "6", "5", "4"] has 2 correct numbers
# ["3", "4", "5", "0"] has 2 correct numbers
# ["3", "1", "5", "1"] has 2 correct numbers
# ["3", "1", "5", "2"] has 2 correct numbers
# ["3", "6", "5", "3"] has 2 correct numbers
# ["3", "6", "5", "4"] has 2 correct numbers
# ["3", "4", "5", "2"] has 2 correct numbers
# ["3", "6", "5", "2"] has 2 correct numbers
# ["3", "0", "5", "0"] has 2 correct numbers
# ["3", "4", "5", "5"] has 3 correct numbers
# ["3", "0", "5", "5"] has 3 correct numbers
# ["3", "1", "5", "5"] has 3 correct numbers
It’s not quite clear what you are trying to accomplish, but this:
if computer[0] == input[0]
computer[0] = input[0]
end
is obviously a noop. Nothing gets updated, since the computer[0], whatever it is, is being set to the same value it was. I believe you wanted to somehow use an index in the array:
4.times.map do |index|
value = rand(0..6)
# somehow check the similarity, e.g.:
if input[index] == value
# do something
end
end
I beg your pardon for the very vague answer, but it is really hard to understand the goal you are trying to achieve.

Why does this ruby sequence not work for double digit numbers?

I was creating a program with ruby to organises sequences of numbers. It worked perfectly except when two digit numbers were involved, here is the code:
print "Hello participant today we will be rearranging your numbers from smallest to largest, press enter to continue!!"
gets.chomp
print "Please enter your first number"
n1 = gets.chomp
print "Please enter your second number"
n2 = gets.chomp
print "Please enter your third number"
n3 = gets.chomp
print "Please enter your fourth number"
n4 = gets.chomp
print "Please enter your fifth number"
n5 = gets.chomp
a = [n1, n2, n3, n4, n5]
print "your numbers from smallest to largest are: #{a.sort!}"
gets.chomp
print "thank you for participating, See you next time!!"
Sort array of strings or array of integers
[n1, n2, n3, n4, n5] is an array of strings, and strings are compared with lexicographic order.
["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"].sort
#=> ["1", "10", "11", "12", "2", "3", "4", "5", "6", "7", "8", "9"]
["12", "11", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"].sort_by(&:to_i)
#=> ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]
So you need :
print "your numbers from smallest to largest are: #{a.sort_by(&:to_i)}"
or just convert your string array to an integer array :
a = [n1, n2, n3, n4, n5].map(&:to_i)
print "your numbers from smallest to largest are: #{a.sort}"
Refactoring
Here's a shorter way to write your script :
puts "Hello participant today we will be rearranging your numbers from smallest to largest, press enter to continue!!"
gets
a = %w(first second third fourth fifth).map do |ordinal|
puts "Please enter your #{ordinal} number"
gets.to_i
end
puts "Your numbers from smallest to largest are: #{a.sort}"
gets
puts "Thank you for participating, See you next time!!"

Using each_slice to split input starting from end of string?

I would like to split a string into array groups of three as shown in desired output. Using Array#each_slice like this 1_223_213_213.to_s.split('').each_slice(3){|arr| p arr }
Current output: Desired output
# ["1", "2", "2"] # ["0", "0", "1"]
# ["3", "2", "1"] # ["2", "2", "3"]
# ["3", "2", "1"] # ["2", "1", "3"]
# ["3"] # ["2", "1", "3"]
Must work with numbers from (0..trillion). I posted my solution as an answer below. Hoping you all can give me some suggestion(s) to optimize or alternative implements?
Try left-padding with zeros until the string length is an even multiple of your "slice" target:
def slice_with_padding(s, n=3, &block)
s = "0#{s}" while s.to_s.size % n != 0
s.to_s.chars.each_slice(n, &block)
end
slice_with_padding(1_223_213_213) { |x| puts x.inspect }
# ["0", "0", "1"]
# ["2", "2", "3"]
# ["2", "1", "3"]
# ["2", "1", "3"]
slice_with_padding(12_345, 4) { |x| puts x.inspect }
# ["0", "0", "0", "1"]
# ["2", "3", "4", "5"]
You might find this a little more pleasing to your eye:
def slice_by_3(n)
n = n.to_s
l = n.length
[*n.rjust(l % 3 == 0 ? l : l + 3 - l % 3, '0').chars.each_slice(3)]
end
slice_by_3 2_123_456_544_545_355
=> [["0", "0", "2"],
["1", "2", "3"],
["4", "5", "6"],
["5", "4", "4"],
["5", "4", "5"],
["3", "5", "5"]]
Alternatively, if you want a more general solution:
def slice_by_n(num, n=3)
num = num.to_s
l = num.length
[*num.rjust(l % n == 0 ? l : l + n - l % n, '0').chars.each_slice(n)]
end
Here is a possible solution for the problem:
def slice_by_3 number
initial_number = number.to_s.split('').size
number = "00#{number}" if initial_number == 1
modulus = number.to_s.split(/.{3}/).size
padleft = '0' * ( (modulus*3) % number.to_s.split('').size )
("#{padleft}#{number}").split('').each_slice(3){|arr| p arr }
end
slice_by_3 2_123_456_544_545_355
# ["0", "0", "2"]
# ["1", "2", "3"]
# ["4", "5", "6"]
# ["5", "4", "4"]
# ["5", "4", "5"]
# ["3", "5", "5"]
Just seems somewhat complex and I want to believe there is a better way. I appreciate your feedback.
def slice_by_3 number
"000#{number}".split('').reverse
.each_slice(3).to_a[0..-2].reverse
.each { |arr| p arr.reverse }
end
slice_by_3 13_456_544_545_355
# ["0", "1", "3"]
# ["4", "5", "6"]
# ["5", "4", "4"]
# ["5", "4", "5"]
# ["3", "5", "5"]
This code reverses the whole array after adding 3 zeroes to the number start. each_slice(3) then slices to the proper groups (although reversed) plus one which consists of either ["0","0","0"], ["0","0"] or ["0"] depending on the original length of the number.
[0..-2] cuts the last group of zeroes. Then the groups are reversed back, and each group is printed (reversed back).
Here are a couple methods
n = 1_223_213_213.to_s
n.rjust(n.size + n.size % 3,"0").chars.each_slice(3).to_a
OR
n.rjust(15,"0").chars.each_slice(3).drop_while{|a| a.join == "000"}
15 is because you stated the max was a trillion obviously this number means very little as it rejects all results that contain all zeros so any number greater than 15 that is divisible by 3 will work for your example
Another way:
def split_nbr(n)
str = n.to_s
len = str.size
str.rjust(len + (3-len%3)%3, '0').scan(/.../).map(&:chars)
end
split_nbr( 1_223_213_213)
#=> [["0", "0", "1"], ["2", "2", "3"], ["2", "1", "3"], ["2", "1", "3"]]
split_nbr( 11_223_213_213)
#=> [["0", "1", "1"], ["2", "2", "3"], ["2", "1", "3"], ["2", "1", "3"]]
split_nbr(111_223_213_213)
#=> [["1", "1", "1"], ["2", "2", "3"], ["2", "1", "3"], ["2", "1", "3"]]

Resources