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

Representing a long regular expression in a Polynomial class

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

Problem

I wrote a simple Polynomial class:

class Polynomial
  def initialize(coefficients)
    @coefficients = coefficients.reverse
  end

  def to_s
    return '0' if @coefficients.all?(&:zero?)
    @coefficients.map.with_index do |k, index| 
      " #{Polynomial.sign(k)} #{k.abs}x^#{index}"
    end.reverse.join.strip          # removes whitespace at the front
    .gsub(/\A\+\s/, '')             # removes + if first coefficient is positive
    .gsub(/x\^0/, '')               # removes x to the power of 0
    .gsub(/\s(\+|-)\s0x\^\d/, '')   # removes 0 coefficients
    .gsub(/\s(\+|-)\s0/, '')        # removes 0 at the end of the expression
    .gsub(/\^1/, '')                # removes power of 1
    .gsub(/1x/, 'x')
  end

  def evaluate(x)
    @coefficients.map.with_index { |k, index| k * (x**index) }.reduce(0, :+)
  end

  private

  def self.sign(integer)
    integer >= 0 ? '+' : '-'
  end
end


There are a few things that bother me in the to_s method:

  • Is it bad that I wrote the short comments explaining what each gsub does? Bad in terms of "Would you do this in production code?".



-
Since all but one of the gsubs do the same, I could replace them with a single gsub, containing a long regexp with lots of ors (|):

.gsub(/\A\+\s|x\^0|\s(\+|-)\s0x\^\d|\s(\+|-)\s0|\^1/, '')


In that case though the regexp become quite unreadable. Which of the two is better?

-
Regarding the map.with_index part:

  • I use do/end, but than the chaining of methods on the end doesn't look good.



  • If I use curly braces on multiple lines, I break the convention curly braces for single-line blocks, do/end for multi-line blocks.



  • If I use curly braces with a single-line block, the line will be more than 80 characters long. So which of the three variants would you use?

Solution

Rather than construct the polynomial string incorrectly, then fix it with regex's, it may be easier to contruct it directly. This is one way you could do that.

class Polynomial
  def initialize(coefficients)
    @coefficients = coefficients
  end

  def to_s
    coeffs = @coefficients.each_with_index.select { |c,k| c.nonzero? || k==0 }.reverse
    coeffs.each_with_object('') do |(c,k), s|
      cneg = (c.to_f  0)
      s  0
      s  1
    end
  end
end                   

coefficients = [2.1, 3, 1.0, 0, -6.1, 1, -5.3, 0.2]
p Polynomial.new(coefficients).to_s
  # => 0.2x^7 - 5.3x^6 + x^5 - 6.1x^4 + x^2 + 3x + 2.1


For this example value of coefficients, this is what's happening:

First save each coeffient's index with its value:

a = @coefficients.each_with_index
  # => [[2.1,0], [3,1], [1.0,2], [0,3], [-6.1,4], [1,5], [-5.3,6], [0.2,7]]


Next, select only pairs for which the value is non-zero or the exponent is zero:

b = a.select { |c,k| c.nonzero? || k==0 }
  # => [[2.1, 0], [3, 1], [1.0, 2], [-6.1, 4], [1, 5], [-5.3, 6], [0.2, 7]]


Notice that [0,3] has been removed. Now reverse the elements of the array:

coeffs = b.reverse
  # => [[0.2, 7], [-5.3, 6], [1, 5], [-6.1, 4], [1.0, 2], [3, 1], [2.1, 0]]


each_with_object('') creates an empty string, designated s in the block. It is then a simple matter to consider each element of each term in the polynomial to build up the string.

I'm not certain if I followed the formatting rules exactly, but if I did not, it should be easy to modify the code to conform.

(Edited to fix boo-boos spotted by @200_success.)

Code Snippets

class Polynomial
  def initialize(coefficients)
    @coefficients = coefficients
  end

  def to_s
    coeffs = @coefficients.each_with_index.select { |c,k| c.nonzero? || k==0 }.reverse
    coeffs.each_with_object('') do |(c,k), s|
      cneg = (c.to_f < 0.0)
      if [c,k] == coeffs.first
        s << '-' if cneg
      else
        s << (cneg ? ' - ' : ' + ')
      end
      s << "#{c.abs}" unless (c.abs.to_f == 1.0 && k > 0)
      s << "x" if k > 0
      s << "^#{k}" if k > 1
    end
  end
end                   

coefficients = [2.1, 3, 1.0, 0, -6.1, 1, -5.3, 0.2]
p Polynomial.new(coefficients).to_s
  # => 0.2x^7 - 5.3x^6 + x^5 - 6.1x^4 + x^2 + 3x + 2.1
a = @coefficients.each_with_index
  # => [[2.1,0], [3,1], [1.0,2], [0,3], [-6.1,4], [1,5], [-5.3,6], [0.2,7]]
b = a.select { |c,k| c.nonzero? || k==0 }
  # => [[2.1, 0], [3, 1], [1.0, 2], [-6.1, 4], [1, 5], [-5.3, 6], [0.2, 7]]
coeffs = b.reverse
  # => [[0.2, 7], [-5.3, 6], [1, 5], [-6.1, 4], [1.0, 2], [3, 1], [2.1, 0]]

Context

StackExchange Code Review Q#37207, answer score: 3

Revisions (0)

No revisions yet.