Can someone explain me what this line of ruby code does? - ruby

I'm a beginner in ruby and found this example on the Odin project about the reduce method, but in line 7 it puts the result variable again, can someone explain me What's the use of putting the result variable?
Thank you in advance!
votes = ["Bob's Dirty Burger Shack", "St. Mark's Bistro", "Bob's Dirty Burger Shack"]
votes.reduce(Hash.new(0)) do |result, vote|
puts "result is #{result} and votes is #{vote}"
puts "This is result [vote]: #{result[vote]}"
result[vote] += 1
result #this part I don't understand
end

They're using the reduce(initial_operand) {|memo, operand| ... } version.
memo is a thing to collect the result. The block has to pass that along to the next iteration. For example, if you wanted to sum up a list of numbers...
(1..4).inject do |sum, element|
p "Running sum: #{sum}; element: #{element}"
# This is passed along to the next iteration as sum.
sum + element
end
Instead of using the default memo, which would be the first element, they've used Hash.new(0) to count the votes. Each iteration counts the votes, and then passes the result has to the next iteration.
# result starts as Hash.new(0)
votes.reduce(Hash.new(0)) do |result, vote|
# This prints the state of the voting and the current choice being tallied.
puts "result is #{result} and votes is #{vote}"
# This displays how many votes there are for this choice at this point
# before this choice is tallied.
puts "This is result [vote]: #{result[vote]}"
# This adds the vote to the tally.
result[vote] += 1
# This passes along the tally to the next iteration.
result
end
If you don't want to print on each iteration, use tally instead.
result = votes.tally

Related

Simple program but so very stuck- Loops in Ruby

I have to write a program which asks the user to enter a number.
The program keeps on asking the user for a number until the user types 'Stop'
at which point the sum of the numbers that the user has entered should be printed.
I've tried many,many things and none of my ideas work.
This is what I have - but I can that it isn't correct. What am I doing wrong?
I've only used while loops and arrays
total_user_input = []
# As long as the user inputs a number, the program will keep putting Give me a number
# and then adding that number to the total_user_input array.
puts "Give me a number: "
while user_input = gets.chomp.to_i
#add the input to the array total_user_input
total_user_input.push(user_input.to_i)
puts "Give me a number: "
# If the user however types stop, then the loop is broken and we jump down to the
# sum bit - where all of the numbers in the total_user_input array are added together
# and printed. End of program!
if user_input == "stop"
break
end
sum = 0
total_user_input.each { |num|
sum += num
}
puts sum
end
The output isn't as it should be.
As others have identified the problems with your code let me suggest how you might reorganize it. Ruby provides many ways to execute loops but you many find it desirable to primarily relay on the method Kernel#loop and the keyword break. (As you will learn in time, loop is particularly convenient when used with enumerators.)
def sum_numbers
tot = 0
loop do
print 'Gimme a number: '
s = gets.chomp
break if s == 'Stop'
tot += s.to_i
end
tot
end
The keyword break can optionally take an argument (though why that is not mentioned in the doc I cannot say), in which case it (if a literal) or its value (if a variable or method) is returned by loop. Here one would generally see
break tot if s == 'Stop'
without the final line, tot. As the loop returns tot and that is the last calculation performed by the method, the method will return the final value of tot.
You could have instead written
return tot if user_input == 'Stop'
but I think most coders believe best practice dictates that one should not return from a method from within a loop (or from within nested loops) unless there is a good reason for doing so.
Some small points:
I used print rather than puts to that the user's entry will be shown on the same line as the prompt.
I used s (for "string") rather than user_input because it reduces the chance of spelling mistakes (e.g., user_imput), speeds reading, and (possibly a foible of mine), looks neater. True, s is not descriptive, but one only has to remember its meaning for three consecutive lines of code. Others may disagree.
You could write, break if s.downcase == 'stop' if you want, say, 'stop' or 'STOP' to have the same effect as 'Stop'.
'23O3'.to_i #=> 23 (that's an an oh, not a zero), so in real life you'd want to confirm that either 'Stop' or the string representation of a number had been typed.
This is how I would do this preferring to use loop do end syntax with a break when it should. Also added a bit more text so user knows what's happening.
total_user_input = []
puts 'Give me a number or "stop" to end: '
loop do
user_input = gets.chomp
total_user_input << user_input.to_i
puts "Give me a number: "
break if user_input.downcase == "stop"
end
puts "Total entered: #{total_user_input.inject(&:+)}" unless total_user_input.empty?
puts 'goodbye!'
Note these few things:
get.chomp.to_i will convert every input to integer. ("stop" or any non integer string will be 0)
Arrangement of the flow is quite messy.
total_user_input = []
puts "Give me a number: "
while user_input = gets.chomp.strip
total_user_input.push(user_input.to_i)
sum = 0
total_user_input.each { |num|
sum += num
}
puts sum
if user_input == "stop"
break
end
end
Hope you understand this.

cant understand the triagle number enumerator yielder yield

unable to understand this code how it works.
triangular_numbers = Enumerator.new do |yielder|
number = 0
count = 1
loop do
number += count
count += 1
yielder.yield number
end
end
5.times { print triangular_numbers.next, " " }
cant understand how yielder works for this block. how its yield work for number variable. how do loop runs 5 times. and how triangular_number.next works for the first time.
An enumerator is basically something that you can call next on and get something back. The yielder is the mechanism where it gives something back when next is called. Execution stops at the yield until the next call to next.
Farfetched analogy
You can think of an enumerator as a ticket machine like when you're waiting in line at a government office. When you press a button (next) it gives you a ticket. Inside the machine there's a chute where the ticket comes out. But the ticket machine is not constantly printing tickets. It waits for the button to be pressed before it prints the next ticket and puts it through the chute.
In this case the analogous code would be:
ticket_machine = Enumerator.new do |chute|
ticket = 0
loop do
#print_ticket
chute.yield ticket #waits here until you hit the button
ticket += 1
end
end
5.times { print ticket_machine.next, " " } # gets 5 tickets
Your code sample is basically the same thing, but instead of issuing tickets, it's issuing triangular numbers. The chute is the yielder where the numbers get passed through.
This is not the only way to use an enumerator, check the docs for more.
Enumerator::new accepts a block. This block, when run, receives an Enumerator::Yielder, which has a method #yield.
When the Enumerator#next is called, the block is executed, up to the first Enumerator::Yielder#yield. The execution is paused there; the value given to yield is the value that next returns. When you call next again on the same Enumerator, the execution is resumed, and proceeds until it encounters yield again.
So in your case, 5.times executes its block, intending to repeat it five times. triangular_numbers.next is called; this starts the execution of the block above. number and count are set to their values, and an infinite loop is started. number is set to 1, count is set to 2, and then we find yielder.yield. This pauses the execution of the block, and returns the control back to where next was called inside 5.times loop. next returns 1, because yielder.yield received number (1).
Second time through the 5.times loop, we want to print the next number. This stops the main execution line, and resumes the enumerator block from just after yielder.yield. The infinite loop continues; number is 3, count is 3, yielder.yield pauses the enumerator and resumes the main code. next gets 3, which gets printed.
Third, fourth and fifth time through the 5.times loop are exactly the same.
After five iterations, 5.times loop ends, and the execution proceeds past it. The enumerator is paused, ready to give the next number in sequence, if you ever call next on it again (since it has an infinite loop), but you never do, and the program exits.
I'll try to explain what it does bit by bit, so you can try to wrap your head around it.
Enumerator.new do |yielder|
end
So you instantiate an enumerator that will work over a variable called yielder.
Inside its scope you set some local vars (that will be kept as the object is reused):
number = 0
count = 1
And then you set a loop that increments number by count and count by 1 and then call yield over your argument passing number to it as an argument.
loop do
number += count
count += 1
yielder.yield number
end
5.times repeats the block passed to it 5 times. The block
-> { print triangular_numbers.next, " " }
calls print that takes n args and concatenates the parts to form a string, but does not append a newline.
The first argument is our enumerator next interaction (triangular_numbers.next), which will compute the current number and call yield on the Enumerator::Yielder that's implicitly created handling the control back to the calling Fiber along with any args that got passed to it.
(All Enumerators are implemented as "Fibers" on MRI)
So that yielder.yield call is similar to a Fiber.yield call and will allow the 5.times loop to run and return the number 1.
I'm adding a piece of code to the already clear explanation provided:
my_enum = Enumerator.new do |whatever_name_for_the_yielder|
n = 0
loop do
whatever_name_for_the_yielder.yield "Return this: #{n}"
n += 1
end
end
puts my_enum.next #=> Return this: 0
puts my_enum.next #=> Return this: 1
puts my_enum.next #=> Return this: 2
When you provide an end to the iteration, it stops with an error:
my_enum2 = Enumerator.new do |whatever_name_for_the_yielder|
2.times do |n|
whatever_name_for_the_yielder.yield "Return this: #{n}"
end
puts "Outside the loop"
end
puts my_enum2.next #=> Return this: 0
puts my_enum2.next #=> Return this: 1
puts my_enum2.next #=> Outside the loop
#=> ERROR: .....in `next': iteration reached an end (StopIteration)

How do you add to a Ruby array based on users input?

I am asking the user to input a number and based on that number I want to add certain players to my game.
class Player
def players_playing
players = []
puts('How many players are playing?')
players_amount = gets.chomp
for i in range(players_amount)
puts ('What is the players name')
name = gets.chomp
players.push(name)
end
end
end
So if they enter 3. Then the code should loop through 3 times and ask the user for names. e.g.
What is the players name? Rich
What is the players name? Tom
What is the players name? Charles
Then it would have players = ['Rich', 'Tom', 'Charles']
any ideas why my code is not correct? ( i figure it is to do with the range part maybe)
There are some mistakes in your code:
At first you are asking for a number, however players_amount is a string. You should convert it using the to_i method.
Then, for iterating over a range, there are several ways of doing it in Ruby, but there is no keyword range as in Python. For iterating over a range (that is, an interval), use:
# Exclusive:
(0...3).each do |i|
puts i
end
# 0
# 1
# 2
# Inclusive:
(0..3).each do |i|
puts i
end
# 0
# 1
# 2
# 3
So, instead of your for loop, just write (0...players_amount).each do.
With those modifications, the program has the expected behaviour. However, if you want the name to appear on the same line of the question, use print instead of puts because puts adds automatically a line break at the end of the string.
I would add a complement to T. Claverie's answer. In this case I guess you only need to iterate a certain number of times and do nothing with the iteration index. That way, I would replace the for loop in your code with the following:
players_amount.times do
puts ('What is the players name')
name = gets.chomp
players.push(name)
end
Hope it helps.

Able to use a variable within another variable's name? Ruby

So my goal is to be able to run through a "while" loop and in each iteration create a new variable that includes the "iteration count" within that variables name and stores it for later use outside of the loop. See below for more details.
NOTE: The code is clearly wrong in so many ways but I'm writing it this way to make it more clear? as to what I am trying to accomplish. Thanks for any input on how this is possible.
count = "4"
while count > "0"
player"#{count}"_roll = rand(20)
puts 'Player "#{count}" rolled: "#{player"#{count}"_roll}"'
count -= 1
end
My goal is then to be able to access the variables that were created from within the loop at a later part of the program like so (more or less)
puts player4_roll
puts player3_roll
puts player2_roll
puts player1_roll
The key being that these variables were A) created in the loop B) With names relying on another variables input, and C) accessible outside the loop for later use.
Hopefully my question came out clear and any input will be greatly appreciated. I'm very new to programming and trying to get my head wrapped around some more complex ideas. I'm not sure if this is even possible to do in Ruby. Thanks!
I think the best way is to use arrays or hashes, with arrays its something like this:
count = 0
array = []
while count < 4 do
array[count] = rand(20)
puts "Player #{count} rolled: #{array[count]}"
count += 1
end
array.each do |var|
puts var
end
You store the result in the array and then you loop trough it. If you want the result of the second iteration of the loop you do something like this:
puts array[1]
If you want to use Hashes there are some modifications you need to do:
count = 0
hash = {}
while count < 4 do
hash["player#{count}_roll"] = rand(20)
puts "Player #{count} rolled: #{hash["player#{count}_roll"]}"
count += 1
end
hash.each do |key, var|
puts var
end
If you want the result of the second iteration of the loop you do something like this:
puts hash["player1_roll"]
You could set the variable using instance_variable_set and reference it that way
instance_variable_set("#player#{count}_roll", rand(20))

Coderbyte Second Great Low - code works but is rejected

I'm currently working through the Coderbyte series to get better at Ruby programming. Maybe this is just a bug in their site (I don't know), but my code works for me everywhere else besides on Coderbyte.
The purpose of the method is to return the 2nd smallest and the 2nd largest elements in any inputted array.
Code:
def SecondGreatLow(arr)
arr=arr.sort!
output=[]
j=1
i=(arr.length-1)
secSmall=''
secLarge=''
while output.length < 1
unless arr.length <= 2
#Get second largest here
while (j<arr.length)
unless arr[j]==arr[j-1]
unless secSmall != ''
secSmall=arr[j]
output.push(secSmall)
end
end
j+=1
end
#get second smallest here
while i>0
unless arr[i-1] == arr[i]
unless secLarge != ''
secLarge=arr[i-1]
output.push(secLarge)
end
end
i-=1
end
end
end
# code goes here
return output
end
# keep this function call here
# to see how to enter arguments in Ruby scroll down
SecondGreatLow(STDIN.gets)
Output
Input: [1,2,3,100] => Output: [2,3] (correct)
Input: [1,42,42,180] => Output: [42,42] (correct)
Input: [4,90] => Output: [90,4] (correct)
The problem is that I'm awarded 0 points and it tells me that my output was incorrect for every test. Yet, when I actually put any inputs in, it gives me the output that I expect. Can someone please assist with what the problem might be? Thanks!
Update
Thanks to #pjs answer below, I realized this could be done in just a few lines:
def SecondGreatLow(arr)
arr=arr.sort!.uniq
return "#{arr[1]} #{arr[-2]}"
end
# keep this function call here
# to see how to enter arguments in Ruby scroll down
SecondGreatLow(STDIN.gets)
It's important to pay close attention to the problem's specification. Coderbyte says the output should be the values separated by a space, i.e., a string, not an array. Note that they even put quotes around their "Correct Sample Outputs".
Spec aside, you're doing way too much work to achieve this. Once the array is sorted, all you need is the second element, a space, and the second-to-last element. Hint: Ruby allows both positive and negative indices for arrays. Combine that with .to_s and string concatenation, and this should only take a couple of lines.
If you are worried about non-unique numbers for the max and min, you can trim the array down using .uniq after sorting.
You need to check condition for when array contains only two elements. Here is the complete code:
def SecondGreatLow(arr)
arr.uniq!
arr.sort!
if arr.length == 2
sec_lowest = arr[1]
sec_greatest = arr[0]
else
sec_lowest = arr[1]
sec_greatest = arr[-2]
end
return "#{sec_lowest} #{sec_greatest}"
end

Resources