感谢各位的指南,至少我想出了自己的解决方案。因为这是我第一次进行数学公式解析,请原谅我如果处理不正确或效率低下,或者帮助我寻找错误:
基本上,这是我实现它的步骤:
1. 在解析之前,始终验证模式。当有误时抛出错误。
2. 验证后,我们将中缀表示法转换为前缀表示法。此步骤要求“and”比“or”优先级高。
- 反转给定的模式
- 进行中缀到后缀表示法的转换。我从
这里学习。
- 再次反转
- 此时应完成中缀到前缀的转换
3. 从前缀表示法构建树,使得
- 节点始终具有最多两个分支
- 向下遍历直到达到完整的叶子节点
4. 优化树,使其合并相似的运算符(例如具有子元素
$and
的多个
$and
运算符可以合并并形成更短的树)。
5. 与给定的条件集混合,所有工作都完成了!
工作示例可在此处找到:
http://jsfiddle.net/chaoszcat/uGKYj/3/
以下是工作代码:
(function() {
function isNumeric(n) {
return !isNaN(parseFloat(n))&&isFinite(n);
}
function Node(token) {
this.parent = null;
this.children = [];
this.token = token;
this.is_operator = token === 'and' || token === 'or';
this.is_numeric = !this.is_operator;
this.destroyed = false;
}
Node.prototype = {
isOperator: function() { return this.is_operator;},
isNumeric: function() { return this.is_numeric;},
isFull: function() {
return this.children.length >= 2;
},
addChild: function(node) {
node.parent = this;
this.children.push(node);
},
hasParent: function() {
return this.parent !== null;
},
indexOfChild: function(node) {
for (var i = 0 ; i < this.children.length ; ++i) {
if (this.children[i] === node) {
return i;
}
}
return -1;
},
removeChild: function(node) {
var idx = this.indexOfChild(node);
if (idx >= 0) {
this.children[idx].parent = null;
this.children.splice(idx, 1);
}
},
passChildrenTo: function(node) {
for (var i = 0 ; i < this.children.length ; ++i) {
node.addChild(this.children[i]);
}
this.destroy();
},
destroy: function() {
this.parent.removeChild(this);
this.children = null;
this.destroyed = true;
}
};
function Tree(prefixTokens) {
this.buildTree(prefixTokens);
this.optimize(this.root);
}
Tree.prototype = {
root: null,
deepestNode: null,
render: function(crits) {
return this.buildCriteria(this.root, crits);
},
buildCriteria: function(node, crits) {
var output = {},
label = '$'+node.token;
output[label] = [];
for (var i = 0 ; i < node.children.length ; ++i) {
if (node.children[i].isOperator()) {
output[label].push(this.buildCriteria(node.children[i], crits));
}else{
output[label].push(crits[node.children[i].token-1]);
}
}
return output;
},
optimize: function(node) {
for (var i = 0 ; i < node.children.length ; ++i) {
if (node.children[i].isOperator()) {
this.optimize(node.children[i]);
if (node.children[i].token === node.token) {
node.children[i].passChildrenTo(node);
i = 0;
}
}
}
},
buildTree: function(tokens) {
for (var i = 0 ; i < tokens.length ; ++i) {
this.addNode(new Node(tokens[i]));
}
},
addNode: function(node) {
if (this.root === null) {
this.root = node;
this.deepestNode = node;
return;
}
while(this.deepestNode && this.deepestNode.isFull()) {
this.deepestNode = this.deepestNode.parent;
}
if (this.deepestNode) {
this.deepestNode.addChild(node);
}
if (node.isOperator()) {
this.deepestNode = node;
}
}
};
var CriteriaParser = {
parse: function(str, crits) {
var tokens = this.tokenize(str),
validationResult = this.validate(tokens, crits),
prefixNotation = '';
if (validationResult === true) {
prefixNotation = this.infixToPrefix(tokens);
return (new Tree(prefixNotation)).render(crits);
}else{
return validationResult;
}
},
infixToPrefix: function(tokens) {
var reversedTokens = tokens.slice(0).reverse(),
stack = [],
output = [];
do {
var stackTop = stack.length > 0 ? stack[stack.length-1] : null,
token = reversedTokens.shift();
if (token === 'and') {
while(stackTop === 'and') {
output.push(stack.pop());
stackTop = stack.length > 0 ? stack[stack.length-1] : null;
}
stack.push(token);
stackTop = token;
}else if (token === 'or') {
while(stackTop === 'and' || stackTop === 'or') {
output.push(stack.pop());
stackTop = stack.length > 0 ? stack[stack.length-1] : null;
}
stack.push(token);
stackTop = token;
}else if (token === '(') {
while(stackTop !== ')' && stackTop !== undefined) {
output.push(stack.pop());
stackTop = stack.length > 0 ? stack[stack.length-1] : null;
}
stack.pop();
stackTop = stack.length > 0 ? stack[stack.length-1] : null;
}else if (token === ')') {
stack.push(token);
}else if (isNumeric(token)) {
output.push(token);
}else if (token === undefined) {
while(stack.length) {
stackTop = stack.pop();
if (stackTop !== undefined && stackTop !== ')') {
output.push(stackTop);
}
}
}
}while(stack.length || reversedTokens.length);
return output.reverse();
},
tokenize: function(str) {
var pattern = str.replace(/\s/g, ''),
tokens = pattern.split(''),
tokenized = [];
var token = null,
next = null;
while (tokens.length > 0) {
token = tokens.shift();
next = tokens.length > 0 ? tokens[0] : null;
if (token === '(' || token === ')') {
tokenized.push(token);
}else if (token === 'a' && tokens.length >= 2 && tokens[0] === 'n' && tokens[1] === 'd') {
tokenized.push(token + tokens.shift() + tokens.shift());
}else if (token === 'o' && tokens.length >= 1 && next === 'r') {
tokenized.push(token + tokens.shift());
}else if (isNumeric(token)) {
while(isNumeric(next)) {
token += next;
tokens.shift();
next = tokens.length > 0 ? tokens[0] : null;
}
tokenized.push(token);
}else{
tokenized.push(token);
}
}
return tokenized;
},
validate: function(tokens, crits) {
var valid = true,
token = null,
stack = [],
nextToken = null,
criteria_count = crits.length;
for (var i = 0 ; i < tokens.length ; ++i) {
token = tokens[i];
nextToken = i < tokens.length - 1 ? tokens[i+1] : null;
if (token === '(') {
stack.push('(');
if (!isNumeric(nextToken) && nextToken !== '(' && nextToken !== ')') {
throw 'Unexpected token "'+nextToken+'"';
}
}else if (token === ')') {
if (stack.length > 0) {
stack.pop();
}else{
throw 'Unexpected closing bracket';
}
if (nextToken !== ')' && nextToken !== 'and' && nextToken !== 'or' && nextToken !== null) {
throw 'Unexpected token "'+nextToken+'"';
}
}else if (token === 'and' || token === 'or') {
if (!isNumeric(nextToken) && nextToken !== '(') {
throw 'Unexpected token "'+nextToken+'"';
}
}else if (isNumeric(token) && token <= criteria_count) {
if (nextToken !== ')' && nextToken !== 'and' && nextToken !== 'or') {
throw 'Unexpected token "'+nextToken+'"';
}
}else{
throw 'Unexpected token "'+token+'"';
}
}
if (valid && stack.length > 0) {
throw 'Missing '+stack.length+' closing bracket';
}
return valid;
}
};
var pattern = '((1 or 3) and (2 or 4) or 5)',
crits = [
1, 2, 3, 4, 5
];
setTimeout(function() {
var result;
try {
result = JSON.stringify(CriteriaParser.parse(pattern, crits), undefined, 4);
}catch(e) {
result = e;
}
var pre = document.createElement('pre');
pre.innerHTML = result;
document.body.appendChild(pre);
}, 10);
})();
((1或3)和(2或4)或5)
。 - Lionel Chan