【问题标题】:Doing exact search across multiple fields跨多个字段进行精确搜索
【发布时间】:2019-08-18 07:20:28
【问题描述】:

假设我有搜索词:

"Terminator 2 US" (search terms can be in any order)

而我的数据如下:

movie                   country     year
Terminator 2            US          1990
Avatar                  CA          2000
Terminator 2            GB          1990
2001: A Space Odyssey   US          1980

我应该得到的结果只是第一行,基于 movie=Terminator 2 的完全匹配和 country=US 的完全匹配。

但是,如果我要按术语分解所有内容并根据确切值比较每个内容。我会得到以下信息:

terminator ==> miss
2 ==> miss
us ==> match

这是否意味着进行搜索,我需要创建以下三种搜索模式来检查:

MATCH "terminator" AND MATCH "2" AND MATCH "US"
OR
MATCH "terminator 2" AND MATCH "US"
OR
MATCH "terminator 2 US"

或者,进行这种多字段搜索的最佳方式是什么?例如,如果我在搜索词中有 100 个单词怎么办?例如,这是搜索中的常见模式还是我可能遗漏了什么?

for row in row:
    if  (row['movie'] == "Terminator 2 US") \
  or (row['movie'] == "Terminator 2") and row['country'] == 'US' \
  or (row['movie'] == "Terminator") and row['country'] == '2 US' \
  or (row['movie'] == "US") and row['country'] == 'Terminator 2' \
  or (row['movie'] == "Terminator 2") and row['year'] == 'US' \
  or (row['movie'] == "Terminator") and row['year'] == '2 US' \
  or (row['movie'] == "US") and row['year'] == 'Terminator 2' \
  # etc...

【问题讨论】:

    标签: python algorithm search


    【解决方案1】:

    如果您要提供体面的、基于相关性的搜索,我强烈建议您查看现有的专用技术,例如 ElasticsearchSolr(均基于 Lucene,所以他们它们提供的与实际搜索相关的功能相似)。这些将为您提供一个工具包,允许您配置和调整您的字段和相关性分数的处理方式。

    解决您遇到的问题的常用方法是将所有内容复制到一个公共字段中,以便您只匹配该字段,或者分别查询每个字段的每个令牌。我会给你一个使用后者的例子,并尝试将实现与 Lucene 中的相同概念联系起来。

    我将使用术语“tokenize”,意思是如何将文本分成单独的部分。然后将这些标记相互匹配以查看该字段是否包含命中。在此示例中,空格标记器可以正常工作,它将输入分解为空格 (' ') 上的单独标记。还有其他一些行为不同的标记器可用,例如也可以在 : 上拆分 - 如果您希望在用户搜索 2001 时获得成功,这将与您的第四个文档相关。

    下面的示例是一种蛮力方法,实际上,随着文档数量的增长,您必须维护一个正确的排序索引(和倒排索引),其中包含所有内容(令牌),并且您仍然希望高效搜索和评分(这使您可以说在title 中的点击比在year 中更重要等)。

    data = [
    {
        'title': 'Terminator 2',
        'country': 'US',
        'year': '1990',
    }, {
        'title': 'Avatar',
        'country': 'CA',
        'year': '2000',
    }, {
        'title': 'Terminator 2',
        'country': 'GB',
        'year': '1990',
    }, {
        'title': '2001: A Space Odyssey',
        'country': 'US',
        'year': '1980',
    }]
    
    query = 'Terminator 2 US'
    
    def match_token_for_document(token, document):
        # any splitting and filtering (lower()) would only be performed
        # when a document is indexed in a proper solution, not for each
        # query
        for field in doc:
            for doc_token in doc[field].split():
                # A lowercase filter in a proper document search engine
                if doc_token.lower() == token:
                    return True
    
        return False
    
    for doc in data:
        hits = True
    
        # This would be a whitespace tokenizer in ES/Solr
        for token in query.split():
            if not match_token_for_document(token.lower(), doc):
                hits = False
                break
    
        if hits:
            print("Match:")
            print(doc)
    

    输出是终结者2(美国):

    Match:
    {'country': 'US', 'year': '1990', 'title': 'Terminator 2'}
    

    【讨论】:

    • 感谢您的解释和示例。用于此的所有技术都需要在浏览器中——在 javascript 或 c++(webassembly)中,所以我们不能真正使用外部产品(我不认为)。即使搜索速度提高 1000 倍——传输 10k 文档的网络也会使其更慢(所有行都像 Excel 一样显示,而不是分页到一小部分结果)。鉴于此,您会建议什么方法?
    • 另外,现在评分/排序并不重要,只是匹配与否。
    • 只要很少进行搜索,迭代解决方案就可以正常工作。根据行的大小,将已经标记化和小写的版本添加为公共列可能是一个好主意;创建新值行或加载文档时不需要太多处理。
    【解决方案2】:

    您可以将查询和行值拆分为集合并检查它们是否匹配,或者在部分查询的情况下查询集是否是值的子集

    q = 'Terminator 2 US'
    # or
    q = 'Terminator 2 US 1990'
    # or
    q = 'Terminator 2 1990 US'
    
    for row in rows:
        row_values = ' '.join(row.values())
        values_set = set(row_values.split(' '))
        query_set = set(q.split(' '))
        if values_set == query_set or values_set.issuperset(query_set):
            matches.append(row)
    

    【讨论】:

    • 是的,这是上面的一个简单示例。我的意思是想象有很多领域,它不会总是按照这个顺序。我可以更新问题。
    • 谢谢。在这个例子中“行”是什么。这是字典列表吗?
    • 这个方案的性能和上面的差不多吗?只是好奇可能会有什么不同。
    猜你喜欢
    • 1970-01-01
    • 2012-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多