Логическое строку в модуле Elasticsearch в ОСС запрос


Я собираю маленький модуль для ОСС-релизе, что позволит вам разбирать логическое выражение, состоящее из and/AND/or/OR'ы (без скобок) и выход полный Elasticsearch в запросе.

Логические выражения:

Сейчас он использует OR в качестве основы и ставит все на вершине, что, как ANDС. Это означает, что AND персонализация слева направо.

Мне не хватает входного сигнала на:

  1. Качество результата запроса Elasticsearch в - это может быть упрощенный? Существуют ли более эффективные подходы?
  2. Я понимаю это логическое выражение.

def string_to_query(s):
    s = s.lower()
    tokens = [' '.join(t.split()) for t in s.split('or')]
    or_terms = []
    while tokens:
        leaf = tokens.pop()

        and_terms = leaf.split('and')
        if len(and_terms) < 2:
            term = and_terms[0]
            or_terms.extend([
                {"match": {"Review.Text": {
                    "query": term, "operator": "and"}}},
                {"match": {"Review.Title": {
                    "query": term, "operator": "and"}}}
            ])
        else:
            filters = [
                {"bool": {
                    "should": [{"match": {"Review.Text": {
                        "query": term, "operator": "and"}}},
                        {"match": {"Review.Title": {
                            "query": term, "operator": "and"}}}]

                }} for term in and_terms]
            or_terms.append(
                {"bool": {
                    "must": filters
                }})

    return {"query":
            {"bool": {
                "should": or_terms
            }}}


query = string_to_query(
    'dog and dog food or cat and cat food'
)

assert query == {
"query": {
    "bool": {
    "should": [
        {
        "bool": {
            "must": [
            {
                "bool": {
                "should": [
                    {
                    "match": {
                        "Review.Text": {
                        "operator": "and",
                        "query": "cat "
                        }
                    }
                    },
                    {
                    "match": {
                        "Review.Title": {
                        "operator": "and",
                        "query": "cat "
                        }
                    }
                    }
                ]
                }
            },
            {
                "bool": {
                "should": [
                    {
                    "match": {
                        "Review.Text": {
                        "operator": "and",
                        "query": " cat food"
                        }
                    }
                    },
                    {
                    "match": {
                        "Review.Title": {
                        "operator": "and",
                        "query": " cat food"
                        }
                    }
                    }
                ]
                }
            }
            ]
        }
        },
        {
        "bool": {
            "must": [
            {
                "bool": {
                "should": [
                    {
                    "match": {
                        "Review.Text": {
                        "operator": "and",
                        "query": "dog "
                        }
                    }
                    },
                    {
                    "match": {
                        "Review.Title": {
                        "operator": "and",
                        "query": "dog "
                        }
                    }
                    }
                ]
                }
            },
            {
                "bool": {
                "should": [
                    {
                    "match": {
                        "Review.Text": {
                        "operator": "and",
                        "query": " dog food"
                        }
                    }
                    },
                    {
                    "match": {
                        "Review.Title": {
                        "operator": "and",
                        "query": " dog food"
                        }
                    }
                    }
                ]
                }
            }
            ]
        }
        }
    ]
    }
}
}


Комментарии
2 ответа

Использование split делает свои функции довольно хрупок:

>>> string_to_query('doctor and heart')
{'query': {'bool': {'should': [{'bool': {'must': [{'bool': {'should': [{'match': {'Review.Text': {'operator': 'and',
'query': ''}}},
{'match': {'Review.Title': {'operator': 'and',
'query': ''}}}]}},
{'bool': {'should': [{'match': {'Review.Text': {'operator': 'and',
'query': ' '
'heart'}}},
{'match': {'Review.Title': {'operator': 'and',
'query': ' '
'heart'}}}]}}]}},
{'match': {'Review.Text': {'operator': 'and',
'query': 'doct'}}},
{'match': {'Review.Title': {'operator': 'and',
'query': 'doct'}}}]}}}

Что эквивалентно: "(пустая строка и сердце) или Дис", а не "доктор и сердце".

Другой случай, чтобы рассмотреть, является использование "И" ИЛИ "ИЛИ", а слова для поиска, а не операторов (как "Том и Джерри", я не хочу искать документы, содержащие "том" и "Джерри" отдельно, а для документов, содержащих словосочетание "том и Джерри").

Как правило, для такого рода проблем, в промежуточном представлении вырабатывается специальный парсер лучше и проще преобразовать в конечный результат. Здесь я предлагаю производить списком списков, поскольку вы не (пока) рассматривать priorisation из положения, используя скобки. Таким образом:

[
[A, B, C],
[D, E],
[F],
]

Будет эквивалентно "(A и B и C) или (D и E) или F". Который затем может быть легко преобразован в Elasticsearch в запросе ДСЛ, используя простые списочные включения. Загвоздка, однако, заключается в том, что каждое предложение может быть полные предложения и должны подать заявление на два поля: "комментарий.Текст" и "комментарий.Звание". Это где мульти-матч запрос можно упростить весь написав: каждый пункт А, Б, С, D, E и F будут преобразованы в

{'multi_match': {
'query': clause,
'type': 'phrase',
'fields': ['Review.Text', 'Review.Title'],
}}

При всех преимуществах мульти-матч запросов, таких как давая больше веса на одно поле.


Следующий вариант увеличить поддерживаемый синтаксис, чтобы разрешить двойные кавычки в значении "идеальный матч":

import re

class ClauseParser:
def __init__(self, tokenizer, *operators):
self._tokenizer = tokenizer
self._operators = set(operators)
self._found_operator = None

def __iter__(self):
for token in self._tokenizer:
token_value = token.group(0)
if token.group(2) in self._operators:
self._found_operator = token_value
return
yield token_value

@property
def operator(self):
found_operator = self._found_operator
self._found_operator = None
return found_operator

def parser(tokenizer):
clause_parser = ClauseParser(tokenizer, 'and', 'or')
current_group = []
while True:
current_group.append(' '.join(clause_parser))
found_operator = clause_parser.operator
if found_operator != 'and':
yield current_group
if found_operator is None:
return
current_group = []

def convert_and_clauses(clauses):
return [
{'multi_match': {
'query': clause,
'type': 'phrase',
'fields': ['Review.Text', 'Review.Title'],
}} for clause in clauses
]

def string_to_query(phrase):
tokenizer = re.finditer(r'"([^"]+)"|(\w+)', phrase)
query = list(parser(tokenizer))

or_clauses = {'bool': {'should': [
{'bool': {'must': convert_and_clauses(clauses)}}
for clauses in query
]}}

return {'query': or_clauses}

Пример использования:

>>> string_to_query('doctor and heart')
{'query': {'bool': {'should': [{'bool': {'must': [{'multi_match': {'fields': ['Review.Text',
'Review.Title'],
'query': 'doctor',
'type': 'phrase'}},
{'multi_match': {'fields': ['Review.Text',
'Review.Title'],
'query': 'heart',
'type': 'phrase'}}]}}]}}}
>>> string_to_query('"Tom and Jerry" or "Road runner and vil coyote"')
{'query': {'bool': {'should': [{'bool': {'must': [{'multi_match': {'fields': ['Review.Text',
'Review.Title'],
'query': '"Tom '
'and '
'Jerry"',
'type': 'phrase'}}]}},
{'bool': {'must': [{'multi_match': {'fields': ['Review.Text',
'Review.Title'],
'query': '"Road '
'runner '
'and '
'vil '
'coyote"',
'type': 'phrase'}}]}}]}}}
>>> string_to_query('cat and cat food or dog and dog food')
{'query': {'bool': {'should': [{'bool': {'must': [{'multi_match': {'fields': ['Review.Text',
'Review.Title'],
'query': 'cat',
'type': 'phrase'}},
{'multi_match': {'fields': ['Review.Text',
'Review.Title'],
'query': 'cat '
'food',
'type': 'phrase'}}]}},
{'bool': {'must': [{'multi_match': {'fields': ['Review.Text',
'Review.Title'],
'query': 'dog',
'type': 'phrase'}},
{'multi_match': {'fields': ['Review.Text',
'Review.Title'],
'query': 'dog '
'food',
'type': 'phrase'}}]}}]}}}

4
ответ дан 22 марта 2018 в 10:03 Источник Поделиться

Я придумал еще одно решение, которое дает результаты гораздо проще.
Он использует query_string поиск и встроенные логические выражения по определенным направлениям:

def string_to_query(s):
s = s.lower()
tokens = [' '.join(t.split()) for t in s.split('or')]
or_terms = []

while tokens:
leaf = tokens.pop()

and_terms = leaf.split('and')
if len(and_terms) < 2:
term = and_terms[0]
or_terms.append('"{}"'.format(term.strip()))
else:
and_terms = ['"{}"'.format(term.strip()) for term in and_terms]
and_string = "( " + " AND ".join(and_terms) + " )"
or_terms.append(and_string)

query_string = " OR ".join(or_terms)
return {
"query": {
"query_string": {
"fields": ["Review.Title", "Review.Text"],
"query": query_string
}
}
}

query = string_to_query(
'dog and dog food or cat and cat food'
)

assert query == {
"query": {
"query_string": {
"fields": [
"Review.Title",
"Review.Text"
],
"query": "( \"cat\" AND \"cat food\" ) OR ( \"dog\" AND \"dog food\" )"
}
}
}

1
ответ дан 22 марта 2018 в 07:03 Источник Поделиться