Coding Challenge #2 – Polynomial

Whew! This solution to @reggaemuffin's Coding Challenge #2 – Polynomial took 2 days to complete. One for scripting and one for troubleshooting. There are limitations I'd be happy to improve upon but it's a distributable product now as far as I'm concerned. The language is Matlab, just like my last solution to his first challenge. While I am not limited to using Matlab, I go with what has a useful function or two that come to mind.

The upside to using Matlab here was that matlab has functions for dealing with polynomials already. The downside is that Matlab switches up the classes of their output functions in cells, which can get tricky. A lot of the troubleshooting was trying to check the class of a function's output to determine the syntax of the next step.

The Challenge

In my own words, the challenge was to make a polynomial object class with a constructor function in it that would take in a string of math terms representing a polynomial and spit out an object of that polynomial.

In simple terms, we needed to write a code that would take the math typed into it and return an ID card of info about the math that was typed in. Remember ax^2 + bx + c from high school Algebra? Any math that was typed in would look like that, but with number values instead of a, b, and c.

The ID card would come with useful details, like the number of terms, what each term is, powers of each term, etc. All stuff that could be referenced later by another program. In everyday video games, a good example of such an ID card is when an enemy is spawned. There's a constructor program like the one I made which is called, possibly asking for a type of enemy from it, and then it spits out an ID card representing the enemy it created. After that, the game just refers to that ID card whenever it needs to check or change any detail about the enemy. Like, current HP, armor class, or rank. It's like one variable that is actually a set of variables. In code this is known as an object.

The Constructor

It was a long piece of work, so there's only so much that I can explain in detail. However, I did leave clear comments of each step to help make it readable. In Matlab, class creation is marked by classdef followed by the new class being created. A class is a type of data, such as a character, integer, string of characters, or boolean (true/false).

The properties section lists out some data that every object of that class should have on it's ID card (object). I decided that the Polynomial objects should have a string (str) of their simplified form, a set of each term as it's own string, a vector representing the polynomial (as per Matlab's preferred means of representing and crunching polynomials), a saved copy of the base variable in the polynomial, a vector of the coefficients and a vector of the powers of each term which both map with the term string set, as well as a vector of the integral and a vector of the derivative functions which Matlab was already prepped with functions to make that happen.

To identify the relevant information from the input string, I used regular expressions (regexp). If you never plan on programming but expect to work with a lot of paperwork to sift through for any reason, you gotta check out the magic that is regex. Nothing beats a program that you can make think like you do. I split each term to be it's own string, identified the power and coefficient of each term, formed the polynomial vector for Matlab with them, then reconstructed the string as a simplified version of the original leading with the traditional highest-power-term-first format. I didn't realize until late in the game that the OP did highest-power-term-last. It didn't really matter since I was checking to simplify for combining like terms anyway.

Matlab has a fun feature for this sort of thing. When you create a vector x = [7] and assign a value to it that's in a higher than the current length of the vector x(5) = 52, you get a vector with all of the new unassigned positions defaulting to 0: x == [7 0 0 0 52]. Therefore, for each higher power term evaluated, I just had to notice that it was such, and then assign the new value. The code could have been simpler, except that Matlab kicks an error if I try to add to a value that isn't there, otherwise I would have just kept adding the coefficients without a vector check.

Followed by my constructor function were 6 functions which if fed a Polynomial object can spit out another Polynomial object based on it. These functions allow you to form them from adding, subtracting, multiplying, or dividing terms from the given object with the last two spitting out a polynomial object of the given object's derivative and integral functions respectively.

Let me know if you have any questions, suggestions, criticisms, you name it, all feedback is welcome.

The Class Code With Constructor

classdef Polynomial
   properties (SetAccess = private, GetAccess = public)
      % A simplified string of the given polynomial.
      str
      % A vector of the leading coefficients of each term of the ...
      ... simplified polynomial. 0 for any missing terms.
      vector
      % The alphabetical character used for the variable in the polynomial.
      var
      % A cell array of each term represented by individual strings.
      terms = {}
      % A vector of the powers for each term coinciding with terms property.
      termPows = []
      % A vector of the coefs for each term coinciding with terms property.
      termCoefs = []
      % A vector of the leading coefficients of each term of the ...
      ... derivative of the polynomial. 0 for any missing terms.
      deriv
      % A vector of the leading coefficients of each term of the ...
      ... integral of the polynomial. 0 for any missing terms.
      integ
   end
   
   methods
      function obj = Polynomial(Str) % String accepting construct.
         % Remove all whitespace from the polynomial string.
         Str = regexprep(Str, '\s+', ''); 
         
         % Replace all subtraction operations with '+-'.
         Str = regexprep(Str, '(\d|[A-z])-(\d|[A-z])','$1+-$2'); 
         
         % Split polynomial string into strings of each term.
         Str = regexprep(Str, '^\+', ''); % Remove any leading '+'.
         Terms = regexp(Str, '\+', 'split');
         
         % Create a vector for the coefficients of each term.
         for i = 1:length(Terms) % Loop through each term
             % Get power and coefficient compinents of the term
             powStr = regexprep(Terms{i}, '.+(\^\d+)$', '$1');
             coefStr = regexprep(Terms{i}, '^(-?((\d|\.)+)?)(.+)?', '$1');
             
             % Check if the coefficient is a given number.
             if isempty(coefStr)
                 coef = 1;
             elseif (length(coefStr) == 1) && (coefStr(1) == '-')
                 coef = -1;
             else    
                 coef = str2num(coefStr); % Get coefficient value.
             end
             
             % Check if the term has a given power or if it's power is 1 or zero
             if powStr(1) == '^' 
                 % Get the power of the term following '^'.
                 pow = str2num(powStr(2:length(powStr)));
             elseif  isstrprop(powStr(length(powStr)),'digit')
                 pow = 0;
             else
                 % Set power to 1 if term without given power doesn't end in a digit.
                 pow = 1;
             end
             % Check for like terms before assigning to polynomial vector
             if pow +1 > length(obj.vector)
                 obj.vector(pow + 1) = coef; % Assign coef to power position.
             else
                 obj.vector(pow +1) = obj.vector(pow + 1) + coef; % Add like terms.
             end
         end
         % Get the variable of the polynomial
         obj.var = regexp(Str, '[A-z]', 'match', 'once');
         
         % Recreate simplified term strings and create obj.terms
         tCount = 1;
         for i = length(obj.vector): -1 : 1
             % Check for nonzero coefficient terms.
             if obj.vector(i) ~= 0
                 % Assign term string to obj.terms cell array.
                 obj.terms{tCount} = strcat(num2str(obj.vector(i)), obj.var(1), '^', num2str(i - 1));
                 % Assign term power to obj.termPows vector.
                 obj.termPows(tCount) = i - 1;
                 % Assign term coefficient to obj.termCoefs vector.
                 obj.termCoefs(tCount) = obj.vector(i);
                 tCount = tCount + 1; % Mark next term position.
             end
         end
         
         % Recreate simplified polynomial
         if ~isempty(obj.terms)
             obj.str = obj.terms{1};
             for i = 2 : length(obj.terms)
                 obj.str = strcat(obj.str, '+', obj.terms{i});
             end
         end
             
         % Horizontally flip obj.vector for Matlab poly functions.
         obj.vector = fliplr(obj.vector);
         
         % Calculate deriv and integ vectors.
         obj.deriv = polyder(obj.vector);
         obj.integ = polyint(obj.vector);
         
      end
      
      % Create function for adding terms
      function r = addTerms(obj, varargin) % Call function with term positions in obj.terms
          % Combine terms into a single string
          s = obj.terms{varargin{1}};
          for i = 2 : length(varargin)
              s = strcat(s, '+', obj.terms{varargin{i}});
          end
          % Return new object of Polynomial class
          r = Polynomial(s);
      end
      
      % Create function for subtracting terms
      function r = subtractTerms(obj,t1,t2) % Call function with term positions in obj.terms
          % Combine terms into a single string
          s = strcat(obj.terms{t1}, num2str(-1 * obj.termCoefs(t2)), obj.var, '^', num2str(obj.termPows(t2)));
          % Retern new object of Polynomial class
          r = Polynomial(s);
      end
      
      % Create function for multiplying terms
      function r = multiplyTerms(obj, varargin)
          % Add powers and multiply coefficients.
          pow = 0;
          coef = 1;
          for i = 1 : length(varargin)
              pow = pow + obj.termPows(varargin{i});
              coef = coef * obj.termCoefs(varargin{i});
          end
          % Create a string of the resulting term
          s = strcat(num2str(coef), obj.var, '^', num2str(pow));
          % Retern new object of Polynomial class
          r = Polynomial(s);
      end
      
      % Create function for dividing terms
      function r = divideTerms(obj, t1, t2)
          % Check that the result won't give a negative power
          if t1 > t2
              error('Error. \n The second term cannot be a higher power than the first.');
          end
          % Subtract powers and divide coefficients.
          pow = obj.termPows(t1) - obj.termPows(t2);
          coef = obj.termCoefs(t1) / obj.termCoefs(t2);
          % Create a string of the resulting term
          s = strcat(num2str(coef), obj.var, '^', num2str(pow));
          % Retern new object of Polynomial class
          r = Polynomial(s);
      end
      
      % Create a function for the derivative object.
      function r = derPoly(obj)
          s = '';
          % Build a string of the polynomial in obj.deriv.
          for i = 1 : length(obj.deriv) - 1
              s = strcat(s, num2str(obj.deriv(i)), obj.var, '^', num2str(length(obj.deriv) - i), '+');
          end
          s = strcat(s, num2str(obj.deriv(length(obj.deriv))));
          % Return the resulting polynomial object
          r = Polynomial(s);
      end
      
      % Create a function for the integral object.
      function r = intPoly(obj)
          s = '';
          % Build a string of the polynomial in obj.integ.
          for i = 1 : length(obj.integ) - 1
              s = strcat(s, num2str(obj.integ(i)), obj.var, '^', num2str(length(obj.integ) - i), '+');
          end
          s = strcat(s, num2str(obj.integ(length(obj.integ))));
          
          % Return the resulting polynomial object
          r = Polynomial(s);
      end
   end
end

The Test Script

pol = Polynomial('+1x^0 -5x^1 +3x^2');
disp('pol:');
disp(pol);
addPol = addTerms(pol, 1, 3);
disp('addPol:');
disp(addPol);
subPol = subtractTerms(pol, 2, 3);
disp('subPol:');
disp(subPol);
multPol = multiplyTerms(pol, 1, 2);
disp('multPol:');
disp(multPol);
divPol = divideTerms(pol, 1, 2);
disp('divPol:');
disp(divPol);
derPol = derPoly(pol);
disp('derPol:');
disp(derPol);
intPol = intPoly(pol);
disp('intPol:');
disp(intPol);

The Test Console Output

pol:
  Polynomial with properties:

          str: '3x^2+-5x^1+1x^0'
       vector: [3 -5 1]
          var: 'x'
        terms: {'3x^2'  '-5x^1'  '1x^0'}
     termPows: [2 1 0]
    termCoefs: [3 -5 1]
        deriv: [6 -5]
        integ: [1 -2.5000 1 0]

addPol:
  Polynomial with properties:

          str: '3x^2+1x^0'
       vector: [3 0 1]
          var: 'x'
        terms: {'3x^2'  '1x^0'}
     termPows: [2 0]
    termCoefs: [3 1]
        deriv: [6 0]
        integ: [1 0 1 0]

subPol:
  Polynomial with properties:

          str: '-5x^1+-1x^0'
       vector: [-5 -1]
          var: 'x'
        terms: {'-5x^1'  '-1x^0'}
     termPows: [1 0]
    termCoefs: [-5 -1]
        deriv: -5
        integ: [-2.5000 -1 0]

multPol:
  Polynomial with properties:

          str: '-15x^3'
       vector: [-15 0 0 0]
          var: 'x'
        terms: {'-15x^3'}
     termPows: 3
    termCoefs: -15
        deriv: [-45 0 0]
        integ: [-3.7500 0 0 0 0]

divPol:
  Polynomial with properties:

          str: '-0.6x^1'
       vector: [-0.6000 0]
          var: 'x'
        terms: {'-0.6x^1'}
     termPows: 1
    termCoefs: -0.6000
        deriv: -0.6000
        integ: [-0.3000 0 0]

derPol:
  Polynomial with properties:

          str: '6x^1+-5x^0'
       vector: [6 -5]
          var: 'x'
        terms: {'6x^1'  '-5x^0'}
     termPows: [1 0]
    termCoefs: [6 -5]
        deriv: 6
        integ: [3 -5 0]

intPol:
  Polynomial with properties:

          str: '1x^3+-2.5x^2+1x^1'
       vector: [1 -2.5000 1 0]
          var: 'x'
        terms: {'1x^3'  '-2.5x^2'  '1x^1'}
     termPows: [3 2 1]
    termCoefs: [1 -2.5000 1]
        deriv: [3 -5 1]
        integ: [0.2500 -0.8333 0.5000 0 0]
Sort:  

Fantastic initiative, @philosophist

that's all nice and dandy. What about Putin though? xD

Hello, MAP14 has started! please go to "Six of the Best" MAP14 Minnow Contest [Vote Now - Win Upvotes]. Please look at the suggestions for all participants, especially creating a comment showcasing your best recent work. Good luck!

And don't forget, you can get further inspiration and assistance at the MAP Members Only Discord chatroom.