11

I am trying to expand an algebraic term.

(x+1)(x+1)/x => x + 2 + x^-1
(x+1)^3 => x^3 + 3x^2 + 3x + 1
(x^2*x)(x^2) => x^5

This is my attempt at it. I have tried a lot of ways trying to fix the problems below.

Problems:

  • Like terms should be added together

  • (x+1)(x+1)(x+1) should be valid.

  • (x+1)^2 should be equal to (x+1)(x+1)

  • x(x+1) should be valid

  • 1x^n should just be x^n

  • There should be no 0x^n terms.

  • nx^0 terms should just be n

Code Snippet:

function split(input) {

    return ((((input.split(")(")).toString()).replace(/\)/g, "")).replace(/\(/g, "")).split(','); }

function strVali(str) {
    str = str.replace(/\s+/g, "");

    var parts = str.match(/[+\-]?[^+\-]+/g);

    // accumulate the results
    return parts.reduce(function(res, part) {
        var coef = parseFloat(part) || +(part[0] + "1") || 1;
        var x = part.indexOf('x');
        var power = x === -1 ?
            0:
            part[x + 1] === "^" ?
                +part.slice(x + 2) :
                1;
        res[power] = (res[power] || 0) + coef;
        return res;
    }, {});
}

function getCoeff(coeff) {

    var powers = Object.keys(strVali(coeff));

    var max = Math.max.apply(null, powers);

    var result = [];
    for(var i = max; i >= 0; i--)
        result.push(strVali(coeff)[i] || 0);

    return result; }

function evaluate(expression) {
    var term1 = getCoeff(expression[0]);
    var term2 = getCoeff(expression[1]);
    var expand = "";
    for ( var j = 0; j < term1.length; j++ ) {
        for ( var i = 0; i < term2.length; i++ ) {
            expand += Number(term1[j] * term2[i]) + 'x^ ' + (Number(term1.length) - 1 - j + Number(term2.length) - 1 - i) + ' + ';
        }}
        var final = "";
    for ( var Z = 0; Z < getCoeff(expand).length; Z++) {
        final += ' ' + getCoeff(expand)[Z] + 'x^ {' + (getCoeff(expand).length - Z - 1) + '} +';
    }
    final = "$$" + ((((((final.replace(/\+[^\d]0x\^ \{[\d]+\}/g,'')).replace(/x\^ \{0}/g,'')).replace(/x\^ \{1}/g,'x')).replace(/[^\d]1x\^ /g,'+ x^')).replace(/\+ -/g,' - ')).slice(0, -1)).substring(1,(final.length)) + "$$";
    document.getElementById('result').innerHTML = final;
    MathJax.Hub.Queue(["Typeset", MathJax.Hub, document.getElementById('result')]);
}

function caller() {
    var input = document.getElementById('input').value;
    evaluate(split(input)); }
div.wrapper {
    width: 100%;
    height:100%;
    border:0px solid black;
}

input[type="text"] {
    display: block;
    margin : 0 auto;
    padding: 10px;
    font-size:20px;
}

button{
    margin:auto;
    display:block;
    background-color: white;
    color: black;
    border: 2px solid #555555;
    padding-left: 20px;
    padding-right: 20px;
    font-size: 20px;
    margin-top:10px;
}

button:hover {
    box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24),0 17px 50px 0 rgba(0,0,0,0.19);
}
<script type="text/javascript" async
        src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-MML-AM_CHTML">
</script>
<div class='wrapper'><input id="input" title="Enter Expression" type="text" value="(x^2+x+1)(x^2+x+1)"></div>
<div> <button onclick="caller()">Click</button></div>
<div id="result">$$x^4 + 2x^3 + 3x^2 + 2x + 1$$</div>

Reference:

  1. How to calculate coefficients of polynomial expansion in javascript

  2. Getting coefficients of algebraic term

  3. How to get a term before a character?

Community
  • 1
  • 1
  • What about `(x + (x + 2)) (x + 5)`? – ibrahim mahrir Apr 22 '17 at 13:28
  • @ibrahimmahrir `(2x+2)(x+5)` => `2x^2+12x^1+10x^0` –  Apr 22 '17 at 13:33
  • 1
    No. I mean could the user type something like that or not? – ibrahim mahrir Apr 22 '17 at 13:33
  • Yes @ibrahimmahrir `(x-x)(x), xx(x)` is invalid –  Apr 22 '17 at 13:34
  • 2
    Your list of problems looks more like a list of requirements. Consequently, your question seems more like an assignment or an invitation to do cooperative development. Please _describe the difficulties_ you are having solving your problems. – Ruud Helderman Apr 29 '17 at 09:00
  • The original question did not include the `/` and `-` operators. Adding these requirements has substantially changed the question since I posted my answer. – James Apr 29 '17 at 10:40
  • thanks @James for your original answer, but i think engineer has set me on the right path. –  Apr 29 '17 at 10:46

3 Answers3

2

Edit: I have re-written my original answer from when the question did not include the - and / operators). My new answer supports - and /.

Since you have not defined a precise grammar, I have made some assumptions. I support the +, -, /, ^ operators and the implicit multiply. These can operate on numbers, x and (...) expressions, except that right hand side of the ^ operator must be a number. I allow spaces between tokens.

Limitations: The -is a binary not unary - operator, So x-2 if OK, -2 on its own is a syntax error. The / is limited. You may divide by a value with a single coefficient such as 2, 'x', 2x^2, but not by cannot have 1/(x^2+1), which would evaluate to an infinite series.

It first takes the string and wraps it in a 'tokenizer' which lets you look it one token at a time, where a token is a number, x, ( ) or operator.

It then calls evaluateSum() which evaluates things separated by + or -, where each thing is a product evaluated by evaluateProduct(). This in turn uses evaluatePower() to evalue the ^. Finally evaluateTerm() looks for x, numbers and bracketed sub-expressions which are evaluates recursively with evaluateSum(). This hierarchy creates the correct operator precedence and evaluation order.

Each layer of the evaluation returns a 'coefficients' object which s array-like, but may contain megative indexes. For example [1,0,1] means 1+x^2.

It can cope with any number of nested brackets and a lot of other case such as xx(x) is x^3, 2^3 is 8. I have added a few throws for syntax errors, for example 2^x is illegal.

function makeTokenizer(source) {
    var c, i, tokenizer;
    i = 0; // The index of the current character.
    c = source.charAt(i); // The current character.
    function consumeChar() { // Move to next c, return previous c.
        var prevC = c;
        i += 1;
        c = source.charAt(i);
        return prevC;
    }
    tokenizer = {
        next : function () {
            var str;
            while (c === ' ') { // Skip spaces
                consumeChar();
            }
            if (!c) {
                tokenizer.token = undefined; // End of source
            } else if (c >= '0' && c <= '9') { // number
                str = consumeChar(); // First digit
                while (c >= '0' && c <= '9') { // Look for more digits.
                    str += consumeChar();
                }
                tokenizer.token = Number(str);
            } else if (c === "x") {
                tokenizer.token = consumeChar();
            } else { // single-character operator
                tokenizer.token = consumeChar();
            }
        }
    };
    tokenizer.next(); // Load first token.
    return tokenizer;
}
function makeCoefficients() { // Make like an array but can have -ve indexes
    var min = 0, max = 0, c = {};
    return {
        get: function (i) {
            return c[i] || 0;
        },
        set: function (i, value) {
            c[i] = value;
            min = Math.min(i, min);
            max = Math.max(i + 1, max);
            return this; // for chaining
        },
        forEach: function (callback) {
            var i;
            for (i = min; i < max; i += 1) {
                if (this.get(i)) {
                    callback(this.get(i), i);
                }
            }
        },
        toString: function () {
            var result = "", first = true;
            this.forEach(function (val, power) {
                result += (val < 0 || first) ? "" : "+";
                first = false;
                result += (val === 1 && power !== 0) ? "" : val;
                if (power) {
                    result += "x";
                    if (power !== 1) {
                        result += "^" + power;
                    }
                }
            });
            return result;
        },
        toPowerOf: function (power) {
            if (power === 0) {
                return makeCoefficients().set(0, 1); // Anything ^0 = 1
            }
            if (power === 1) {
                return this;
            }
            if (power < 0) {
                throw "cannot raise to negative powers";
            }
            return this.multiply(this.toPowerOf(power - 1));
        },
        multiply: function (coefficients) {
            var result = makeCoefficients();
            this.forEach(function (a, i) {
                coefficients.forEach(function (b, j) {
                    result.set(i + j, result.get(i + j) + a * b);
                });
            });
            return result;
        },
        divide: function (coefficients) {
            // Division is hard, for example we cannot do infinite series like:
            // 1/(1 + x^2) = sum_(n=0 to infinity) 1/2 x^n ((-i)^n + i^n)
            // So we do a few easy cases only.
            var that = this, result = makeCoefficients(), done;
            coefficients.forEach(function (value, pow) {
                that.forEach(function (value2, pow2) {
                    result.set(pow2 - pow, value2 / value);
                });
                if (done) {
                    throw "cannot divide by " + coefficients.toString();
                }
                done = true;
            });
            return result;
        },
        add: function (coefficients, op) {
            var result = makeCoefficients();
            this.forEach(function (value, i) {
                result.set(value, i);
            });
            op = (op === "-" ? -1 : 1);
            coefficients.forEach(function (value, i) {
                result.set(i, result.get(i) + value * op);
            });
            return result;
        }
    };
}
var evaluateSum; // Called recursively
function evaluateTerm(tokenizer) {
    var result;
    if (tokenizer.token === "(") {
        tokenizer.next();
        result = evaluateSum(tokenizer);
        if (tokenizer.token !== ")") {
            throw ") missing";
        }
        tokenizer.next();
    } else if (typeof tokenizer.token === "number") {
        result = makeCoefficients().set(0, tokenizer.token);
        tokenizer.next();
    } else if (tokenizer.token === "x") {
        tokenizer.next();
        result = makeCoefficients().set(1, 1); // x^1
    } else {
        return false; // Not a 'term'
    }
    return result;
}
function evaluatePower(tokenizer) {
    var result;
    result = evaluateTerm(tokenizer);
    if (tokenizer.token === "^") {
        tokenizer.next();
        if (typeof tokenizer.token !== "number") {
            throw "number expected after ^";
        }
        result = result.toPowerOf(tokenizer.token);
        tokenizer.next();
    }
    return result;
}
function evaluateProduct(tokenizer) {
    var result, term, divOp;
    result = evaluatePower(tokenizer);
    if (!result) {
        throw "Term not found";
    }
    while (true) {
        divOp = (tokenizer.token === "/");
        if (divOp) {
            tokenizer.next();
            term = evaluatePower(tokenizer);
            result = result.divide(term);
        } else {
            term = evaluatePower(tokenizer);
            if (!term) {
                break;
            }
            result = result.multiply(term);
        }
    }
    return result;
}
function evaluateSum(tokenizer) {
    var result, op;
    result = evaluateProduct(tokenizer);
    while (tokenizer.token === "+" || tokenizer.token === "-") {
        op = tokenizer.token;
        tokenizer.next();
        result = result.add(evaluateProduct(tokenizer), op);
    }
    return result;
}

function evaluate(source) {
    var tokenizer = makeTokenizer(source),
        coefficients = evaluateSum(tokenizer);
    if (tokenizer.token) {
        throw "Unexpected token " + tokenizer.token;
    }
    console.log(source + " => " + coefficients.toString());
}

// Examples:
evaluate("(x+1)(x+1)"); // => 1+2x+x^2
evaluate("(x+1)^2"); // => 1+2x+x^2
evaluate("(x+1)(x+1)(x+1)"); // => 1+3x+3x^2+x^3
evaluate("(x+1)^3"); // => 1+3x+3x^2+x^3
evaluate("(x)(x+1)"); // => x+x^2
evaluate("(x+1)(x)"); // => x+x^2
evaluate("3x^0"); // => 3
evaluate("2^3"); // => 8
evaluate("(x+2x(x+2(x+1)x))"); // => x+6x^2+4x^3
evaluate("(x+1)(x-1)"); // => -1+x^2
evaluate("(x+1)(x+1)/x"); // x^-1+2+x
//evaluate("(x+1)/(x^2 + 1)"); //throws  cannot divide by 1+2x
James
  • 5,635
  • 2
  • 33
  • 44
  • Could you add the '/' function, I tried modifying your code but to no avail. –  Apr 27 '17 at 01:15
  • @kaien dividing by a single `x` or a constant would be relatively easy - dividing by a polynomial in `x` is *way* harder. – Alnitak Apr 28 '17 at 08:48
  • @Alnitak Yes I, for example, according to [Wolfram Alpha](https://www.wolframalpha.com/input/?i=1%2F(x%5E2%2B1)) `1/(1 + x^2) = sum_(n=0)^∞ 1/2 x^n ((-i)^n + i^n)` so unless we want to support infinite series coefficients we can't go there. – James Apr 29 '17 at 10:54
  • @James that's a particularly bad example because `(1 + x^2)` doesn't factorise with real roots. Taking instead `foo / (1 - x^2)`, for example, the problem becomes one not of trivial _expansion_ of the polynomials, but of _factorising_ them. The factors of that are `(1 + x)` and `(1 - x)`, so you'd have to look to see whether either of those factors are also factors of the numerator `foo`. – Alnitak Apr 30 '17 at 06:37
0

It can handle +, -, / and implicit multiplication. It expands the parenthesis accordingly and adds them to the original expression while removing the parenthesised version. It collects like terms accordingly. Limitations: It cannot divide by a polynomial and it does not support the * operator.

function strVali(str) {
    str = str.replace(/\s+/g, "");
    var parts = str.match(/[+\-]?[^+\-]+/g);
    return parts.reduce(function(res, part) {
        var coef = parseFloat(part) || +(part[0] + "1") || 1;
        var x = part.indexOf('x');
        var power = x === -1 ?
            0:
            part[x + 1] === "^" ?
                +part.slice(x + 2) :
                1;
        res[power] = (res[power] || 0) + coef;
        return res;
    }, {});
}

function getCoeff(coeff) {
    var powers = Object.keys(strVali(coeff));
    var max = Math.max.apply(null, powers);
    var result = [];
    for(var i = max; i >= 0; i--)
        result.push(strVali(coeff)[i] || 0);
    return result; }

function format(str) {
    str = ' ' + str;
    str = str.replace(/-/g,'+-').replace(/x(?!\^)/g,'x^1').replace(/([+\/*)(])(\d+)([+\/*)(])/g,'$1$2x^0$3').replace(/([^\d])(x\^-?\d+)/g,'$11$2').replace(/(-?\d+x\^\d+)(?=\()/g,'($1)').replace(/(\))(-?\d+x\^\d+)/g,'$1($2)').replace(/([^\)\/])(\()([^\*\/\(\)]+?)(\))(?![(^\/])/g,'$1$3');
    str = str.replace(/(\([^\(\)]+?\))\/(\d+x\^-?\d+)/g,'$1/($2)').replace(/(\d+x\^-?\d+)\/(\d+x\^-?\d+)/g,'($1)/($2)').replace(/(\d+x\^-?\d+)\/(\(\d+x\^-?\d+\))/g,'($1)/$2');
    return str;
}

function expBrackets(str) {
    var repeats = str.match(/\([^\(\)]+?\)\^\d+/g);
    if ( repeats === null ) { return str; } else { var totalRepeat = '';
    for ( var t = 0; t < repeats.length; t++ ) { var repeat = repeats[t].match(/\d+$/); for ( var u = 0; u < Number(repeat); u++ ) { totalRepeat += repeats[t].replace(/\^\d+$/,''); }
        str = str.replace(/\([^\(\)]+?\)\^\d+/, totalRepeat); totalRepeat = ''; }
    return str; }
}

function multiply(str) {
    var pairs = str.match(/\([^\(\)]+?\)\([^\(\)]+?\)/g);
    if ( pairs !== null ) { while ( pairs !== null ) { var output = '';
        for (var i = 0; i < pairs.length; i++) { var pair = pairs[i].slice(1).slice(0, -1).split(')('); var firstCoeff = getCoeff(pair[0]); var secondCoeff = getCoeff(pair[1]);
            for (var j = 0; j < firstCoeff.length; j++) {
                for (var k = 0; k < secondCoeff.length; k++) { output += firstCoeff[j] * secondCoeff[k] + 'x^' + Number(firstCoeff.length - 1 - j + secondCoeff.length - 1 - k) + '+'; } }
            var regexp = new RegExp(pairs[i].replace(/\(/g,'\\(').replace(/\+/g,'\\+').replace(/\)/g,'\\)').replace(/\^/g,'\\^').replace(/\-/g,'\\-'));
            str = str.replace(regexp, '(' + (output.slice(0, -1).replace(/[^\d]0x\^\d+/g,'')) + ')');
            output = ''; }
        pairs = str.match(/\([^\(\)]+?\)\([^\(\)]+?\)/g); } }
        else { }
    str = str.replace(/\+/g,' + ');
    return str;
}

function divide(str) {
    if ( str.match(/\/(\(-?\d+x\^-?\d+.+?\))/g) === null && str.match(/\//g) !== null ) {
        while ( pairs !== null ) {
        var pairs = str.match(/\([^\(\)]+?\)\/\([^\(\)]+?\)/g);
        var output = '';
        for (var i = 0; i < pairs.length; i++) {
            var pair = pairs[i].slice(1).slice(0, -1).split(')/(');
            var firstCoeff = getCoeff(pair[0]);
            var secondCoeff = getCoeff(pair[1]);
            for (var j = 0; j < firstCoeff.length; j++) {
                for (var k = 0; k < secondCoeff.length; k++) {
                    output += firstCoeff[j] / secondCoeff[k] + 'x^' + Number(firstCoeff.length - 1 - j - secondCoeff.length + 1 + k) + '+';
                    output = output.replace(/([+-])Infinityx\^\-?\d+/g,'').replace(/([+-])NaNx\^\-?\d+/g,'');
                } }
            var regexp = new RegExp(pairs[i].replace(/\(/g,'\\(').replace(/\+/g,'\\+').replace(/\)/g,'\\)').replace(/\^/g,'\\^').replace(/\-/g,'\\-'));
            str = str.replace(regexp, '(' + (output.slice(0, -1).replace(/[^\d]0x\^-?\d+/g,'')) + ')');

            output = ''; }
        pairs = str.match(/\([^\(\)]+?\)\/\([^\(\)]+?\)/g); } }
    else { }
    return str;
}

function evaluate(str) {
    var result = format(divide(format(multiply(expBrackets(format((str)))))));
    var resultCollect = '';
    result = result.replace(/\s+/g, "").replace(/[^\d]0x\^-?\d+/g,'').replace(/\+/g,' + ');
    if ( result === '') {
        document.getElementById('result').innerHTML = '$$' + str + '$$'  + '$$ = 0 $$';
        MathJax.Hub.Queue(["Typeset", MathJax.Hub, document.getElementById('result')]);
    } else if ( result.match(/-?\d+x\^-\d+/g) === null && str.match(/\/(\(-?\d+x\^-?\d+.+?\))/g) === null) {
        for ( var i = 0; i < getCoeff(result).length; i++ ) {
        resultCollect += getCoeff(result)[i] + 'x^' + Number(getCoeff(result).length - 1 - i) + '+' ; }
        if ( resultCollect !== '')
        resultCollect = '$$ = ' + resultCollect.slice(0,-1).replace(/[^\d]0x\^-?\d+/g,'').replace(/\+/g,' + ').replace(/x\^0/g,'').replace(/x\^1(?!\d+)/g,'x').replace(/\^(-?\d+)/g,'\^\{$1\}').replace(/\+ -/g,' - ') + '$$';
        else
        resultCollect = 'Error: Trying to divide by a polynomial ';
        document.getElementById('result').innerHTML = '$$' + str.replace(/\^(-?\d+)/g,'\^\{$1\}') + '$$' + resultCollect;
        MathJax.Hub.Queue(["Typeset", MathJax.Hub, document.getElementById('result')]);
    } else {
        resultCollect = '$$ = ' + result.replace(/\^(-?\d+)/g,'\^\{$1\}') + '$$';
        document.getElementById('result').innerHTML = '$$' + str.replace(/\^(-?\d+)/g,'\^\{$1\}').replace(/\+ -/g,' - ') + '$$' + resultCollect;
        MathJax.Hub.Queue(["Typeset", MathJax.Hub, document.getElementById('result')]);
    }
}

function caller() {
    var input = document.getElementById('input').value;
    evaluate(input);
}
div.wrapper {
    width: 100%;
    height:100%;
    border:0 solid black;
}

input[type="text"] {
    display: block;
    margin : 0 auto;
    padding: 10px;
    font-size:20px;
}

button{
    margin:auto;
    display:block;
    background-color: white;
    color: black;
    border: 2px solid #555555;
    padding-left: 20px;
    padding-right: 20px;
    font-size: 20px;
    margin-top:10px;
}

button:hover {
    box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24),0 17px 50px 0 rgba(0,0,0,0.19);
}
<script type="text/javascript" async
        src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-MML-AM_CHTML">
</script>
<input id="input" type="text" title="Enter Expression: ">
<button onclick="caller()">Click</button>
<div id="result"></div>
<div id="errors"></div>
  • It incorrectly evaluates `1/(x^2+1) = 1x^-2 + 1`. Suppose we let x=3. `1/(x^2+1)=1/(9+1)=0.1`, but `1x^-2 + 1=1/9+1=1.111` – James Apr 29 '17 at 11:10
  • @James i pointed out at first that division by a polynomial would lead to errors as `a/(n+b) != a/n + a/b` that is why I created a simple polynomial factor script to help him with division by polynomials. –  Apr 29 '17 at 11:13
  • 1
    But it gives wrong answers! It it cannot, for whatever reason, give the correct answer maybe it should throw an error. – James Apr 29 '17 at 13:13
  • @engineeriscool your "simple polynomial factor script" is incorrect. – Alnitak Apr 30 '17 at 06:43
  • @Alnitak i have updated the bit about dividing by polynomials –  Apr 30 '17 at 09:35
0

My approach utilizes two helper classes:

-Term: This stores a coefficient and an object, the keys of which are variables and the values of which are the exponents. It has methods for determining whether terms are the "same" (i.e. whether they have the same variables with the same exponents, thus allowing them to be added), adding terms, and multiplying terms. -Polynomial: This stores an array of terms. It has methods for adding two polynomials, multiplying two polynomials, and simplifying polynomials (eliminating terms with 0 coefficients and combining like terms).

It has two substantial helper functions: -findOuterParens - Given a string, this function returns the indices of the first outermost pair of left and right parentheses. -parseExpressionStr - This function uses a regular expression to split a string into three types of substrings: 1) a number, 2) a symbol (i.e. a variable, like 'x' or 'y'), or 3) an operator (*, -, +, ^, /). It creates Polynomial objects for the first two types and leaves the operators as strings.

The expand function runs the show. It takes in a string and returns a Polynomial object representing the expanded form of the polynomial in the string. It does this as follows: 1) It deals with parentheses by recursion. It uses findOuterParens to find all outermost parenthsized substrings. So for "3x*(x+4)+(2(y+1))*t", for instance, it would find "x+4" and "2(y+1)" as parenthsized substrings. It then passes these to itself for further parsing. For "2(y+1)", it would identify "y+1" as a parenthesized substring, and pass this to itself, for three levels of recursion for this example. 2) It deals with other parts of the string using parseExpressionStr. After steps 1 and 2, it has an array containing Polynomial objects and operators (stored as strings). It then proceeds to simplifying and expanding. 3) It converts subtraction subproblems to addition subproblems by replacing all -'s with +'s and multiplying all polynomials following -'s by -1. 4) It converts division subproblems to multiplication subproblems by replacing /'s with *'s and inverting the Polynomial following the /. If the polynomial following the / has more than one term, it throws an error. 5) It converts power subproblems to multiplication subproblems by replacing ^'s with a series of multiplications. 6) It adds in implicit *'s. I.e. if two Polynomial elements are right next to each other in the array, then it's inferred that they are being multiplied, so, e.g. '2*x' == '2x'. 7) Now the array has polynomial objects alternating with either '+' or '*'. It first performs all polynomial multiplications and then performs all additions. 8) It performs a final simplification step and returns the resulting expanded polynomial.

<html>
<head></head>
<body></body>
<script>

function findOuterParens(str) {
 var leftIndex = str.indexOf('('), leftNum = 1, rightNum = 0;

 if (leftIndex === -1)
  return

 for (var i=leftIndex+1; i<str.length; i++) {
  if (str[i] === '(')
   leftNum++;
  else if (str[i] === ')')
   rightNum++, rightIndex=i;
  if (leftNum === rightNum)
   return {start: leftIndex, end: rightIndex}
 }

 throw Error('Parenthesis at position ' + leftIndex + ' of "' + str + '" is unpaired.');
}

function parseExpressionStr(inputString) {
 var result = [], str = inputString;
 while (str) {
  var nextPart = str.match(/([\d]+)|([\+\-\^\*\/])|([a-zA-z])/);
  if (!nextPart)
   return result;
  if (nextPart.length === 0)
   throw Error('Unable to parse expression string "' + inputString + '". Remainder "' + str + '" could not be parsed.');
  else if (nextPart[1])     // First group (digits) matched
   result.push(new Polynomial(parseFloat(nextPart[0])));
  else if (nextPart[3])     // Third group (symbol) matched
   result.push(new Polynomial(nextPart[0]));
  else         // Second group (operator) matched
   result.push(nextPart[0]);
  str = str.substring(nextPart.index+nextPart[0].length);
 }
 return result
}

function isOperator(char) {
 return char === '*' || char === '/' || char === '^' || char === '+' || char === '-';
}


function Polynomial(value) {
 this.terms = (value!==undefined) ? [new Term(value)] : [];
}

Polynomial.prototype.simplify = function() {
 for (var i=0; i<this.terms.length-1; i++) {
  if (this.terms[i].coeff === 0) {
   this.terms.splice(i--, 1);
   continue;
  }
  for (var j=i+1; j<this.terms.length; j++) {
   if (Term.same(this.terms[i], this.terms[j])) {
    this.terms[i] = Term.add(this.terms[i], this.terms[j]);
    this.terms.splice(j--, 1);
   }
  }
 }
}

Polynomial.add = function(a, b) {
 var result = new Polynomial();
 result.terms = a.terms.concat(b.terms);
 result.simplify();
 return result
}

Polynomial.multiply = function(a, b) {
 var result = new Polynomial();
 a.terms.forEach(function(aTerm) {
  b.terms.forEach(function (bTerm) {
   result.terms.push(Term.multiply(aTerm, bTerm));
  });
 });
 result.simplify();

 return result
}

Polynomial.prototype.toHtml = function() {
 var html = ''
 for (var i=0; i<this.terms.length; i++) {
  var term = this.terms[i];
  if (i !== 0)
   html += (term.coeff < 0) ? ' - ' : ' + ';
  else
   html += (term.coeff < 0) ? '-' : '';
  var coeff = Math.abs(term.coeff);
  html += (coeff === 1 && Object.keys(term.symbols).length > 0) ? '' : coeff;

  for (var symbol in term.symbols) {
   var exp = term.symbols[symbol];
   exp = (exp !== 1) ? exp : '';
   html += symbol + '<sup>' + exp + '</sup>';
  }
 }
 return html;
}

function Term(value) {
 this.symbols = {};
 if (typeof value==='string') {   // Symbol
  this.symbols[value] = 1;
  this.coeff = 1;
 } else if (typeof value==='number') { // Number
  this.coeff = value;
 } else {
  this.coeff = 1;
 }
}

Term.same = function(a, b) {
 if (Object.keys(a.symbols).length != Object.keys(b.symbols).length)
  return false
 else
  for (var aSymbol in a.symbols)
   if (a.symbols[aSymbol] != b.symbols[aSymbol]) return false
 return true
}

Term.add = function(a, b) {
 var result = new Term();
 Object.assign(result.symbols, a.symbols);
 result.coeff = a.coeff + b.coeff;
 return result
}

Term.multiply = function(a, b) {
 var result = new Term();
 Object.assign(result.symbols, a.symbols);
 for (var symbol in b.symbols) {
  if (!(symbol in result.symbols))
   result.symbols[symbol] = 0;
  result.symbols[symbol] += b.symbols[symbol];
  if (result.symbols[symbol] === 0)
   delete result.symbols[symbol];
 }
 result.coeff = a.coeff * b.coeff;
 return result
}

function expand(str) {
 var result = [];

 var parens = findOuterParens(str);
 while (parens) {
  result = result.concat(parseExpressionStr(str.slice(0,parens.start)));
  result.push(expand(str.slice(parens.start+1, parens.end)))
  str = str.slice(parens.end+1);
  parens = findOuterParens(str);
 } 
 result = result.concat(parseExpressionStr(str));

 // Move -s to coefficients
 var minus = result.indexOf('-'), minusPoly = new Polynomial(-1);
 while (minus !== -1) {
  result[minus] = '+';
  result[minus+1] = Polynomial.multiply(minusPoly, result[minus+1]);
  minus = result.indexOf('-');
 }

 // Get rid of +s that follow another operator
 var plus = result.indexOf('+');
 while (plus !== -1) {
  if (plus===0 || isOperator(result[plus-1])) {
   result.splice(plus--, 1);
  }
  plus = result.indexOf('+', plus+1);
 }

 // Convert /s to *s
 var divide = result.indexOf('/');
 while (divide !== -1) {
  result[divide] = '*';
  var termsToInvert = result[divide+1].terms;
  if (termsToInvert.length > 1)
   throw Error('Attempt to divide by a polynomial with more than one term.');
  var termToInvert = termsToInvert[0];
  for (var symbol in termToInvert.symbols) {
   termToInvert.symbols[symbol] = -termToInvert.symbols[symbol];
  }
  termToInvert.coeff = 1/termToInvert.coeff;
  divide = result.indexOf('/');
 }

 // Convert ^s to *s
 var power = result.indexOf('^');
 while (power !== -1) {
  var exp = result[power+1];
  if (exp.terms.length > 1 || Object.keys(exp.terms[0].symbols).length != 0)
   throw Error('Attempt to use non-number as an exponent');
  exp = exp.terms[0].coeff;
  var base = result[power-1];
  var expanded = [power-1, 3, base];
  for (var i=0; i<exp-1; i++) {
   expanded.push('*');
   expanded.push(base);
  }
  result.splice.apply(result, expanded);
  power = result.indexOf('^');
 }

 // Add implicit *s
 for (var i=0; i<result.length-1; i++)
  if (!isOperator(result[i]) && !(isOperator(result[i+1])))
   result.splice(i+1, 0, '*');

 // Multiply
 var mult = result.indexOf('*');
 while (mult !== -1) {
  var product = Polynomial.multiply(result[mult-1], result[mult+1]);
  result.splice(mult-1, 3, product);
  mult = result.indexOf('*');
 }

 // Add
 var add = result.indexOf('+');
 while (add !== -1) {
  var sum = Polynomial.add(result[add-1], result[add+1]);
  result.splice(add-1, 3, sum);
  add = result.indexOf('+');
 }

 result[0].simplify();
 return result[0];
}

var problems = ['(x+1)(x+1)/x', '(x+1)^3', '(x^2*x)(x^2)', '(x+1)(x+1)(x+1)',
    '(x+1)^2', 'x(x+1)', '1x^4', '(x + (x+2))(x+5)', '3x^0', '2^3',
    '(x+2x(x+2(x+1)x))', '(x+1)(x-1)', '(x+1)(y+1)', '(x+y+t)(q+x+7)'];

var solutionHTML = '';
for (var i = 0; i<problems.length; i++) {
 solutionHTML += problems[i] + '    =>    ' + expand(problems[i]).toHtml() + '<br>';
}
document.body.innerHTML = solutionHTML;


</script>
</html>

It outputs:

enter image description here

cjg
  • 2,594
  • 12
  • 13