Ruby on Rails - Calculate Size of Number Range - ruby-on-rails

Forgive my lack of code but I can't quite figure out the best way to achieve the following:
two strings (stored as strings because of the leading 0 - they are phone numbers) :
a = '0123456700'
b = '0123456750'
I am trying to find a way to write them as a range as follows
0123456700 - 750
rather than
0123456700 - 0123456750
which I currently have.
It's not as straightforward as getting the last 3 digits of b since the range can vary and perhaps go up to 4 digits so I'm trying to find the best way of being able to do this.

I'd look up the index of the first unequal pair of characters:
a = '0123456700'
b = '0123456750'
index = a.chars.zip(b.chars).index { |x, y| x != y }
#=> 8
And extract the suffix with:
"#{a} - #{b[index..-1]}" if index
#=> "0123456700 - 50"

Here's a method that returns the range:
def my_range(a, b)
a = a.delete(" ") # remove all spaces from string
b = b.delete(" ")
a, b = b, a if a.to_i > b.to_i # a is always smaller than b
ai, bi = a.to_i, b.to_i
pow = 1
while ai > 1
pow += 1
len = pow if ai % 10 != bi % 10
ai /= 10
bi /= 10
end
a + " - " + b[-len..-1]
end
puts my_range("0123456700", "0123456750") # 0123456700 - 750
puts my_range("0123456669", "0123456675") # 0123456669 - 675
puts my_range("0123400200", "0123500200") # 0123400200 - 3500200
puts my_range("012 345 678", "01 235 0521") # 012345678 - 350521

From my personal library (simplified):
def common_prefix first, second
i = 0
loop{break unless first[i] and second[i] == first[i]; i += 1}
first[0, i]
end
a = "0123456700"
b = "0123456750"
c = "0123457750"
common_prefix(a, b)
# => "01234567"
"#{a} - #{b.sub(common_prefix(a, b), "")}"
# => "0123456700 - 50"
"#{a} - #{c.sub(common_prefix(a, c), "")}"
# => "0123456700 - 7750"
Note. This will work correctly only under the assumption that all strings are right padded with 0 to be the same length.

Related

How to round Decimals to the First Significant Figure in Ruby

I am attempting to solve an edge case to a task related to a personal project.
It is to determine the unit price of a service and is made up of the total_amount and cost.
Examples include:
# 1
unit_price = 300 / 1000 # = 0.3
# 2
unit_price = 600 / 800 # = 0.75
# 3
unit_price = 500 / 1600 # = 0.3125
For 1 and 2, the unit_prices can stay as they are. For 3, rounding to 2 decimal places will be sufficient, e.g. (500 / 1600).round(2)
The issue arises when the float becomes long:
# 4
unit_price = 400 / 56000 # = 0.007142857142857143
What's apparent is that the float is rather long. Rounding to the first significant figure is the aim in such instances.
I've thought about using a regular expression to match the first non-zero decimal, or to find the length of the second part and apply some logic:
unit_price.match ~= /[^.0]/
unit_price.to_s.split('.').last.size
Any assistance would be most welcome
One should use BigDecimal for this kind of computation.
require 'bigdecimal'
bd = BigDecimal((400.0 / 56000).to_s)
#⇒ 0.7142857142857143e-2
bd.exponent
#⇒ -2
Example:
[10_000.0 / 1_000, 300.0 / 1_000, 600.0 / 800,
500.0 / 1_600, 400.0 / 56_000].
map { |bd| BigDecimal(bd.to_s) }.
map do |bd|
additional = bd.exponent >= 0 ? 0 : bd.exponent + 1
bd.round(2 - additional) # THIS
end.
map(&:to_f)
#⇒ [10.0, 0.3, 0.75, 0.31, 0.007]
You can detect the length of the zeros string with regex. It's a bit ugly, but it works:
def significant_round(number, places)
match = number.to_s.match(/\.(0+)/)
return number unless match
zeros = number.to_s.match(/\.(0+)/)[1].size
number.round(zeros+places)
end
pry(main)> significant_round(3.14, 1)
=> 3.14
pry(main)> significant_round(3.00014, 1)
=> 3.0001
def my_round(f)
int = f.to_i
f -= int
coeff, exp = ("%e" % f).split('e')
"#{coeff.to_f.round}e#{exp}".to_f + int
end
my_round(0.3125)
#=> 0.3
my_round(-0.3125)
#=> -0.3
my_round(0.0003625)
#=> 0.0004
my_round(-0.0003625)
#=> -0.0004
my_round(42.0031)
#=> 42.003
my_round(-42.0031)
#=> -42.003
The steps are as follows.
f = -42.0031
int = f.to_i
#=> -42
f -= int
#=> -0.0031000000000034333
s = "%e" % f
#=> "-3.100000e-03"
coeff, exp = s.split('e')
#=> ["-3.100000", "-03"]
c = coeff.to_f.round
#=> -3
d = "#{c}e#{exp}"
#=> "-3e-03"
e = d.to_f
#=> -0.003
e + int
#=> -42.003
To instead keep only the most significant digit after rounding, change the method to the following.
def my_round(f)
coeff, exp = ("%e" % f).split('e')
"#{coeff.to_f.round}e#{exp}".to_f
end
If f <= 0 this returns the same as the earlier method. Here is an example when f > 0:
my_round(-42.0031)
#=> -40.0

How to optimize the program with two for loops

I have a following programm
def calc_res(a)
n = a.length
result = 0
for i in 0 .. (n - 1)
for j in i .. (n - 1)
if (a[i] != a[j] && j - i > result) then
result = j - i
end
end
end
return result
end
which return following output
irb(main):013:0> calc_res([4, 6, 2, 2, 6, 6, 4])
=> 5
but it is taking time if array size is too large e.g. [0,1,2,3,.....70000]
can any one suggest me how can I optimize it.
Thanks
If I have understood the problem you are trying to solve (from code)
def calc_res(a)
last_index = a.length - 1
index = 0
while a[index] == a.last do
index = index + 1
break if index == last_index
end
last_index - index
end
It checks items from start if they are equal to items from end, end it moves the index toward the last element. As I understood you search for max length between different elements.
For you problem with [4, 6, 2, 2, 6, 6, 4] it will have one iteration and return 5, for the problem with [1...70000] it will have zero iterations and will return the difference in positions for those two (size of the array - 1)
My understanding is that the problem is to find two unique elements in the array whose distance apart (difference in indices) is maximum, and to return the distance they are apart. I return nil if all elements are the same.
My solution attempts to minimize the numbers of pairs of elements that must be examined before an optimal solution is identified. For the example given in the question only two pairs of elements need be considered.
def calc_res(a)
sz = a.size-1
sz.downto(2).find { |n| (0..sz-n).any? { |i| a[i] != a[i+n] } }
end
a = [4,6,2,2,6,6,4]
calc_res a
#=> 5
If sz = a.size-1, sz is the greatest possible distance two elements can be apart. If, for example, a = [1,2,3,4], sz = 3, which is the number of positions 1 and 4 are apart.
For a, sz = a.size-1 #=> 6. I first determine if any pair of elements that are n = sz positions apart are unique. [a[0], a[6]] #=> [4,4] is the only pair of elements 6 positions apart. Since they are not unique I reduce n by one (to 5) and examine all pairs of elements n positions apart, looking for one whose elements are unique. There are two pairs 5 positions apart: [a[0], a[5]] #=> [4,6] and [a[1], a[6]] #=> [6,4]. Both of these meet the test, so we are finished, and return n #=> 5. In fact we are finished after testing the first of these two pairs. Had neither these pairs contained unique values n would have been reduced by 1 to 4 and the three pairs [a[0], a[4]] #=> [4,6], [a[1], a[5]] #=> [6,6] and [a[2], a[6]] #=> [2,6] would have been searched for one with unique values, and so on.
See Integer#downto, Enumerable#find and Enumerable#any?.
A more rubyesque versions include:
def calc_res(a)
last = a.last
idx = a.find_index {|e| e != last }&.+(1) || a.size
a.size - idx
end
def calc_res(a)
last = a.last
a.size - a.each.with_index(1).detect(->{[a.size]}) {|e,_| e != last }.last
end
def calc_res(a)
last = a.last
a.reduce(a.size) do |memo, e|
return memo unless e == last
memo -= 1
end
end
def calc_res(a)
return 0 if b = a.uniq and b.size == 1
a.size - a.index(b[-1]).+(1)
end

Random sum of elements in an array equals to y - ruby [duplicate]

This question already has answers here:
Finding all possible combinations of numbers to reach a given sum
(32 answers)
Closed 6 years ago.
Need to create an array whose sum should be equal to expected value.
inp = [1,2,3,4,5,6,7,8,9,10]
sum = 200
output:
out = [10,10,9,1,3,3,3,7,.....] whose sum should be 200
or
out = [10,7,3,....] Repeated values can be used
or
out = [2,3,4,9,2,....]
I tried as,
arr = [5,10,15,20,30]
ee = []
max = 200
while (ee.sum < max) do
ee << arr.sample(1).first
end
ee.pop(2)
val = max - ee.sum
pair = arr.uniq.combination(2).detect { |a, b| a + b == val }
ee << pair
ee.flatten
Is there any effective way to do it.
inp = [1,2,3,4,5,6,7,8,9,10]
sum = 20
inp.length.downto(1).flat_map do |i|
inp.combination(i).to_a # take all subarrays of length `i`
end.select do |a|
a.inject(:+) == sum # select only those summing to `sum`
end
One might take a random element of resulting array.
result = inp.length.downto(1).flat_map do |i|
inp.combination(i).to_a # take all subarrays of length `i`
end.select do |a|
a.inject(:+) == sum # select only those summing to `sum`
end
puts result.length
#⇒ 31
puts result.sample
#⇒ [2, 4, 5, 9]
puts result.sample
#⇒ [1, 2, 3, 6, 8]
...
Please note, that this approach is not efficient for long-length inputs. As well, if any original array’s member might be taken many times, combination above should be changed to permutation, but this solution is too ineffective to be used with permutation.
I found an answer of this question in the following link:
Finding all possible combinations of numbers to reach a given sum
def subset_sum(numbers, target, partial=[])
s = partial.inject 0, :+
#check if the partial sum is equals to target
puts "sum(#{partial})=#{target}" if s == target
return if s >= target #if we reach the number why bother to continue
(0..(numbers.length - 1)).each do |i|
n = numbers[i]
remaining = numbers.drop(i+1)
subset_sum(remaining, target, partial + [n])
end
end
subset_sum([1,2,3,4,5,6,7,8,9,10],20)

Calculate letter grade using a series of grades

Noob to Ruby here. Working through some exercises and have hit a wall.
Exercise: Calculate the letter grade of a series of grades
Create a method get_grade that accepts an Array of test scores. Each score in the array should be between 0 and 100, where 100 is the max score.
Compute the average score and return the letter grade as a String, i.e., 'A', 'B', 'C', 'D', 'E', or 'F'.
I keep returning the error:
avg.rb:1: syntax error, unexpected tLBRACK, expecting ')'
def get_grade([100,90,80])
^
avg.rb:1: syntax error, unexpected ')', expecting $end
Here's what I have so far. I'd like to stick with the methods below or .join as I'm trying to work with the methods we're learning in class. So sum, inject, etc won't necessarily be helpful. And I apologize in advance for the specificity of the request :) I'm sure there's a way better way that is way less code, but I'm just trying to learn it this way to start.
def get_grade([100,90,80])
get_grade = (array[0] + array[1] + array[2]).to_i / array.length.to_i
case get_grade
when 90..100
"A"
when 80..90
"B"
when 70..80
"C"
when 60..70
"D"
when 0..60
"F"
else
"Error"
end
end
puts get_grade([100,90,80])
You can't just randomly dump an array literal like [100,90,80] into the parameter list of a function definition. Judging by the function body, I think you meant to accept a single parameter array:
def get_grade(array)
grade = (array[0].to_i + array[1].to_i + array[2].to_i) / array.length
case grade
# unchanged
end
end
A terse replacement of the big case statement, for fun:
def letter_grade( score ) # assumes that score is between 0 and 100 (not 0-1)
%w[F F F F F F D C B A][ (score/10.0).floor ] || 'A' # handles grades >=100
end
Or, for more granularity:
def letter_grade( score ) # score is between 0 and 100 (not 0-1)
grades = %w[F F F F F F F F F F F F F F F F F F D- D D+ C- C C+ B- B B+ A- A A+ A+]
grades[ (3.0*score/10).floor ]
end
Thanks for the help today! Here's what I ended up doing to make it work with more than just 3 arguments. I used an Array#each method. I imagine there's a more elegant solution out there, but it worked! Worked on this since 10:00 AM, greatly appreciate the help!
def get_grade(array)
sum = 0
array.each do |element|
sum += element
end
average = sum / array.length
if average >= 90
grade = "A"
elsif average >= 80
grade = "B"
elsif average >= 70
grade = "C"
elsif average >= 60
grade = "D"
elsif average >= 0
grade = "F"
else
"Error"
end
end
puts get_grade([70,80,80,90,100])
puts get_grade([100,80,90,11,20])
puts get_grade([30,20,10,60,75])
Remember that the max score is 100 (and it can be assumed that the min is 0).
def get_grade(array)
sum = 0
array.each do |x|
sum += x
end
average = sum / array.length
if average > 100
print "Grades must be no more than 100!"
elsif average >= 90
grade = "A"
elsif average >= 80
grade = "B"
elsif average >= 70
grade = "C"
elsif average >= 60
grade = "D"
elsif average >=0
grade = "F"
else
print "Grades must be no less than 0!"
end
grade
end
puts get_grade([100,90,80]) == "A"
puts get_grade([98,90,80]) == "B"
puts get_grade([80,80,80]) == "B"
puts get_grade([55,45,35]) == "F"
puts get_grade([101,100,104])
puts get_grade([-2,-3,-4])
Added a proc so that even if a user enters a score over 100 it won't be calculated into the average.
Also refactored the switch statements to one line each. Let me know if this helps. Good luck.
def get_grade array
scores_under_100 = Proc.new {|score| score <= 100 && score > 0}
scores = array.select(&scores_under_100)
average = scores.inject(:+) / scores.size
case average
when 90..100 then puts "A."
when 80..89 then puts "B."
when 70..79 then puts "C."
when 60..69 then puts "D."
else puts "F."
end
end
puts get_grade([100, 100, 90, 67, 85, 200, 290, 299, 299])

Ruby/Rails while loop not breaking correctly?

I am working on a client's site, and I'm writing an amortization schedule calculator in in ruby on rails. For longer loan term calculations, it doesn't seem to be breaking when the balance reaches 0
Here is my code:
def calculate_amortization_results
p = params[:price].to_i
i = params[:rate].to_d
l = params[:term].to_i
j = i/(12*100)
n = l * 12
m = p * (j / (1 - (1 + j) ** (-1 * n)))
#loanAmount = p
#rateAmount = i
#monthlyAmount = m
#amort = []
#interestAmount = 0
while p > 0
line = Hash.new
h = p*j
c = m-h
p = p-c
line["interest"] = h
line["principal"] = c
if p <= 0
line["balance"] = 0
else
line["balance"] = p
end
line["payment"] = h+c
#amort.push(line)
#interestAmount += h
end
end
And here is the view:
- #amort.each_with_index do |a, i|
%li
.m
= i+1
.i
= number_to_currency(a["interest"], :unit => "$")
.p
= number_to_currency(a["principal"], :unit => "$")
.pp
= number_to_currency(a["payment"], :unit => "$")
.b
= number_to_currency(a["balance"], :unit => "$")
What I am seeing is, in place of $0.00 in the final payment balance, it shows "-$-inf", iterates one more loop, then displays $0.00, but shows "-$-inf" for interest. It should loop until p gets to 0, then stop and set the balance as 0, but it isn't. Any idea what I've done wrong?
The calculator is here. It seems to work fine for shorter terms, like 5 years, but longer terms cause the above error.
Edit:
Changing the while loop to n.times do
and then changing the balance view to
= number_to_currency(a["balance"], :unit => "$", :negative_format => "$0.00")
Is a workaround, but i'd like to know why the while loop wouldn't work correctly
in Ruby the default for numerical values is Fixnum ... e.g.:
> 15 / 4
=> 3
You will see weird rounding errors if you try to use Fixnum values and divide them.
To make sure that you use Floats, at least one of the numbers in the calculation needs to be a Float
> 15.0 / 4
=> 3.75
> 15 / 4.0
=> 3.75
You do two comparisons against 0 , which should be OK if you make sure that p is a Float.
As the other answer suggests, you should use "decimal" type in your database to represent currency.
Please try if this will work:
def calculate_amortization_results
p = params[:price].to_f # instead of to_i
i = params[:rate].to_f # <-- what is to_d ? use to_f
l = params[:term].to_i
j = i/(12*100.0) # instead of 100
n = l * 12
m = p * (j / (1 - (1 + j) ** (-1 * n))) # division by zero if i==0 ==> j==0
#loanAmount = p
#rateAmount = i
#monthlyAmount = m
#amort = []
#interestAmount = 0.0 # instead of 0
while p > 0
line = Hash.new
h = p*j
c = m-h
p = p-c
line["interest"] = h
line["principal"] = c
if p <= 0
line["balance"] = 0
else
line["balance"] = p
end
line["payment"] = h+c
#amort.push(line)
#interestAmount += h
end
end
If you see "inf" in your output, you are doing a division by zero somewhere.. better check the logic of your calculation, and guard against division by zero.
according to Wikipedia the formula is:
http://en.wikipedia.org/wiki/Amortization_calculator
to improve rounding errors, it's probably better to re-structure the formula like this:
m = (p * j) / (1 - (1 + j) ** (-1 * n) # these are two divisions! x**-1 == 1/x
which is equal to:
m = (p * j) + (p * j) / ((1 + j) ** n) - 1.0)
which is equal to: (use this one)
q = p * j # this is much larger than 1 , so fewer rounding errors when dividing it by something
m = q + q / ((1 + j) ** n) - 1.0) # only one division
I think it has something to do with the floating point operations precision. It has already been discussed here: Ruby number precision with simple arithmetic and it would be better to use decimal format for financial purposes.
The answer could be computing the numbers in the loop, but with precomputed number of iterations and from the scratch.

Resources