HiveBrain v1.2.0
Get Started
← Back to all entries
patternrubyMinor

Newton's Method Polynomial solver in Ruby

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
methodpolynomialnewtonrubysolver

Problem

I am learning Ruby programming from "Learn Ruby the Hard way" and I am doing the "Ruby koans".

I have heard a little bit about "Idiomatic" Ruby but I don't know much about it. How can I make it more idiomatic?

# Program to solve polynomials

def solve(coeff)
    coefficients = []
    coeff.each{ |a|
        coefficients.push a.to_f
    }
    diffrentiate = []
    n = 0
    coefficients.reverse_each { |a| 
        diffrentiate.unshift n*a
        n += 1
    }
    diffrentiate.pop
    return newton(coefficients,diffrentiate,-1)
end

def newton(coefficients,diffrentiate,initialvalue)
    x = initialvalue
    10.times { |n|  
        x -= evaluate_polynomial(coefficients,x)/evaluate_polynomial(diffrentiate,x)
    }
    return x
end

def evaluate_polynomial(coefficients,x)
    n = 0
    value = 0
    coefficients.reverse_each { |a| 
        value += a*x**n
        n+=1
    }
    return value
end

puts solve ([1,0,0,0,-1,1])

Solution

-
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

Code Snippets

# 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
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
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

Context

StackExchange Code Review Q#112127, answer score: 5

Revisions (0)

No revisions yet.