Use do...end
instead of {...}
for multi-line blocks.
return
is implicit at the end of a method.
differentiate
is a verb. derivatives
is the plural noun you're looking for when naming the array.
Use Ruby's array methods (map
, reduce
, and friends) to your advantage.
Don't modify external variables from inside a block if you can avoid it. I.e. don't do things like n += 1
, or x -= ...
. A combination of with_index
and reduce
should do the same trick more neatly.
There's some repetition in your handling of the order of coefficients (i.e. the n += 1
in the blocks). It'd be nice to get rid of that.
It'd be nice to specify the number of iterations the approximation should use.
Here's a pretty direct translation of your solution:
# Add a method to Array which zips the array with the index
# of each element in reverse order
class Array
def zip_reverse_index
zip((0...count).to_a.reverse)
end
end
# Use a *-splat to make the method variadic
def solve(*coefficients)
coefficients.map!(&:to_f)
derivatives = coefficients.zip_reverse_index.map { |n, i| n * i }
newton(coefficients, derivatives[0...-1])
end
# Added some (optional) arguments
def newton(coefficients, derivatives, iterations = 10, initial = -1)
iterations.times.reduce(initial) do |x, i|
x -= evaluate_polynomial(coefficients, x) / evaluate_polynomial(derivatives, x)
end
end
# A simpler implementation using reduce
def evaluate_polynomial(coefficients, x)
coefficients.zip_reverse_index.reduce(0) do |sum, (c, n)|
sum += c * x ** n
end
end
Here's a different version that keeps everything in 1 method:
def newton_raphson(coefficients, intial = -1, iterations = 10)
evaluate = -> (array, x) do
array.reduce(0) { |sum, (c, ord)| sum += c * x ** ord }
end
coefficients = coefficients
.map(&:to_f)
.zip((0...coefficients.count).to_a.reverse)
derivatives = coefficients.map { |c, ord| [c * ord, ord - 1] }[0...-1]
iterations.times.reduce(intial) do |approx, _|
approx -= evaluate[coefficients, approx] / evaluate[derivatives, approx]
end
end
It's pretty long for a Ruby method, but some of the methods in the other implementation seemed too specific to keep around in the global scope (and the Array
mixin also seemed a little heavy-handed).
Edit: as @jQweirdy mentioned in the comments, this could also be solved with a class, but I was hesitant to add a class just for one method. But there are a few things here, that might be generically useful for Polynomial
class. So here's an example:
class Polynomial
attr_reader :coefficients
def initialize(*coefficients)
@coefficients = coefficients
end
def evaluate(x)
coefficients.zip(ordinals).reduce(0) do |sum, (n, i)|
sum += n * x ** i
end
end
def derivative
@derivative ||= begin
derivatives = coefficients.zip(ordinals)[0...-1].map { |n, i| n * i }
self.class.new(*derivatives)
end
end
def order
coefficients.count - 1
end
def ordinals
(0..order).to_a.reverse
end
def newton_raphson_approximation(iterations = 1, initial = -1)
iterations.times.reduce(initial) do |x, _|
x -= evaluate(x).fdiv(derivative.evaluate(x))
end
end
end