Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/70.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C 最适合基于前缀的搜索的数据结构_C_Regex_Algorithm_Data Structures_Hash - Fatal编程技术网

C 最适合基于前缀的搜索的数据结构

C 最适合基于前缀的搜索的数据结构,c,regex,algorithm,data-structures,hash,C,Regex,Algorithm,Data Structures,Hash,我必须维护一个键值对的内存数据结构。我有以下限制: 键和值都是长度为256和1024的文本字符串 分别地任何键通常看起来像k1k2k3k4k5,每个k(i)本身就是4-8字节的字符串 内存中的数据结构应尽可能具有连续内存。我有400 MB的键值对,允许分配120%的值。(元数据额外增加20%,仅在需要时。) DS将进行以下操作: 添加[不频繁操作]:典型的签名看起来像void Add_kv(void*ds,char*key,char*value) 删除[不频繁操作]:典型的签名看起来像void

我必须维护一个键值对的内存数据结构。我有以下限制:

  • 键和值都是长度为256和1024的文本字符串 分别地任何键通常看起来像k1k2k3k4k5,每个k(i)本身就是4-8字节的字符串
  • 内存中的数据结构应尽可能具有连续内存。我有400 MB的键值对,允许分配120%的值。(元数据额外增加20%,仅在需要时。)
  • DS将进行以下操作:
  • 添加[不频繁操作]:典型的签名看起来像
    void Add_kv(void*ds,char*key,char*value)
  • 删除[不频繁操作]:典型的签名看起来像
    void del_kv(void*ds,char*key)
  • 查找[最频繁的操作]:典型的签名看起来像
    char*LookUp(void*ds,char*key)
  • 迭代[最频繁的操作]:此操作基于前缀。它分配一个迭代器,即迭代整个DS并返回与前缀_key匹配的键值列表(例如,“k1k2k3.*”,k(i)如上定义)。每次迭代都在此迭代器(列表)上迭代。释放迭代器将释放列表。通常期望迭代器在400 MB DS(100KB:400 MB::1:4000)中返回100 KB的键值对。典型的签名看起来像
    void*iterate(void*ds,char*prefix\u key)
  • 项目符号6和项目符号7是最频繁的操作,需要对其进行优化
  • 我的问题是什么是最适合上述约束的数据结构


    我已经考虑过了。添加/删除/查找可以在o(1)中完成,因为我有足够的内存,但它不是迭代的最佳选择。散列的散列(先在k1上散列,然后在k2上散列,然后在k3上散列…)或散列数组可以完成,但它违反了项目符号2。我还有什么其他选择?

    我可能会使用类似于B+树的东西来实现这一点:

    由于内存效率对您很重要,当一个叶块满时,您应该尽可能在几个块之间重新分配键,以确保块始终>=85%满。块大小应足够大,以便内部节点的开销仅为几%

    您还可以优化叶块中的存储,因为块中的大多数键都有一个很长的公共前缀,您可以从更高级别的块中找出它。因此,您可以从叶块中的键中删除公共前缀的所有副本,400MB的键值对占用的RAM将大大少于400MB。这将使插入过程稍微复杂化


    为了进一步压缩此结构,您还可以做其他事情,但这会很快变得复杂,而且听起来您并不需要它。

    我会将此实现为一个哈希表用于查找,并为您的迭代实现一个单独的哈希表。我认为尝试将这些独立的键段转换成整数,就像你要求的那样,是一堆不必要的工作

    已经有很多很好的C哈希表实现可用,所以我就不赘述了

    要为迭代创建反向索引,请创建N个哈希表,其中N是键段数。然后,对于每个键,将其分解为单独的段,并将该值的条目添加到相应的哈希表中。因此,如果您有“abcxyzqgx”键,其中:

    然后在k1哈希表中添加一个条目“abc=abcxyzqgx”。在k2哈希表中添加一个条目“xyz=abcxyzqgx”。在k3哈希表中添加“qgx=abcxyzqgx”。(当然,这些值不是字符串键本身,而是对字符串键的引用。否则,您将有O(nk)256个字符串。)

    完成后,哈希表中的每个表都有唯一的段值作为键,这些值是这些段所在的键列表

    如果要查找所有具有k1=abc和k3=qgx的键,请查询k1哈希表以获取包含abc的键列表,查询k3哈希表以获取包含qgx的键列表。然后将这两个列表相交以获得结果

    构建各个散列表是O(nk)的一次性成本,其中n是键的总数,k是键段的数目。内存需求也是O(nk)。当然,这有点贵,但你说的总共只有160万把钥匙

    迭代的情况是O(m*x),其中m是单个键段引用的键的平均数,x是查询中的键段数

    一个明显的优化是在查找之前放置一个LRU缓存,以便从缓存中提供频繁的查询


    另一种可能的优化是创建组合键的附加索引。例如,如果查询经常要求k1和k2,并且可能的组合相当小,那么使用组合的k1k2缓存是有意义的。因此,如果有人搜索k1=abc和k2=xyz,那么您有一个包含“abcxyz=[密钥列表]”的k1k2缓存。

    我将使用五个并行哈希表,对应于可能搜索的五个前缀。每个哈希表槽将包含零个或多个引用,每个引用包含该特定键值对的前缀长度、该键值前缀的哈希以及指向实际键和数据结构的指针

    对于删除,实际的键和数据结构将包含所有五个前缀长度和相应的哈希,以及键和值的字符数据

    例如:

    #define  PARTS  5
    
    struct hashitem {
        size_t            hash[PARTS];
        size_t            hlen[PARTS];
        char             *data;
        char              key[];
    };
    
    struct hashref {
        size_t            hash;
        size_t            hlen;
        struct hashitem  *item;
    };
    
    struct hashrefs {
        size_t            size;
        size_t            used;
        struct hashref    ref[];
    };
    
    struct hashtable {
        size_t            size[PARTS];
        struct hashrefs **slot[PARTS];
    };
    
    struct hashitem
    中,如果
    key
    k1k3k4k5
    ,那么
    hlen[0]=2
    hash[0]=hash(“k1”)
    hlen[1]=4
    hash[1]=hash(“k1k2k2”)
    ,等等,直到
    hlen[4]=10
    hash[4]=hash(“k3k5”)

    插入新的键值对时,首先要找出前缀长度(
    hlen[]
    #define  PARTS  5
    
    struct hashitem {
        size_t            hash[PARTS];
        size_t            hlen[PARTS];
        char             *data;
        char              key[];
    };
    
    struct hashref {
        size_t            hash;
        size_t            hlen;
        struct hashitem  *item;
    };
    
    struct hashrefs {
        size_t            size;
        size_t            used;
        struct hashref    ref[];
    };
    
    struct hashtable {
        size_t            size[PARTS];
        struct hashrefs **slot[PARTS];
    };
    
    static int insert_pair(struct hashtable *ht,
                           const char       *key,
                           const size_t      hash[PARTS],
                           const size_t      hlen[PARTS],
                           const char       *data,
                           const size_t      datalen)
    {
        struct hashitem *item;
        size_t           p, i;
    
        /* Verify the key is not already in the
           hash table. */
    
        /* Allocate 'item', and copy 'key', 'data',
           'hash', and 'hlen' to it. */
    
        for (p = 0; p < PARTS; p++) {
            i = hash[p] % ht->size[p];
    
            if (!ht->entry[i]) {
                /* Allocate a new hashrefs array,
                   with size=1 or greater, initialize
                   used=0 */
            } else
            if (ht->entry[i].used >= ht->entry[i].size) {
                /* Reallocate ht->entry[i] with
                   size=used+1 or greater */
            }
    
            ht->entry[i].ref[ht->entry[i].used].hash = hash[p];
            ht->entry[i].ref[ht->entry[i].used].hlen = plen[p];
            ht->entry[i].ref[ht->entry[i].used].item = item;
    
            ht->entry[i].used++;
        }
    
        return 0; /* Success, no errors */
    }
    
    int lookup_filter(struct hashtable *ht,
                      const size_t      hash,
                      const size_t      hashlen,
                      const size_t      parts, /* 0 to PARTS-1 */
                      const char       *key,
                      int (*func)(struct hashitem *, void *),
                      void             *custom)
    {
        const struct hashrefs *refs = ht->entry[parts][hash % ht->size[parts]];
        int                    retval = -1; /* None found */
        size_t                 i;
    
        if (!refs)
            return retval;
    
        for (i = 0; i < refs->used; i++)
            if (refs->ref[i].hash == hash &&
                refs->ref[i].hlen == hashlen &&
                !strncmp(refs->ref[i].item->key, key, hashlen)) {
                if (func) {
                    retval = func(refs->ref[i].item, custom);
                    if (retval)
                        return retval;
                } else
                    retval = 0;
            }
    
        return retval;
    }
    
    struct hashitem *lookup(struct hashtable *ht,
                            const size_t      hash,
                            const size_t      hashlen,
                            const char       *key)
    {
        const struct hashrefs *refs = ht->entry[PARTS-1][hash % ht->size[PARTS-1]];
        size_t                 i;
    
        if (!refs)
            return NULL;
    
        for (i = 0; i < refs->used; i++)
            if (refs->ref[i].hash == hash &&
                refs->ref[i].hlen == hashlen &&
                !strncmp(refs->ref[i].item->key, key, hashlen))
                return refs->ref[i].item;
    
        return NULL;
    }