基于多个字段搜索大型数据集的有效方法

Posted

技术标签:

【中文标题】基于多个字段搜索大型数据集的有效方法【英文标题】:Efficient way to search large data set based on several fields 【发布时间】:2013-10-30 18:45:45 【问题描述】:

我想知道基于不同字段搜索大型数据集的最佳方法是什么。 例如,Person 对象定义如下:

Person:
    first name
    last name
    phone numbers

我有 100k 个 Person 类型的对象,我想根据任何字段搜索特定的人?

我尝试使用不同的字段对数据集进行排序,以便可以在 O(logn) 时间内执行搜索操作,但我知道这不是正确的方法。

【问题讨论】:

你可以试试Boost.MultiIndex: "Boost Multi-index Containers Library 提供了一个名为 multi_index_container 的类模板,它可以构建维护一个或多个具有不同排序和访问语义的索引的容器" 感谢您的回复。我可以轻松使用这个库,但我想开发自己的解决方案。 你使用什么样的流程来构建你的结构? insert_all->build_once->search 场景是否适合您的需求? IE。你需要频繁插入/删除吗? 会批量插入,然后根据任何字段进行大量搜索。 【参考方案1】:

对此没有一个答案,因为正确答案(很大程度上)取决于您对速度与额外存储的关心程度。

如果您想要绝对的最大速度,并且根本不关心使用额外的存储空间,是的,您可以创建三个数据副本,按每个字段排序,当输入搜索时,只需使用适当的一。这可能不像最初出现的那样糟糕。假设您的字符串平均每个大约 10 个字节,因此结构的总大小约为 30 个字节。其中 100'000 份大约为每个副本 3 兆字节,总计约 9 兆字节。有一次,这显然是令人望而却步的——但现在一台典型的机器现在至少有 8 GB 的 RAM,它并没有那么糟糕。

假设您排除了这一点,下一个最明显的可能性是在原始数据中建立索引——将原始数据放入一个数组中,然后为每个字段建立一个索引,其中索引中的每个条目都包含数据对于一个字段,以及指向主要数据的指针/下标。每个索引条目可以是 ~14 字节,因此每个索引大约是整体数据大小的一半。只有三个字段不会节省很多,但确实节省了一些 - 并且以非常最小的复杂性成本。有了更多字段,您将节省更多。

另一种可能性是将索引实现为哈希表。这里的主要优点是您可以避免重复存储日期。例如,如果您计算一个 16 位散列,每个存储桶有 2 个条目,则可以将一个索引存储在 ~512K 字节中。如果存储桶已满,但没有一个条目与您的输入匹配,则您重新散列并尝试另一个存储桶。继续前进,直到找到您的物品或找到一个空桶。

【讨论】:

+1 用于探索替代方案。为什么第二种情况下的索引条目约为 14 个字节?每个索引条目只需要一个指向数据的指针(4 或 8 个字节,取决于 32 或 64 位),并且每个索引都有一个单独的比较函数。它只是使用适当的比较函数调用binary_search @JimMischel:在第二种情况下,我想到的至少是一个包含“关键”数据副本的索引,以及指向相应记录的索引/指针。当然也有可能以正确的顺序拥有一组指针/索引——在速度和内存使用之间进行另一个权衡(更多的间接性通常会降低速度)。 感谢您的回复。因为,我已经完成了第一部分,我一定会尝试你的第二和第三个方法【参考方案2】:

你可以试试Boost.MultiIndex:

Boost Multi-index Containers Library 提供了一个名为 multi_index_container 的类模板,它可以构建维护一个或多个具有不同排序和访问语义的索引的容器:


但是,如果您想亲自尝试,那么最简单的解决方案之一是:为所有数据使用一个容器,此外还维护多个具有适当索引的地图:

class Indixer

    vector<Record> values; // without specific order
    unordered_map<field_type1, Record*> index1; // Search: O(1) average
    unordered_map<field_type2, Record*> index2; // Search: O(1) average
    map<field_type3, Record*> index3; // Search: O(log N) worst case
public:
    // ...
;

您可以使用std::unordered_map 来获得 O(1) 平均访问权限。这是一个例子:

#include <initializer_list>
#include <unordered_map>
#include <functional>
#include <algorithm>
#include <iostream>
#include <iterator>
#include <utility>
#include <vector>
#include <string>
using namespace std;

struct Record

    string first_name, last_name;
;

class Indexer

    typedef vector<Record> Container;
    typedef Record *Handle;
    Container values;
    unordered_map<string, Handle> first_name_index, last_name_index;

public:
    Indexer(Container &&x) : values(move(x))
    
        for(auto &x : values)
        
            first_name_index[x.first_name] = &x;
            last_name_index[x.last_name] = &x;
        
    
    const Record &first_name(const string &x)
    
        return *first_name_index[x];
    
    const Record &last_name(const string &x)
    
        return *last_name_index[x];
    
;

int main()

    vector<Record> v = "F1", "L1", "F2", "L2";
    Indexer x(move(v));

    cout << x.first_name("F1").last_name << endl;
    cout << x.first_name("F2").last_name << endl;

    cout << x.last_name("L1").first_name << endl;
    cout << x.last_name("L2").first_name << endl;

输出是:

L1
L2
F1
F2

Live Demo on Coliru

【讨论】:

以上是关于基于多个字段搜索大型数据集的有效方法的主要内容,如果未能解决你的问题,请参考以下文章

在 R 中拆分大型数据集的有效方法

(预)处理存储在 json 中的大型数据集的最有效方法是啥?

在 phpMyAdmin SQL 表中存储大型数据集的有效方法

在 Java Spark 中迭代大型数据集的最快且有效的方法

从大型数据集的数据框有效地创建矩阵

计算大型数据集的python树高度