Better way to write this in Ruby? - ruby

I'm new to Ruby. After a ton of refactoring I came down to this. Is there a better way to write this?
51 def tri_num?(n)
52 i = 1
53 while i < n
54 return i if i * (i + 1) / 2 == n
55 i += 1
56 end
57 raise InvalidTree
58 end

What about solving it directly?
def tri_num? n
i = (0.5*(-1.0 + Math.sqrt(1.0 + 8.0*n))).to_i
if i*(i+1)/2 == n
return i
else
raise InvalidTree
end
end
Though I don't know if tri_num? is a good name. Usually a function ending with a ? should return true or false.

Yes.
def tri_num?(n)
1.upto(n-1) do |i|
return i if i * (i + 1) / 2 == n
end
raise InvalidTree
end

I thought the same as dantswain, basically invert the equation:
=> i * (i + 1) / 2 = n
=> i * (i + 1) = 2*n
=> i^2 + i = 2*n
=> i^2 + i -2*n = 0
And the solutions for the above are:
i = (-1 +- sqrt(1+8n))/2
Here I don't consider the - solution as it will give negative for any value of n bigger than 0, in the end the code is:
def tri_num?(n)
i = (-1 + Math.sqrt(1 + 8*n))/2.0
return i.to_i if i == i.to_i
raise InvalidTree
end

def tri_num?(n)
(1...n).each do |i|
return i if i * (i + 1) / 2 == n
end
rails InvalidTree # not defined..
end

Related

How to loop over an entire method until you achieve what you want ? (ruby)

I'm learning ruby and practicing with codewars, and I've come to a challenge that I feel I mainly understand (rudimentarily) but I'm unable to figure out how to continue looping over the method until I reach the result I'm looking for.
The challenge is asking to reduce a number, by multiplying its digits, until the multiplication results in a single digit. In the end it wants you to return the number of times you had to multiply the number until you arrived at a single digit. Example -> given -> 39; 3 * 9 = 27, 2 * 7 = 14, 1 * 4 = 4; answer -> 3
Here's my code :
def persistence(n)
if n < 10
return 0
end
arr = n.to_s.split("")
sum = 1
count = 0
arr.each do |num|
sum *= num.to_i
if num == arr[-1]
count += 1
end
end
if sum < 10
return count
else
persistence(sum)
end
end
Thanks for your help!
Your function is looking great with recursion but you are reseting the count variable to 0 each time the loop runs, I think if you use an auxiliar method it should run ok:
this is in base of your code with minor improvements:
def persistence(n)
return 0 if n < 10
count = 0
multiply_values(n, count)
end
def multiply_values(n, count)
arr = n.to_s.chars
sum = 1
arr.each do |num|
sum *= num.to_i
if num == arr[-1]
count += 1
end
end
if sum < 10
return count
else
multiply_values(sum, count)
end
end
a shorter solution could be to do:
def persistence(n)
return 0 if n < 10
multiply_values(n, 1)
end
def multiply_values(n, count)
sum = n.to_s.chars.map(&:to_i).reduce(&:*)
return count if sum < 10
multiply_values(sum, count + 1)
end
and without recursion:
def persistence(n)
return 0 if n < 10
count = 0
while n > 10
n = n.to_s.chars.map(&:to_i).reduce(&:*)
count += 1
end
count
end
Let's look at a nicer way to do this once:
num = 1234
product = num.to_s.split("").map(&:to_i).reduce(&:*)
Breaking it down:
num.to_s.split("")
As you know, this gets us ["1", "2", "3", "4"]. We can easily get back to [1, 2, 3, 4] by mapping the #to_i method to each string in that array.
num.to_s.split("").map(&:to_i)
We then need to multiply them together. #reduce is a handy method. We can pass it a block:
num.to_s.split("").map(&:to_i).reduce { |a, b| a * b }
Or take a shortcut:
num.to_s.split("").map(&:to_i).reduce(&:*)
As for looping, you could employ recursion, and create product_of_digits as a new method for Integer.
class Integer
def product_of_digits
if self < 10
self
else
self.to_s.split("").map(&:to_i).reduce(&:*).product_of_digits
end
end
end
We can now simply call this method on any integer.
1344.product_of_digits # => 6

Improve recursive methods

This Fibonacci method works with small numbers but the calculations won't work when factored out very far. How can I store what is done at lower level cycles to be reused for later cycles?
def fib(n)
return 0 if n==0
return 1 if n==1
fib(n-1) + fib(n-2)
end
Here's a very basic way to memoize a method like this with a Hash.
#fib_memo = {}
def fib(n)
return 0 if n == 0
return 1 if n == 1
return #fib_memo[n] if #fib_memo.key?(n)
#fib_memo[n] = fib(n - 1) + fib(n - 2)
end
And it doesn't take much imagination to see how that could be shortened to this:
#fib_memo = { 0 => 0, 1 => 1 }
def fib(n)
#fib_memo[n] ||= fib(n - 1) + fib(n - 2)
end
Or, as spickermann demonstrates, you can do it all in a Hash's default proc, but that's just showing off. ;)
You could use a hash to store lower level cycles:
fibonacci = Hash.new { |h, k| h[k] = k < 2 ? k : h[k-1] + h[k-2] }
fibonacci[100]
#=> 354224848179261915075

Convert excel column letter to integer in Ruby

What's are the simplest way to convert excel-like column letter to integer?
for example:
AB --> 27
AA --> 26
A --> 0
Z --> 25
def excel_col_index( str )
value = Hash[ ('A'..'Z').map.with_index.to_a ]
str.chars.inject(0){ |x,c| x*26 + value[c] + 1 } - 1
end
Or
def excel_col_index( str )
offset = 'A'.ord - 1
str.chars.inject(0){ |x,c| x*26 + c.ord - offset } - 1
end
I would do something like this:
def column_name_to_number(column_name)
multipliers = ('A'..'Z').to_a
chars = column_name.split('')
chars.inject(-1) { |n, c| multipliers.index(c) + (n + 1) * 26 }
end
here's a version of the accepted answer, with a spec:
RSpec.describe "#excel_col_index" do
def excel_col_index(str)
value = Hash[('A'..'Z').map.with_index.to_a]
str.chars.inject(0) { |x, c| x * 26 + value[c] + 1 } - 1
end
{ "A" => 0, "Z" => 25, "AB" => 27, "AA" => 26 }.each do |col, index|
it "generates #{index} from #{col}" do
expect(excel_col_index(col)).to eq(index)
end
end
end
(but I'll also edit the accepted answer to have the required - 1)
ah nevermind..
def cell2num col
val = 0
while col.length > 0
val *= 26
val += (col[0].ord - 'A'.ord + 1)
col = col[1..-1]
end
return val - 1
end
This is one of those bits that you can keep iterating on for a long time. I ended up with this:
"AB1".each_codepoint.reduce(0) do |sum, n|
break sum - 1 if n < 'A'.ord # reached a number
sum * 26 + (n - 'A'.ord + 1)
end # => 27
From the xsv source code

Factorial in Ruby

I'm trying to do in the Ruby program the summation of a factorial. and I can not solve. a clarification, when they run out points .... means that the logic is fine. if you get a F in one of the points, it means that the logic is wrong.
Write a program to calculate the sum of 1 + 1 / (2!) + 1 / (3!) + 1 / (4!) + .... + 1 / (n!) For a given n. Write the program in two ways: using While, For
def factorial(n)
#(n == 0) ? 1 : n * factorial(n - 1)
fact = 1
for i in 1..n
fact = fact * i
end
return fact
end
def sumatoriaWhile(n)
total = n
sumatoria = 0.0
while n > 1
total = total * (n - 1)
n = n - 1
sumatoria =sumatoria + total.to_f
end
return (1 + (1 / total.to_f)).round(2)
end
def sumatoriaFor(n)
fact = 1
sumatoria = 0.0
for i in 1..n
for j in 1..i
fact = fact * j
end
sumatoria = sumatoria + (1 / fact.to_f)
i = i + 1
end
return sumatoria.round(2)
end
#--- zona de test ----
def test_factorial
print validate(120, factorial(5))
print validate(5040, factorial(7))
print validate(362880, factorial(9))
end
def test_sumatoriaWhile
print validate(1.50, sumatoriaWhile(2))
print validate(1.83, sumatoriaWhile(3))
end
def test_sumatoriaFor
print validate(1.50, sumatoriaFor(2))
print validate(1.83, sumatoriaFor(3))
end
def validate (expected, value)
expected == value ? "." : "F"
end
def test
puts "Test program"
puts "---------------------------"
test_factorial
test_sumatoriaWhile
test_sumatoriaFor
puts " "
end
test
My friend, Thank you for your prompt response. First of all, I appreciate the help given. I am learning ruby programming and want to learn more as you and the other people. if indeed the answer is wrong. I have modified the answer. and also need to know what the function of While. and I hereby amended program again. and now I get an F on the part of WHILE.
def factorial(n)
fact = 1
for i in 1..n
fact = fact * i
end
return fact
end
def sumatoriaWhile(n)
total = n
sumatoria = 0.0
while n < 1
total = total * (n - 1)
sumatoria = sumatoria + (1.0 / total.to_f)
n = n - 1
end
return sumatoria.round(2)
end
def sumatoriaFor(n)
fact = 1
sumatoria = 0.0
for i in 1..n
fact = fact * i
sumatoria = sumatoria + (1.0 / fact.to_f)
end
return sumatoria.round(2)
end
#--- zona de test ----
def test_factorial
print validate(120, factorial(5))
print validate(5040, factorial(7))
print validate(362880, factorial(9))
end
def test_sumatoriaWhile
print validate(1.50, sumatoriaWhile(2))
print validate(1.67, sumatoriaWhile(3))
end
def test_sumatoriaFor
print validate(1.50, sumatoriaFor(2))
print validate(1.67, sumatoriaFor(3))
end
def validate (expected, value)
expected == value ? "." : "F"
end
def test
puts "Test de prueba del programa"
puts "---------------------------"
test_factorial
test_sumatoriaWhile
test_sumatoriaFor
puts " "
end
test
I have a hard time figuring out what you're doing in the summation function. Here's a straightforward function:
def sumatoriaFor(n)
return 0 if n <= 0
factorial = 1
sum = 0.0
for i in 1..n
factorial *= i
sum += 1.0 / factorial.to_f
end
return sum.round(2)
end
def sumatoriaWhile(n)
return 0 if n <= 0
i = 1
factorial = 1
sumatoria = 0.0
while i <= n
factorial *= i
sumatoria += (1.0 / factorial.to_f)
i = i + 1
end
return sumatoria.round(2)
end
The while look is straight forward too now. Also your validation is wrong:
1 + 1/2 + 1/6 ~= 1 + 0.5 + 0.17 = 1.67

adding 1 to each element of an array

i tred creating a method called "Sum_plus_one" that accepts an array argument containing integers. Themethod should return the sum of the integers in the array after adding one to each of them.
example:
sum_plus_one([1,2,3])
result should be: 9
my code looks like this
def sum_plus_one(*nums)
for num in nums
num + 1
end
total = 0
for num in nums
total += num
end
return total
end
Why not do a little bit of math beforehand and see that summing the array-elements-plus-one is the same as summing the elements and then adding the array length? For example:
(5+1) + (6+1) + (11+1) = 5 + 6 + 11 + (1 + 1 + 1)
= 5 + 6 + 11 + 3
That gives you something nice and simple:
array.inject(:+) + array.length
map/reduce is handy here:
def sum_plus_one(nums)
nums.map(&:succ).reduce(:+)
end
Edit:
here is one way to make your code work:
def sum_plus_one(nums)
nums.map! do |num|
num + 1
end
total = 0
for num in nums
total += num
end
return total
end
Functional Style Version
[1, 2, 3].reduce(0) {|acc, n| acc + n + 1}
Use Enumerable#inject
[105] pry(main)> arr
=> [1, 2, 3]
[106] pry(main)> arr.inject(0) { |var, i| var + i + 1 }
=> 9
So the method would look like
def sum_plus_one(*nums)
nums.inject(0) { |var, num| var + num + 1 }
end
your problem is that you need to assign your num + 1 value to the corresponding element of the array that you are enumerating in the first loop.
maybe something like this:
for i in (0...nums.count)
nums[i] += 1
end
after that your program should work
(yes, you can use fancy libraries or constructs instead of the above loop)
please note that you can eliminate the top loop and just add num + 1 to your total in the second loop.

Resources