【问题标题】:Recursively Traverse Object till last leaf node JavaScript递归遍历对象直到最后一个叶子节点 JavaScript
【发布时间】:2021-06-09 04:47:34
【问题描述】:

我正在使用 Lucene Syntax (a AND b, c OR d) 作为搜索查询,我需要翻译此搜索查询。为了将 Lucene 语法转换为 JavaScript 对象,我使用了Lucene Parser npm module (https://www.npmjs.com/package/lucene)。

对于 AND 查询,我的翻译需要像这样发生: 查询:凯悦和威斯汀 翻译后的 Lucene 对象

{
   "left":{
      "field":"<implicit>",
      "fieldLocation":null,
      "term":"Hyatt",
      "quoted":false,
      "regex":false,
      "termLocation":{
         "start":{
            "offset":0,
            "line":1,
            "column":1
         },
         "end":{
            "offset":6,
            "line":1,
            "column":7
         }
      },
      "similarity":null,
      "boost":null,
      "prefix":null
   },
   "operator":"AND",
   "right":{
      "field":"<implicit>",
      "fieldLocation":null,
      "term":"Westin",
      "quoted":false,
      "regex":false,
      "termLocation":{
         "start":{
            "offset":10,
            "line":1,
            "column":11
         },
         "end":{
            "offset":16,
            "line":1,
            "column":17
         }
      },
      "similarity":null,
      "boost":null,
      "prefix":null
   }
}

AND 的翻译搜索查询: 对于 AND 查询,我得到以下翻译:

[
   [
     {
        "col":"*",
        "test":"Contains",
        "value":"Hyatt"
     },
     {
        "col":"*",
        "test":"Contains",
        "value":"Westin"
     }
   ]
]

OR 的翻译搜索查询: 对于 OR 查询,我得到以下翻译:

[
   [
      {
         "col":"*",
         "test":"Contains",
         "value":"Hyatt"
      }
   ],
   [
      {
         "col":"*",
         "test":"Contains",
         "value":"Westin"
      }
   ]
]

对于这个翻译,我编写了函数(readExpression),它递归遍历输入(翻译的 Lucene 对象):

result = [];

isFieldImplicit(node: Node): boolean {
    return node.field === '<implicit>';
  }

isFreeTextSearch(searchText: string): boolean {
    // user RegExp for pattern matching of field.f1:abc pattern... i.e. alpha-num.alpha-num:any-char
    return !this.isContainingLuceneReservedCharacters(searchText);
  }

isAndOperator(expression): boolean {
    return expression && expression.operator && expression.operator === 'AND';
  }

isOrOperator(expression): boolean {
    return expression && expression.operator && expression.operator === 'OR';
  }

isQueryContainingQuotes(searchText: string): boolean {
    const matches = searchText.match(/"/g);
    const firstLastCharacterQuoteMatch = new RegExp(
      /((?<![\\])['"])((?:.(?!(?<![\\])\1))*.?)\1/
    );
    const isFirstAndLastCharacterQuote = firstLastCharacterQuoteMatch.test(
      searchText
    );

    if (!matches) {
      return false;
    }

    if (/""/.test(searchText) && isFirstAndLastCharacterQuote) {
      return true;
    }

    return isFirstAndLastCharacterQuote;
  }

isQueryEndingWithStar(searchText: string): boolean {
    return searchText.endsWith('*');
  }

doesQueryContainsStar(searchText: string): boolean {
    return searchText.indexOf('*') !== -1;
  }

getTestFromQuery(keyword: string): string {
    if (this.isQueryContainingQuotes(keyword)) {
      return WHERE_CLAUSE_TESTS.CONTAINS;
    } else if (this.isQueryEndingWithStar(keyword)) {
      return WHERE_CLAUSE_TESTS.CONTAINS_ANY;
    } else {
      return WHERE_CLAUSE_TESTS.CONTAINS_ANY;
    }
  }

generateFreeTextClause(node: Node): WhereClause[] {
    const searchText = node.quoted ? `"${node.term}"` : node.term;
    const clause = [
      {
        col: '*',
        test: this.getTestFromQuery(searchText as string),
        value: (node.term as string).replace('*', '')
      } as WhereClause
    ];
    return clause;
  }

generateFieldSearchClause(node: Node): WhereClause[] {
    const searchText = node.quoted ? `"${node.term}"` : node.term;
    const clause = [
      {
        col: node.field.split('.')[1],
        test: this.getTestFromQuery(searchText as string),
        value: (node.term as string).replace('*', '')
      } as WhereClause
    ];
    return clause;
  }

readExpression(expression): any {
    let left, right;
    if (expression && expression.field) {
      return expression;
    }

    if (this.isOrOperator(expression)) {
      left = this.readExpression(expression.left);
      right = this.readExpression(expression.right);
      if (left) {
        if (this.isFieldImplicit(left)) {
          this.results.push(this.generateFreeTextClause(left));
        } else {
          this.results.push(this.generateFieldSearchClause(left));
        }
      }
      if (right) {
        if (this.isFieldImplicit(right)) {
          this.results.push(this.generateFreeTextClause(right));
        } else {
          this.results.push(this.generateFieldSearchClause(right));
        }
      }
      console.log(this.results);
      return this.results;
    }
    if (this.isAndOperator(expression)) {
      left = this.readExpression(expression.left);
      right = this.readExpression(expression.right);
      if (left) {
        if (this.isFieldImplicit(left)) {
          this.results.push(this.generateFreeTextClause(left)[0]);
        } else {
          this.results.push(this.generateFieldSearchClause(left)[0]);
        }
      }
      if (right) {
        if (this.isFieldImplicit(right)) {
          this.results.push(this.generateFreeTextClause(right)[0]);
        } else {
          this.results.push(this.generateFieldSearchClause(right)[0]);
        }
      }
      console.log(this.results);
      return this.results;
    }
  }

它适用于 1 级 AND 和 OR,但一旦我有 2 级,它就会失败。 查询:(Hyatt AND Westin)OR Orchid

我期待以下翻译:

[
   [
      {
         "col":"*",
         "test":"Contains",
         "value":"Hyatt"
      },
      {
         "col":"*",
         "test":"Contains",
         "value":"Westin"
      }
   ],
   [
      {
          "col":"*",
          "test":"Contains",
          "value":"Orchid"
       }
   ]
]

在递归调用中,OR 的叶节点被忽略并且没有注入到结果数组中。欢迎提出任何建议。

【问题讨论】:

  • 我建议这包含太多和太少。这是一堵巨大的代码墙,要尝试通读,看起来输入格式中的几乎所有内容都不是问题所必需的。另一方面,您向我们展示了它工作的示例输入和输出,但仅输出不工作的情况。您能否创建一个更简洁的minimal reproducible example,包括失败的案例,但跳过大部分不相关的字段?并缩小有问题的代码以仅处理特定条件下的 AND/OR?最后,你的输出真的应该忽略 AND 和 OR 之间的区别吗?
  • 这并不是说这是一个坏问题,但如果你简化,你可能会得到更多的帮助。

标签: javascript recursion functional-programming lucene


【解决方案1】:

无需深入研究您的代码(请参阅我的 cmets 问题),其核心应该涉及简单的递归。

在这里,我从我认为相关的输入中得到你的输出:

const convert = (query) =>
  'operator' in query
    ? [convert (query .left), convert (query .right)]
    : {
        col: '*',
        test: 'Contains',
        value: query .term
      }

const query = {left: {left: {field: "<implicit>", fieldLocation: null, term: "Hyatt", quoted: false, regex: false, termLocation: {start: {offset: 2, line: 1, column: 3}, end: {offset: 8, line: 1, column: 9}}, similarity: null, boost: null, prefix: null}, operator: "AND", right: {field: "<implicit>", fieldLocation: null, term: "Westin", quoted: false, regex: false, termLocation: {start: {offset: 12, line: 1, column: 13}, end: {offset: 18, line: 1, column: 19}}, similarity: null, boost: null, prefix: null}, parenthesized: true}, operator: "OR", right: {field: "<implicit>", fieldLocation: null, term: "Orchid", quoted: false, regex: false, termLocation: {start: {offset: 23, line: 1, column: 24}, end: {offset: 29, line: 1, column: 30}}, similarity: null, boost: null, prefix: null}}

console .log (convert (query))
.as-console-wrapper {max-height: 100% !important; top: 0}

当然我硬编码col: '*'test: 'Contains';你必须适当地填写它们。我的测试看看我们是在树枝还是树叶 ('operator' in query) 可能太天真了。除了我这里的情况之外,您可能还需要其他情况(AND/OR 对和叶节点。)最后,您可能会替换此函数的每一部分,但它很可能作为一个框架增加其他需求。

我仍然认为您的输出格式很奇怪。此结果不会区分 '(Hyatt AND Westin) OR Orchid''(Hyatt AND Westin) AND Orchid''(Hyatt OR Westin) OR Orchid''(Hyatt OR Westin) AND Orchid'

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-11-01
    • 2011-04-18
    • 2012-03-21
    • 1970-01-01
    • 1970-01-01
    • 2017-02-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多