Python查询处理和布尔搜索

时间:2017-10-27 14:54:47

标签: python python-3.x search-engine booleanquery boolean-search

我有一个反向索引(作为字典),我想把一个布尔搜索查询作为输入来处理它并产生一个结果。

倒排索引是这样的:

{ Test : { FileName1: [213, 1889, 27564], FileName2: [133, 9992866, 27272781, 78676818], FileName3: [9211] }, Try : { FileName4 ... ..... }

现在,给定一个布尔搜索查询,我必须返回结果。

示例:

布尔搜索查询:test AND try 结果应该是所有包含单词test和try的文档。

布尔搜索查询:test OR try 结果应该是所有测试或尝试的文档。

布尔搜索查询:test AND NOT try 结果应该是所有已经测试但未尝试的文档。

如何构建此搜索引擎以处理给定的布尔搜索查询?

提前致谢!

3 个答案:

答案 0 :(得分:3)

另一种方法可能是张贴列表的内存内交集(对于AND案例,您可以针对OR,NOT等进行增强)。

在发布列表上附加了一个简单的合并算法,假设列表已排序(增加doc_id顺序,如果我们为索引建立索引,则可以轻松实现)正确的术语)-这将改善时间复杂度(O(n1 + n2)),因为我们将对已排序列表执行线性合并,并且可能会更早停止。

现在假定我们的位置倒排索引看起来像这样 :(与您的位置类似,但将列表作为列表而不是字典发布-这将允许将来使用时进行压缩)positional index 映射的位置-字符串>术语,而每个术语由(tf,发布列表([P1,P2,...]))组成,每个发布都有(docid,df,位置列表)。现在,我们可以迭代地对所有发布列表执行简单的AND:

def search(self, sq: BoolQuery) -> list:

    # Performs a search from a given query in boolean retrieval model,
    # Supports AND queries only and returns sorted document ID's as result:

    if sq.is_empty():
        return super().search(sq)

    terms = [self.index[term] for term in sq.get_terms() if term in self.index]
    if not terms:
        return []

    # Iterate over posting lists and intersect:
    result, terms = terms[0].pst_list, terms[1:]
    while terms and result:
        result = self.intersect(result, terms[0].pst_list)
        terms = terms[1:]
    return [p.id for p in result]

现在让我们看一下交叉点:

def intersect(p1: list, p2: list) -> list:

    # Performs linear merge of 2x sorted lists of postings,
    # Returns the intersection between them (== matched documents):

    res, i, j = list(), 0, 0
    while i < len(p1) and j < len(p2):
        if p1[i].id == p2[j].id:
            res.append(p1[i])
            i, j = i + 1, j + 1
        elif p1[i].id < p2[j].id:
            i += 1
        else:
            j += 1
    return res

以后可以在执行词组搜索时扩展此简单算法(编辑交点以计算倾斜距离,例如:| pos1-pos2 | <倾斜)

答案 1 :(得分:2)

编辑:我保留了我的答案的第一部分,因为如果这是一个学校作业,那么在我看来,这仍然是一个更好的方法来完成任务。我用更新匹配OP的问题替换了答案的第二部分。

您似乎想要做的是创建一个查询字符串解析器,它将读取查询字符串并将其转换为一系列AND / OR / NOT组合以返回正确的键。

有两种方法。

  1. 根据您所编写的内容,到目前为止,最简单的解决方案是将数据加载到任何SQL数据库(例如,SQLite,不需要运行完整的SQL服务器),将字典键加载为一个单独的字段(如果您不关心普通表单和c,则其余数据可能都在另一个字段中),并将传入的查询转换为SQL,大致如下:
  2. SQL表至少包含:

    CREATE TABLE my_data(
    dictkey text,
    data text);
    
    python_query="foo OR bar AND NOT gazonk"
    sql_keywords=["AND","NOT","OR"]
    sql_query=[]
    for word in python_query.split(" "):
        if word in sql_keywords:
            sql_query+=[ word ]
        else:
            sql_query+=["dictkey='%s'" % word]
    
    real_sql_query=" ".join(sql_query)
    

    这需要对SQL注入和特殊字符进行一些转义和控制检查,但一般情况下它只会将您的查询转换为SQL,当针对SQL数据库运行时,它将返回密钥(可能还有数据)以供进一步处理。

    1. 现在是纯Python版本。
    2. 您需要做的是分析您获得的字符串并将逻辑应用于现有的Python数据。

      分析字符串以将其减少为特定组件(及其交互)是parsing。如果你真的想要构建自己的完全成熟的解析器,那么就会有Python模块,但是,对于学校作业,我希望你的任务是建立自己的。

      根据您的说明,查询可以用准BNF form表示为:

      (<[NOT] word> <AND|OR>)...
      

      既然你说优先级与所有优先级无关,你可以通过简单的方式进行,并逐字解析。

      然后你必须将关键字与文件名相匹配,正如另一个答案中所提到的,这对于sets来说是最容易的。

      所以,它可能会像这样:

      import re
      
      query="foo OR bar AND NOT gazonk"
      
      result_set=set()
      operation=None
      
      for word in re.split(" +(AND|OR) +",query):
          #word will be in ['foo', 'OR', 'bar', 'AND', 'NOT gazonk']
      
          inverted=False # for "NOT word" operations
      
          if word in ['AND','OR']:
              operation=word
              continue
      
          if word.find('NOT ') == 0:
              if operation is 'OR':
              # generally "OR NOT" operation does not make sense, but if it does in your case, you 
              # should update this if() accordingly
                  continue
      
              inverted=True
              # the word is inverted!
              realword=word[4:]
          else:
              realword=word
      
          if operation is not None:
              # now we need to match the key and the filenames it contains:
              current_set=set(inverted_index[realword].keys())
      
              if operation is 'AND':
                  if inverted is True:
                      result_set -= current_set
                  else:
                      result_set &= current_set
              elif operation is 'OR':
                  result_set |= current_set
      
          operation=None
      
      print result_set
      

      请注意,这不是一个完整的解决方案(例如,它不包括处理查询的第一个术语,它要求布尔运算符为大写),并且未经过测试。但是,它应该主要用于向您展示如何进行操作。做更多就是为你写课程,这对你有害。因为您需要学习如何操作,以便您能够理解它。随意请求澄清。

答案 2 :(得分:0)

考虑到你有倒排索引,这是一个以testtry为键的字典,你可以定义以下函数并使用它们:

def intersection(list1, list2):
    return list(set(list1).intersection(list2))
def union(list1, list2):
    return list(set(list1).union(list2))
def notin(list1, list2)
    return [filter(lambda x: x not in list1, sublist) for sublist in list2]

intersection(inverted_index['people'].keys(), intersection(inverted_index['test'].keys(), inverted_index['try'].keys()))