UNPKG

mathsteps-experimental-fork

Version:

Step by step math solutions. Experimental Fork

196 lines (188 loc) 6.58 kB
'use strict'; const config = require('../../config.js'); const index = require('../../node/index.js'); const ChangeTypes = require('../../types/changeType/ChangeTypes.js'); // Limit exponent to (a+b)^10. const MAX_EXPONENT = 10; // Precompute common integer nodes. const CACHE_ConstantIntegers = [ [index.default.Creator.constant(0), index.default.Creator.constant(0)], [index.default.Creator.constant(-1), index.default.Creator.constant(1)], [index.default.Creator.constant(-2), index.default.Creator.constant(2)], [index.default.Creator.constant(-3), index.default.Creator.constant(3)], [index.default.Creator.constant(-4), index.default.Creator.constant(4)], ]; // Cache binomial coefficient (Pascal's triangle). // One row is array of binomial with given n: // n! // (n) = --------- // (k) k! (n-k)! const CACHE_PascalTriangleForSum = []; const CACHE_PascalTriangleForDifference = []; function _getPascalTriangleRowForSum(n) { let rv = CACHE_PascalTriangleForSum[n]; if (!rv) { // First row usage. Build it and cache for further use. // Example for n=4: [1 4 6 4 1]. rv = []; const n2 = Math.ceil(n / 2); // Build the first half of row from the scratch. // https://stackoverflow.com/questions/11032781/fastest-way-to-generate-binomial-coefficients rv[0] = _createNodeInteger(1); for (let k = 0; k < n2; k++) rv[k + 1] = _createNodeInteger((rv[k].value * (n - k)) / (k + 1)); // The second half if copy of the first one. for (let k = 0; k < n2; k++) rv[n - k] = rv[k]; // Cache row for further calls. CACHE_PascalTriangleForSum[n] = rv; } return rv } function _getPascalTriangleRowForDifference(n) { let rv = CACHE_PascalTriangleForDifference[n]; if (!rv) { // First row usage. Build it and cache for further use. // Example for n=2: [1, -2, 1]. // Example for n=3: [1, -3, 3, 1]. // Example for n=4: [1, -4, 6, -4, 1]. rv = _getPascalTriangleRowForSum(n).slice(0); for (let idx = 1; idx < n; idx += 2) { // Negate odd coefficients. rv[idx] = index.default.Creator.unaryMinus(rv[idx]); } // Cache row for further calls. CACHE_PascalTriangleForDifference[n] = rv; } return rv } function _getArrayOfCoefficients(n, sign) { if (sign === -1) return _getPascalTriangleRowForDifference(n) else return _getPascalTriangleRowForSum(n) } function _createNodeInteger(value, sign) { let rv = null; let cacheEntry = CACHE_ConstantIntegers[value]; if (!cacheEntry) { // First integer usage. // Create new underlying node and cache for further usage. cacheEntry = [index.default.Creator.constant(-value), index.default.Creator.constant(value)]; CACHE_ConstantIntegers[value] = cacheEntry; } { // +value rv = cacheEntry[1]; } return rv } function _createNodePow(a, k) { // a^k let rv = null; switch (k) { case 0: { // a^0 = 1 rv = _createNodeInteger(1); break } case 1: { // a^1 = a rv = a; break } default: { // General case: a^n rv = index.default.Creator.operator('^', [a, _createNodeInteger(k)]); } } return rv } function multiplyShortFormulas(node) { let rv = null; // eslint-disable-next-line eqeqeq if ((node.op == '^') && index.default.Type.isConstant(node.args[1])) { // base ^ n // (...) ^ n const base = node.args[0]; // eslint-disable-next-line eqeqeq if ((base.op === '+') && (base.args.length == 2)) { // (a+b)^n or (a-b)^n const n = Number.parseInt(node.args[1].value); if ((n > 1) && (n <= MAX_EXPONENT) && (node.args[1].value.toString() === n.toString())) { // n is integer greatater than one. Go on. const terms = []; let changeType = null; let newNode = null; const a = base.args[0]; let b = base.args[1]; // Handle negative second argument. // Split second argument (b) into value and sign part. let bSign = 1; if (index.default.Type.isUnaryMinus(b)) { // (a-b)^n b = b.args[0]; bSign = -1; } else if (index.default.Type.kemuIsConstantNegative(b)) { // (a-4)^n like. b = index.default.Creator.constant(config.default.multiply(-1, b.value)); bSign = -1; } if (bSign === -1) { // (a-b)^n changeType = ChangeTypes.ChangeTypes.KEMU_SHORT_MULTIPLICATION_ABN_SUB; switch (n) { case 2: changeType = ChangeTypes.ChangeTypes.KEMU_SHORT_MULTIPLICATION_AB2_SUB; break case 3: changeType = ChangeTypes.ChangeTypes.KEMU_SHORT_MULTIPLICATION_AB3_SUB; break default: changeType = ChangeTypes.ChangeTypes.KEMU_SHORT_MULTIPLICATION_ABN_SUB; } } else { // (a+b)^n changeType = ChangeTypes.ChangeTypes.KEMU_SHORT_MULTIPLICATION_ABN_ADD; switch (n) { case 2: changeType = ChangeTypes.ChangeTypes.KEMU_SHORT_MULTIPLICATION_AB2_ADD; break case 3: changeType = ChangeTypes.ChangeTypes.KEMU_SHORT_MULTIPLICATION_AB3_ADD; break default: changeType = ChangeTypes.ChangeTypes.KEMU_SHORT_MULTIPLICATION_ABN_ADD; } } // The first a^n term. terms.push(_createNodePow(a, n)); // Mixed terms containing both a and b. const arrayOfCoeffs = _getArrayOfCoefficients(n, bSign); for (let k = 1; k < n; k++) { // binomial(n, k) * a^(n-k) * b^k const termA = _createNodePow(a, n - k); const termB = _createNodePow(b, k); terms.push(index.default.Creator.operator('*', [arrayOfCoeffs[k], termA, termB])); } // The last b^n term. let bn = _createNodePow(b, n); if ((bSign === -1) && (n % 2 === 1)) bn = index.default.Creator.unaryMinus(bn); terms.push(bn); // Join all terms into final sum: // a^n + c1*a^(n-1)*k + ... + b^n newNode = index.default.Creator.operator('+', terms); // Build result object if possible. rv = { changeType, rootNode: newNode, }; } } } return rv } module.exports = multiplyShortFormulas;