简化表达式树

问题描述

我正在尝试编写一个简化数学表达式的程序。

我已经编写了一个字符串转换为二叉树的解析器。 例如 (1+2)*x 会变成

    *
   / \
  +   x
 / \
1   2

我简化这些树的想法如下: 您存储一组树及其简化版本 例如

    *                       +
   / \                     / \
  a   +        and        *   *
     / \                 / \ / \
    b   c               a  b a  c

(其中 a,b,c 可以是任何子树) 然后,如果我找到与存储的树之一匹配的子树,我会 将其替换为简化版。

如有必要,我会重复这个过程,直到树完全简化。

这种方法的问题在于它在某些情况下不能“组合类似的术语”。 例如,如果我尝试存储树:

      +                *
     / \      and     / \
    x   x            2   x

然后,当我尝试使用以下树简化表达式 x+y+x 时:

    +
   / \
  x   +
     / \
    y   x  

不会简化为2x+y,因为子树

      +     
     / \     
    x   x      

不包含在树中,因此树不会被简化。

我尝试编写一个显式算法来组合类似的术语,但有太多 需要考虑的情况。

谁能帮我找到解决这个问题的方法

解决方法

这是计算机代数系统中使用的基本思想之一。

对于 Plus (+) 和 Times (*) 这样的运算符,您可以定义 Flat (associativity) 和 Orderless ({{3 }})。也不要将 PlusTimes 定义为“二元”运算符,而是“多参数”运算符。

所以输入如下:

Plus(x,Plus(y,x))

在第一步中可以转换(扁平化),因为Flat属性为

Plus(x,y,x)

在下一步中,它可以被转换(排序),因为 Orderless 属性为

Plus(x,x,y)

在您的“评估”步骤中,您现在可以检查参数并将表达式“简化”为:

Plus(Times(2,x),y)

这种方法的优点是“结构相等”的表达式以相同的“规范形式”存储,并且例如可以更容易地与所用编程语言中的“对象相等”进行比较。

,

我们可以考虑多项式

我们得到 '+'-reducer 中两个多项式的 '*'-reducerX

现在在树中,我们可以考虑一个“不可约”多项式,而不是将标量或 x 视为节点。

然后我们应用 '*'-reducer 如果节点运算符是 *'+'-reducer 否则这两个不可约多项式都将转换为新的不可约多项式。

例如 where P_a,P_b 两个多项式和

P_a = {
  x0: 1 // term of degree 0 idem 1
  x1: 2 // 2x
  x3: 4 // 4x^3
}

P_b = {x1: 3}

我们得到总和:P_a + P_b = {x0: 1,x1: 5,x3: 4}

(所以树 ['+',P_a,P_b] 简化为 {x: 0,x3: 4}

乘法得到:P_a * P_b = {x1: 3,x2: 6,x3: 12}

最终,我们在 X 中得到了一个不可约的多项式。

我们可以将该多项式写回二叉树(因此是一个简化的树):

对于每个monome(在X^i中),写出其关联的二叉树(只包含*运算符) 例如:5x^3 => ['*',['*',x],5] 然后将它们相加 例如:1 + x + x^2 => ['+',1,1],x]

同样的想法(idem 实现 '+'-reducer/'*'-reducer)可以应用于在 XYZ 中具有多项式的表达式,或其他(所以在你的情况下,x,y)

下面是一个实现示例(您可以使用 nodejs 取消注释并通过测试)

// a,b are polynomes of form {monomialKey: scalar,monomialKey2,scalar2,...}
// a monomial key is e.g x1y2z2
const add = (a,b) => {
  const out = Object.assign({},a)
  Object.entries(b).forEach(([monomialKey,scalar]) => {
    out[monomialKey] = (out[monomialKey] || 0) + scalar
    if (out[monomialKey] === 0) {
      delete out[monomialKey]
    }
  })
  return out
}
// transforms x1y2z2 to {x: 1,y: 2,z: 2}
const parseKey = s => s.match(/[a-z]+\d+/g).reduce((o,kv) => {
  const [,varname,deg] = kv.match(/([a-z]+)(\d+)/)
  o[varname] = parseInt(deg)
  return o
},{})
const writeKey = o => Object.entries(o).reduce((s,[varname,deg]) => s + varname+deg,'')

// simplify monomial,e.g x1y3*x1 => x2y3
const timesMonomialKey = (iA,iB) => {
  const a = parseKey(iA)
  const b = parseKey(iB)
  const out = {}
  ;[a,b].forEach(x => Object.entries(x).forEach(([varname,deg]) => {
    if (deg === 0) return
    out[varname] = (out[varname] || 0) + deg
  }))
  if (Object.keys(out).length === 0) return writeKey({ x: 0 })
  return writeKey(out)
}
// a,b both polynomes
const times = (a,b) => {
  const out = {}
  Object.entries(a).forEach(([monimalKeyA,sA]) => {
    Object.entries(b).forEach(([monimalKeyB,sB]) => {
      const key = timesMonomialKey(monimalKeyA,monimalKeyB)
      out[key] = (out[key] || 0) + sA * sB
      if (out[key] === 0) {
        delete out[key]
      }
    })
  })
  return out
}


const reduceTree = t => { // of the form [operator,left,right] or val
  if (!Array.isArray(t)) {
    return typeof(t) === 'string'
      ? { [writeKey({ [t]: 1 })]: 1 } // x => {x1: 1}
      : { [writeKey({ x: 0 })]: t }  // 5 => {x0: 5}
  }
  const [op,leftTree,rightTree] = t
  const left = reduceTree(leftTree)
  const right = reduceTree(rightTree)
  return op === '+' ? add(left,right) : times(left,right)
}
const writePolynomial = o => {
  const writeMonomial = ([key,s]) => {
    const a = parseKey(key)
    const factors = Object.entries(a).flatMap(([varname,deg]) => {
      return Array.from({length: deg}).fill(varname)
    }).concat(s !== 1 ? s : [])
    return factors.reduce((t,next) => ['*',t,next])
  }
  if (Object.keys(o).length === 0) return 0
  return Object.entries(o).map(writeMonomial).reduce((t,next) => ['+',next])
}
console.log(writePolynomial(reduceTree(['+',['+','x','y'],'x'])))
//const assert = require('assert')
//assert.deepEqual(parseKey('x0y2z3'),{ x: 0,z: 3 })
//assert.deepEqual(writeKey({ x: 0,z: 3 }),'x0y2z3')
//assert.deepEqual(timesMonomialKey('x1y2','x3z1'),'x4y2z1')
//assert.deepEqual(timesMonomialKey('x0y0','z0'),'x0')
//assert.deepEqual(timesMonomialKey('x0y0','z0x1'),'x1')
//assert.deepEqual(add({x0: 3,x1: 2},{x0: 4,x3: 5}),{x0: 7,x1: 2,x3: 5})
//assert.deepEqual(add({x0: 3,y1: 2},y2: 5}),y1: 2,y2: 5})
//assert.deepEqual(add({x0: 1},{x0: -1}),{})
//assert.deepEqual(times({x0: 3,x1: 5}),{x0: 12,x1: 23,x2: 10})
//assert.deepEqual(times(
//  {x1y0: 3,x1y1: 2},//  {x1y0: 4,x1y1: 5}),//  {x2: 12,x2y1: 23,x2y2: 10}
//)
//assert.deepEqual(reduceTree('x'),{x1: 1})
//assert.deepEqual(reduceTree(['*',2,'x']),{x1: 2})
//assert.deepEqual(reduceTree(['+',{x0: 2,x1: 1})
//assert.deepEqual(reduceTree(['+','y','x']]),{x1: 2,y1: 1})
//assert.deepEqual(writePolynomial({ x1y1:1,x1y2: 2}),2]])
//assert.deepEqual(writePolynomial(reduceTree(['*',0])),0)
//assert.deepEqual(writePolynomial(reduceTree(['+',0],2])),2)
//
//// finally your example :)
//assert.deepEqual(writePolynomial(reduceTree(['+','x'])),2],'y'])