【问题标题】:splitting a string based on AND OR logic in javascript在javascript中基于AND OR逻辑拆分字符串
【发布时间】:2016-04-16 06:32:38
【问题描述】:

我的问题是拆分包含逻辑操作的字符串。 例如,这是我的示例字符串:

var rule = "device2.temperature > 20 || device2.humidity>68 && device3.temperature >10"

我需要以一种我可以轻松操作我的逻辑的方式解析该字符串,但我不确定哪种方法会更好。

PS:请记住,这些规则字符串可以有 10 个或更多不同的条件组合,例如 4 个 AND 和 6 个 OR。

【问题讨论】:

  • 为什么要这样做?字符串从何而来?你想轻松操作的逻辑是什么?您需要处理带括号的分组吗?
  • @torazaburo 因为我们使用第三方后端软件来支持物联网应用程序 ThingWorx,它们的警报功能仅限于一台设备,而且我们没有适当的数据库来保存我们自己的信息。无论如何,由于某些功能有限的原因,我们必须将此信息保存在字符串字段中并在 ThingWorx 中处理数据,这就是我们必须以这种方式保存它的原因。
  • 这可能很有趣,使用模板字符串:stackoverflow.com/questions/34882100/…
  • 我的意思是,当你说“操作我的逻辑”时,你的意思是evaluate,还是parse进一步操纵表达式?
  • 如果可以立即评估,这将完全解决我的问题,但如果不可能,那么我可以评估解析后的数据。

标签: javascript string algorithm parsing data-structures


【解决方案1】:

假设没有括号,我可能会使用这样的东西(JavaScript 代码):

function f(v,op,w){
  var ops = {
    '>': function(a,b){ return a > b; },
    '<': function(a,b){ return a < b; },
    '||': function(a,b){ return a || b; },
    '&&': function(a,b){ return a && b; },
     '==': function(a,b){ return a == b;}
  }

  if (ops[op]){
    return ops[op](v,w);
  } else alert('Could not recognize the operator, "' + op + '".');
}

现在,如果你能设法得到一个表达式列表,你就可以对它们进行连续求值:

var exps = [[6,'>',7],'||',[12,'<',22], '&&', [5,'==',5]];

var i = 0, 
    result = typeof exps[i] == 'object' ? f(exps[i][0],exps[i][1],exps[i][2]) : exps[i];

i++;

while (exps[i] !== undefined){
  var op = exps[i++],
      b = typeof exps[i] == 'object' ? f(exps[i][0],exps[i][1],exps[i][2]) : exps[i];

  result = f(result,op,b);
  i++;
}

console.log(result);

【讨论】:

    【解决方案2】:

    如果您绝对确定输入始终是有效的 JavaScript

    var rule = "device2.temperature > 20 || device2.humidity>68 && device3.temperature >10"
    var rulePassed = eval(rule);
    

    请记住,在大多数情况下,“eval”是“邪恶的”,并且可能会引入比它解决的问题更多的问题。

    【讨论】:

    • 所以,我只是从服务器接收到该字符串,我需要创建名为 device2 和 device3 的变量,然后我将向服务器发出另一个请求以检索 device2 和 device3 的实际温度值,然后最后评估该字符串。但是,我现在的问题是根据解析的字符串命名变量。因此,每当我从该字符串中捕获“device2”时,我都需要执行“var device2;”知道我该怎么做吗?
    【解决方案3】:
    function parse(rule){
        return Function("ctx", "return("+rule.replace(/[a-z$_][a-z0-9$_\.]*/gi, "ctx.$&")+")");
    }
    

    比 eval 好一点,因为它很可能会抛出错误,当 sbd.尝试注入一些代码。 因为它会尝试访问 ctx-object 而不是 window-object 上的这些属性。

    var rule = parse("device2.temperature > 20 || device2.humidity>68 && device3.temperature >10");
    var data = {
        device2: {
            temperature: 18,
            humidity: 70
        },
    
        device3: {
            temperature: 15,
            humidity: 75
        }
    };
    
    console.log( rule.toString() );
    console.log( rule(data) );
    

    【讨论】:

      【解决方案4】:

      矫枉过正:

      请注意,未经过全面测试。可能仍包含错误
      而且,代码不会检查语法是否有效,只会抛出一些明显的错误。

      var parse = (function(){    
      
          function parse(){
              var cache = {};
      
              //this may be as evil as eval, so take care how you use it.
              function raw(v){ return cache[v] || (cache[v] = Function("return " + v)) }
      
              //parses Strings and converts them to operator-tokens or functions
              function parseStrings(v, prop, symbol, number, string){
                  if(!prop && !symbol && !number && !string){
                      throw new Error("unexpected/unhandled symbol", v);
                  }else{
                      var w;
                      switch(prop){
                          //keywords
                          case "true":
                          case "false":
                          case "null":
                              w = raw( v );
                              break;
                      }
                      tokens.push( 
                          w || 
                          ~unary.indexOf(prop) && v ||
                          prop && parse.fetch(v) || 
                          number && raw( number ) || 
                          string && raw( string ) ||
                          symbol
                      );
                  }
              }       
      
              var tokens = [];
              for(var i = 0; i < arguments.length; ++i){
                  var arg = arguments[i];
                  switch(typeof arg){
                      case "number":
                      case "boolean":
                          tokens.push(raw( arg ));
                          break;
      
                      case "function":
                          tokens.push( arg );
                          break;
      
                      case "string":
                          //abusing str.replace() as kind of a RegEx.forEach()
                          arg.replace(matchTokens, parseStrings);
                          break;
                  }
              }
      
              for(var i = tokens.lastIndexOf("("), j; i>=0; i = tokens.lastIndexOf("(")){
                  j = tokens.indexOf(")", i);
                  if(j > 0){
                      tokens.splice(i, j+1-i, process( tokens.slice( i+1, j ) ));
                  }else{
                      throw new Error("mismatching parantheses")
                  }
              }
              if(tokens.indexOf(")") >= 0) throw new Error("mismatching parantheses");
      
              return process(tokens);
          }
      
          //combines tokens and functions until a single function is left
          function process(tokens){
              //unary operators like
              unary.forEach(o => {
                  var i = -1;
                  while((i = tokens.indexOf(o, i+1)) >= 0){
                      if((o === "+" || o === "-") && typeof tokens[i-1] === "function") continue;
                      tokens.splice( i, 2, parse[ unaryMapping[o] || o ]( tokens[i+1] ));
                  }
              })
              //binary operators
              binary.forEach(o => {
                  for(var i = tokens.lastIndexOf(o); i >= 0; i = tokens.lastIndexOf(o)){
                      tokens.splice( i-1, 3, parse[ o ]( tokens[i-1], tokens[i+1] ));
                  }
              })
      
              //ternary operator
              for(var i = tokens.lastIndexOf("?"), j; i >= 0; i = tokens.lastIndexOf("?")){
                  if(tokens[i+2] === ":"){
                      tokens.splice(i-1, 5, parse.ternary(tokens[i-1], tokens[i+1], tokens[i+3] ));
                  }else{
                      throw new Error("unexpected symbol")
                  }
              }
      
              if(tokens.length !== 1){
                  throw new Error("unparsed tokens left");
              }
              return tokens[0];
          }
      
          var unary = "!,~,+,-,typeof".split(",");
          var unaryMapping = {    //to avoid collisions with the binary operators
              "+": "plus",
              "-": "minus"
          }
          var binary = "**,*,/,%,+,-,<<,>>,>>>,<,<=,>,>=,==,!=,===,!==,&,^,|,&&,||".split(",");
          var matchTokens = /([a-z$_][\.a-z0-9$_]*)|([+\-*/!~^]=*|[\(\)?:]|[<>&|=]+)|(\d+(?:\.\d*)?|\.\d+)|(["](?:\\[\s\S]|[^"])+["]|['](?:\\[\s\S]|[^'])+['])|\S/gi;
      
          (function(){
              var def = { value: null };
              var odp = (k,v) => { def.value = v; Object.defineProperty(parse, k, def) };
      
              unary.forEach(o => {
                  var k = unaryMapping[o] || o;
                  k in parse || odp(k, Function("a", "return function(ctx){ return " + o + "(a(ctx)) }"));
              })
      
              //most browsers don't support this syntax yet, so I implement this manually
              odp("**", (a,b) => (ctx) => Math.pow(a(ctx), b(ctx)));
              binary.forEach(o => {
                  o in parse || odp(o, Function("a,b", "return function(ctx){ return a(ctx) "+o+" b(ctx) }"));
              });
      
              odp("ternary", (c,t,e) => ctx => c(ctx)? t(ctx): e(ctx));
      
              odp("fetch", key => {
                  var a = key.split(".");
                  return ctx => {
                      //fetches a path, like devices.2.temperature
                      //does ctx["devices"][2]["temperature"];
                      for(var i=0, v = ctx /*|| window*/; i<a.length; ++i){
                          if(v == null) return void 0;
                          v = v[a[i]];
                      }
                      return v;
                  }
              });
      
              /* some sugar */
              var aliases = {
                  "or": "||",
                  "and": "&&",
                  "not": "!"
              }
              for(var name in aliases) odp(name, parse[aliases[name]]);
          })();
      
          return parse;
      })();
      

      和你的代码:

      var data = {
          device2: {
              temperature: 18,
              humidity: 70
          },
      
          device3: {
              temperature: 15,
              humidity: 75
          }
      };
      
      //you get back a function, that expects the context to work on (optional).
      //aka. (in wich context/object is `device2` defined?)
      var rule = parse("device2.temperature > 20 || device2.humidity>68 && device3.temperature >10");
      console.log("your rule resolved:", rule(data));
      

      糖:

      var rule1 = parse("device2.temperature > 20");
      var rule2 = parse("device2.humidity>68 && device3.temperature >10");
      
      //partials/combining rules to new ones
      //only `and` (a && b), `or` (a || b), `plus` (+value), `minus` (-value) and 'not', (!value) have named aliases
      var rule3 = parse.or(rule1, rule2);
      //but you can access all operators like this
      var rule3 = parse['||'](rule1, rule2);
      //or you can combine functions and strings 
      var rule3 = parse(rule1, "||", rule2);
      
      console.log( "(", rule1(data), "||", rule2(data), ") =", rule3(data) );
      
      //ternary operator and Strings (' and " supported)
      var example = parse(rule1, "? 'device2: ' + device2.temperature : 'device3: ' + device3.temperature");
      console.log( example(data) )
      

      还有什么要知道的:

      代码处理运算符优先级并支持圆括号

      如果无法获取路径,则特定函数将返回未定义(此处未引发错误)
      访问路径中的 Array-keys:parse("devices.2.temperature") fetches devices[2].temperature

      未实施:

      解析数组和解析函数调用以及与值修改有关的一切。这个引擎做一些计算,它期望一些值,并给你一个值。不多也不少。

      【讨论】:

      • 非常感谢,我认为一旦完成数据配置,它就会正常工作。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-31
      • 1970-01-01
      相关资源
      最近更新 更多