Infinite While loops in ruby - ruby

I have the following program which should count the number of years it should take a population to grow to the desired size. Whenever I run this I get an infinite loop. Can someone help me identify my error?
def pop_growth(start, percent, desired)
year_count = 0
while start <= desired
year_count += 1
start = start + (start * (percent / 100))
end
return year_count
end

I'm sure that you are trying with Integers (instead floats), so you are losing precision try this
def pop_growth(start, percent, desired)
year_count = 0
while start <= desired
year_count += 1
start = start + (start * (percent.to_f / 100))
end
return year_count
end
and let me know if it works for you. if not can you send me your start, percent and desired values?

The proper answer is given by Horacio, let me rewrite this in idiomatic ruby:
def pop_growth start, percent, desired
(0..Float::INFINITY).inject(start) do |memo, years|
break years if memo > desired
memo *= (1.0 + percent / 100.0)
end
end
or, with infinite loop:
def pop_growth start, percent, desired
loop.each_with_object(years: 0, count: start) do |_, memo|
break memo[:years] if memo[:count] > desired
memo[:years] += 1
memo[:count] *= (1.0 + percent / 100.0)
end
end

Three ways.
#1 Solve equation
Solve desired = start * (1.0 + 0.01 * percent)**n for n:
def pop_growth(start, percent, desired)
Math.log(desired.to_f/start)/Math.log(1.0 + percent/100.0)
end
years = pop_growth(100, 10, 200)
#=> 7.272540897341713
years.ceil #=> 8 if desired.
#2 Compound until desire met
def pop_growth(start, percent, desired)
return 0 if start >= desired
alpha = 1.0 + 0.01 * percent
1.step.find { (start *= alpha) >= desired }
end
pop_growth 100, 10, 200
#=> 8
#3 Use recursion
def pop_growth(start, percent, desired, years=0)
return years if start >= desired
pop_growth(start*(1.0+0.01*percent), percent, desired, years+1)
end
pop_growth 100, 10, 200
#=> 8

Just add .to_f method to percent or divide by 100.0, which will convert the integer into float.
start + (start * (percent / 100))
When you are dividing, you need at least one float number in order to return the exact division answer, else Ruby will round it down to nearest whole number, which in this case percent / 100 will result in 0, assuming that the value in percent is less than 100. This will cause this statement start + (start * (percent / 100)) to become start = start + 0, which is why you are seeing the infinite loop.

Related

For from a range(0, value goal)

'Hi, I'm trying to calculate how many months to achieve a million dollars with compounding interest and monthly investments. There are my tries.'
'This first code work, but I want to replace the 92 in the rage with a compare formula like fv >= 1000000.'
'When I place the range like here, it doesn't work.'
Try while-loop may help:
pv = 130000 # present value
i = 4000 # regular monthly investment
r = 0.1375 # annual interest rate
n = 0 # number of months
# for n in range(0, 92):
fv = 0
while fv < 1000000:
fv = pv * (1 + r / 12) ** n + i * (((1 + r / 12) ** n - 1) / (r / 12))
n += 1 #don't forget
print(fv)
print(n)
You need to manually increase the value of n

working with exponentiation of time

I have a string as follows:
"00:48:22"
From right to left, I am working with a power of 60, because I want to get the total number of seconds from hours, minutes, seconds.
This is what I have tried:
clock
=> "00:48:22"
i = 0
result = clock.split(":").reverse.reduce(0) do |acc, segment|
acc += segment.to_i + (60 ** i)
i += 1
acc
end
=> 3731
The result is off. It should be 2902. Any idea what I am doing wrong?
Your algorithm is a little messy and error prone.
This will give you the right answer:
clock = '00:48:22'
clock.split(':').map(&:to_i).reduce(0) do |acc, segment|
acc * 60 + segment
end
You are adding where you should be multiplying
acc += segment.to_i * (60 ** i)
require 'date'
d = Date.today
(Time.new(d.year,d.month,d.day,*str.split(':').map(&:to_i))-d.to_time).to_i
#=> 2902

Generate hex numbers based on percentage

I'm looking for a way to generate a gradual hexadecimal color based on a percentage with Ruby.
0% = #6da94a
50% = #decc30
100% = #ce2d4a
Then programmatically generate the hexadecimal values in between those.
So I might have something like percent_to_hex(10) and that would spit out whatever hexadecimal value is 10% along the gradual gradient between 0% (green) and 50% (yellow).
Actually there is a small mistake in tralston's answer.
(x + percent * 100 * (y - x)).round
should be changed to:
(x + percent / 100.0 * (y - x)).round
Also i.to_s(16) would be a problem if you have 0 (255), as you can get a result like "ff0ff". I would recommend using "%02x" % i instead.
Here is the complete example:
def percent_to_hex(percent, start, stop)
colors = [start,stop].map do |c|
c.scan(/../).map { |s| s.to_i(16) }
end
colors_int = colors.transpose.map do |x,y|
(x + percent / 100.0 * (y - x)).round
end
colors_int.map { |i| "%02x" % i }.join("")
end
Not a very polished method, but here's a good start:
# Example input: percent_to_hex(25, "abcdef", "ffffff") => "c0daf3"
def percent_to_hex(percent, start, stop)
colors = [start,stop].map do |c|
c.scan(/../).map { |s| s.to_i(16) }
end
colors_int = colors.transpose.map do |x,y|
(x + percent * 100 * (y - x)).round
end
colors_int.map { |i| i.to_s(16) }.join("")
end
Of course if you could customize it further to add or remove the leading "#" of the hex color code, etc.

How do I generate a number in a percentage range?

I am making a text adventure game and have to randomise the stats of my hero's enemies.
Is there a way to generate a random whole number from within a percentage range?
Like this: BaseHealth ± 10%, where BaseHealth is a variable.
def randomize_value(value, percent)
bottom = (value * (1 - percent / 100.0)).to_i
up = (value * (1 + percent / 100.0)).to_i
(bottom..up).to_a.sample
end
health = randomize_value(BaseHealth, 10)
This is assuming that health is to be integer.
If BaseHealth is integer,
def take_random base, percent
d = (base * percent / 100.0).to_i
base - d + rand(d * 2)
end
take_random(BaseHealth, 10)
or following Stefan's suggestion,
def take_random base, percent
d = (base * percent / 100.0).to_i
rand(base - d..base + d)
end
take_random(BaseHealth, 10)
I understand what you mean now
You can do this:
BaseHealth = ( rand(BaseHealth * 0.2) + BaseHealth*0.9 ).to_i
This can be accomplished with some basic arithmetic:
TotalHealth = BaseHealth + (BaseHealth * (rand(21)-10)/100)
This will take the BaseHealth and multiply it by a random number 0..20 minus 10, converted to a percent.
Assume BaseHealth = 20:
If rand returns 17, you get 7/100 = .07 so TotalHealth = 21.41
If rand returns 7, you get -7/100 = -.07 so TotalHealth = 18.6

Can this check digit method be refactored?

I have the following method for doing a check digit on a tracking number, but it just feels lengthy/sloppy. Can it be refactored and just generally cleaned up?
I'm running Ruby 1.8.7.
def is_fedex(number)
n = number.reverse[0..14]
check_digit = n.first.to_i
even_numbers = n[1..1].to_i + n[3..3].to_i + n[5..5].to_i + n[7..7].to_i + n[9..9].to_i + n[11..11].to_i + n[13..13].to_i
even_numbers = even_numbers * 3
odd_numbers = n[2..2].to_i + n[4..4].to_i + n[6..6].to_i + n[8..8].to_i + n[10..10].to_i + n[12..12].to_i + n[14..14].to_i
total = even_numbers + odd_numbers
multiple_of_ten = total + 10 - (total % 10)
remainder = multiple_of_ten - total
if remainder == check_digit
true
else
false
end
end
EDIT: Here are valid and invalid numbers.
Valid: 9612019950078574025848
Invalid: 9612019950078574025847
def is_fedex(number)
total = (7..20).inject(0) {|sum, i| sum + number[i..i].to_i * ( i.odd? ? 1 : 3 ) }
number[-1].to_i == (total / 10.0).ceil * 10 - total
end
I believe you should keep your code. While it's not idiomatic or clever, it's the one you will have the least trouble to understand a few months from now.
I'm not a ruby programmer, so if any of the syntax is off, I apologize but you should get the general idea. A few things I see: First, you don't need to slice the array, a single index should be sufficient. Second, Instead of splitting even and odd, you could do something like this:
total = 0
for i in (1..14)
total += n[i].to_i * ( i % 2 == 1 ? 1 : 3 )
end
Third, remainder could be simplified to 10 - (total % 10).
I realize you're running 1.8.7, but here's my attempt using each_slice and inject in conjunction, a 1.9.2 feature:
def is_fedex(number)
total = number.reverse[1..14].split(//).map(&:to_i).each_slice(2).inject(0) do |t, (e,o)|
t += e*3 + o
end
10 - (total % 10) == number[-1].to_i
end
It passes both tests
Give this a try:
#assuming number comes in as a string
def is_fedex(number)
n = number.reverse[0..14].scan(/./)
check_digit = n[0].to_i
total = 0
n[1..14].each_with_index {|d,i| total += d.to_i * (i.even? ? 3 : 1) }
check_digit == 10 - (total % 10)
end
> is_fedex("12345678901231") => true
edit incorporating simplified remainder logic as Kevin suggested
Something like this?
def is_fedex(number)
even_arr, odd_arr = number.to_s[1..13].split(//).map(&:to_i).partition.with_index { |n, i| i.even? }
total = even_arr.inject(:+) * 3 + odd_arr.inject(:+)
number.to_s.reverse[0..0].to_i == (total + 10 - (total % 10)) - total
end
If you can give me a valid and invalid number I can test if it works and maybe tweak it further :)
This function should to:
def is_fedex(number)
# sanity check
return false unless number.length == 15
data = number[0..13].reverse
check_digit = number[14..14].to_i
total = (0..data.length-1).inject(0) do |total, i|
total += data[i..i].to_i * 3**((i+1)%2)
end
(10 - total % 10) == check_digit
end
The arithmetic expression 3**((i+1)%2) might look a bit complex, but is essentially the same as (i.odd? ? 1 : 3). Both variants are correct, which you use is up to you (and might affect speed...)
Also note, that if you use Ruby 1.9, you can use data[i] instead of data[i..i] which is required for for Ruby 1.8.

Resources