I am trying to increase the number of sets until workout is at least as long as min_workout but not longer thanmax_workout. I am testing with valuesmin30,max40,run3, andwalk2`. It should stop at 5 repeats and total of 33 minutes.
warm_cool = 8
puts "What is the least amount of time you want to work out?"
min_workout = gets.chomp.to_i
puts "What is the longest time you want to work out?"
max_workout = gets.chomp.to_i
puts "How many minutes per set do you want to run?"
run = gets.chomp.to_i
puts "How many minutes do you want to walk each set?"
walk = gets.chomp.to_i
i = 0
workout = (run+walk)*i + warm_cool
until workout >=min_workout && workout <=max_workout do
workout = (run+walk)*i+=1 + warm_cool
puts "You need to perform #{i} repeats and your workout time will be #
{workout} minutes, including a 4 minute warmup and cooldown "
end
I can't figure out why I am getting an infinite loop here.
There's a few little slips here that have had some pretty dramatic consequences. First of all is using += in the middle of a statement. That's generally bad form and it's caused absolute chaos here.
The reason for this is your code is evaluated as this:
workout = (run + walk) * i += (1 + warm_cool)
Since warm_cool is 8 then it increments by 9 each time and you can easily skip past the end of your range. This is why it's generally best to limit how many times you try things to a reasonable count. Wrapping it in a simple method also helps contain things and makes managing flow easier:
def intervals_required(run, walk, warm_cool, range)
10.times do |i|
workout = (run + walk) * i + warm_cool
return i if range.include?(workout)
end
# Couldn't find a matching interval
nil
end
Where you call it like this:
if (intervals = intervals_required(run, walk, (min_workout..max_workout))
puts "..."
end
You're probably thinking that your first iteration is yielding the expression
(3 + 2) * 0 + 1 + 8
which would evaluate correctly, but you need to understand the way += works under the hood.
Underneath the syntax conveniences of += given to you by Ruby, you're actually doing a few things at once. += is an assignment method, and everything to the right of it is an argument to the method and wrapped in implicit parentheses, as in i += (1 + 8). It's actually two method calls in one, adding the receiver to the argument before assignment, like so
i = i + (1 + 8)
Underneath all the syntactic sugar it really looks like this with dot notation and parentheses
i.=(i.+(1 + 8))
So instead of
(3 + 2) * 1 + 8
5 * 1 + 8
5 + 8
13
on the first pass you're actually getting
(3 + 2) * (i = 0 + (1 + 8))
(3 + 2) * 9
5 * 9
45
and skipping your upper condition of 40, so it just keeps going. i is now set to 9, and i increases by 9 on each pass, so your next result is 90, then 135, and so on.
Try wrapping the assignment in parentheses, like this
(run + walk) * (i += 1) + 8
Also consider adding a guard clause inside your loop to prevent infinite repetition, something like break if workout > max_workout
I believe you problem is that you have reached an invalid state with your until loop and it just keeps going out of bounds. Your logic makes sense for a while type scenario because you want it to stop whenever both of those are true. However with an until it will break from the loop when your condition equals true.
So if workout is more than the max_workout it will keep incrementing since it will never satisfy the conditional and the until will keep going.
Related
How would I do this using ruby and only using a while loop with if, elsif and else only?
sum = 0
i = 1
while i < 251
sum += i
i += 1
end
puts sum
You don’t need a while loop to implement this. But if they want a while loop…
n = 250
while true
puts ( n * (n + 1)) / 2
break
end
Some of the comments have alternatives that are interesting, but keeping this one because it has the flow of control at least pass through the while loop. Not sure if it counts as using a loop if control doesn't even enter the body of the loop.
Though I certainly admit I may be wrong, as for as I can tell, in order to produce
*
**
***
****
*****
with a #times do loop, you need to initialize a variable, and only that variable, outside of the loop.
For example,
m=0
5.times do
m+=1
puts "*" * m
end
produces the aforementioned image, however both
Variant 1
m=0
m+=1
5.times do
puts "*" * m
end
and
Variant 2
5.times do
m=0
m+=1
puts "*" * m
end
produce
*
*
*
*
*
*
*
*
*
*
=> 10
Why is this the case?
More interestingly,
Variant 3
6.times do
m=0
puts "*" * m
m+=1
end
produces a series of 6 blank lines followed by a return of 6. Clearly, the placement of both the initialization of the variable and the iterator matter (at least with #times do loops), but my question is why? If this is a case of "I know you think you want an answer but you really don't want to go down this rabbit hole" then maybe we could treat this as a fun version of reddit's "explain it to me like i'm five", stackoverflow style. For example, with Variant 3, since m is initialized to 0, I would expect a blank line on the first iteration since I am essentially telling Ruby to multiply the asterisk symbol by the value of m at that moment. However, at the end of the first iteration, I would also expect the value of m to increment by 1. It's almost as if Ruby does not get to that line because if it did then the 2nd iteration should include m with a value of 1 and hence produce a line with one asterisk.
In variant 1, m is initialized, immediately incremented by 1, and then you begin the do loop. Each of the 5 times you run through the loop, m is going to be 1, since it was defined outside the loop.
In variant 2, you are closer but the do loop will reset m and increment by 1 each time through the loop. In both of these examples, m = 1 when you puts "*"
With variant 3, you are correct that the first time through the loop you will have a blank line since m = 0. However, since you are looping through these commands, when m increments the script will repeat, so the second time through the loop m is again reset to 0. You may be confusing yourself by using irb - running that same script from the terminal will yield 6 blank lines. I haven't used irb enough to know why exactly the 6 is returned, but I do know that irb will always return something, even if it's nil.
Obviously the first variant won't work because you don't do anything inside the loop to increase m.
The second variant won't work because you keep resetting it to 0 before incrementing it.
Just started learning about loops in ruby and I'm trying to figure out how to add a number to itself.. Like 1+1+1+1+1...
I've tried variations of
3.times() do
self.+(self)
end
But always get undefined method '+'.
I've done this too
number = 1
3.times() do
number = number.+(number)
end
Although it works, it isn't what I'm really trying to do since it's giving me 8, and I just wanted it to be 4 by adding 1+1 over and over. Also I want to be able to use any number not necessarily always 1.
Using times you can do it like:
number = 1
3.times do
number += number
end
puts number
#=> 3
number += number is just a shortcut for number = number + number
Perhaps having another variable to hold onto the sum could fix your issue. Right now, your number starts as 1. Then your loop runs 3 times and looks like the following:
(number = number.+(number))
Loop 1:
number = 1.+(1) ==> 2
number = 2.+(2) ==> 4
number = 4.+(4) ==> 8
Above youre actually multiplying the number by itself X.times
Instead, store the sum of the numbers in a separate variable like this:
sumOfNumbers = 0
numberToAdd = 1
3.times() do
sumOfNumbers = sumOfNumbers.+(numberToAdd)
end
or better yet
sumOfNumbers += numberToAdd
I was given this problem to solve with Ruby:
Compute the sum of cubes for a given range a through b. Write a method called sum_of_cubes to accomplish this task.
I wrote this:
def sum_of_cubes(a, b)
sum = 0
for x in a..b
c = x ** 3
end
sum += c
end
I got the value of the cube of b. What is wrong with this code? How can I solve this problem with a simple loop?
Thanks!
I would use Enumerable#reduce
def sum_of_cubes min, max
(min..max).reduce(0) { |a, b| a + b ** 3 }
end
A little explanation of what's happening here
We start with range (min..max) which is an Enumerable
irb> (1..3).is_a? Enumerable
=> true
Using the reduce instance method we get from Enumerable, we can use a block of code that gets called for each item in our (enumerable) range, and ultimately returns a single value.
The function name makes sense if you think "take my group of items and reduce them to a single value."
Here's our block
{ |a, b| a + b ** 3 }
We called reduce with 0 which is the initial value given to the block's a param
The return value of the block is passed to the block's a param on subsequent calls
Each item in the range will be passed to the block's b param
Let's step through and see how it works
(1..3).reduce(0) { |a, b| a + b ** 3 }
the first block call gets a=0 (initial value) and b=1 (first item in our range)
the return value of our block is 0 + 1 ** 3 or 1
the second block call gets a=1 (return value from the last call) and b=2 (the second item in our range)
the return value of our block is 1 + 2 ** 3 or 9
the third block call gets a=9 (return value from the last call) and b=3 (the third and last item in our range)
the return value of our block is 9 + 3 ** 3 or 36
the final return value of reduce is the last-called block's return value
in this case 36
You need to have sum += c inside the loop. And then return sum when done.
Here’s another way to calculate this. It doesn’t address your problems with your loop but I think it’s worth mentioning.
The sum of cubes of integers 13 + 23 + 33 + ... + n3 is given by the formula (n(n + 1)/2)2, so the sum of cubes of a given range min..max is therefore given by:
(max(max + 1)/2)2 - ((min-1)((min-1) + 1)/2)2
In code this could look like:
def sum_of_cubes_fixed min, max
lower = min - 1
(((max * (max + 1))/2) ** 2) - (((lower * (lower + 1))/2) ** 2)
end
This code avoids the loop, and so is O(1) rather than O(n) (almost – I’m hand waving a bit here, the time complexity of the multiplications and exponentiations will depend on the size of the numbers). For small sized ranges you won’t notice this, but for larger sizes the difference between this and the loop version becomes increasingly obvious. I haven’t done any strict benchmarks, but a quick test on my machine with the range 1 to 10,000,000 takes several seconds with the reduce method but is almost instantaneous with this method.
Normally I would just use reduce for something like this, but the structure of the task suggested that there might be a better way. With the help of Google I found the formula and came up with a more efficient solution (at least for large ranges).
(a..b).map{|i| i**3 }.inject(&:+) map and inject does the job, elegantly.
EDIT: Although it walks through the list twice ;)
Here is what I'm doing:
(1..999).each do |a|
(1..999).each do |b|
if Math.sqrt(a**2 + b**2) % 1 == 0 && a + b + Math.sqrt(a**2 + b**2) == 1000 && a >= b
puts a * b * Math.sqrt(a**2 + b**2)
end
end
end
What is happening is that a and b are interchangeable in the formulas so there are two matches and thus puts gets outputted twice. To fix this, I added a >= b and now it only gets outputted once. But, if a == bit outputs it twice. I know that a and b will always be different in the example I'm using, but this seems like bad design to me.
Two questions:
Is there a better pattern in Ruby for taking an array and comparing it to it self?
How can I avoid it outputting twice always. I could set a variable that if changed before the start of the next loop would break out. Is that the proper way to do this?
#using combination
(1..999).to_a.combination(2).each do |low, high|
if Math.sqrt(low**2 + high**2) % 1 == 0 && low + hight + Math.sqrt(low**2 + high**2) == 1000
puts low * high * Math.sqrt(low**2 + high**2)
end
end
Edited to use a little better practice (each block with first and second for arrays of arrays)