散列大数据集和C实现

时间:2011-11-13 16:47:55

标签: c gcc data-structures hash

我有大量的值,范围从0到5463458053.对于每个值,我希望映射一个包含字符串的集合,以便操作查找,即。即查找该集合中是否存在字符串所花费的时间最少。请注意,这组值可能不包含(0 - 5463458053)中的所有值,但是可以包含大量值。

我目前的解决方案是散列这些值(介于0 - 5463458053之间),并且对于每个值,都有一个与该值对应的字符串的链接列表。每次,我想检查一个给定集合中的字符串,我散列该值(在0 - 5463458053之间),获取链接列表,并遍历它以查明它是否包含上述字符串。

虽然这可能看起来更容易,但这需要花费一些时间。你能想到更快的解决方案吗?此外,碰撞将是可怕的。它们会导致错误的结果。

另一部分是关于在C中实现这一点。我将如何做到这一点?

注意:有人建议使用数据库。我想知道这是否有用。

我有点担心自然会耗尽RAM。 : - )

5 个答案:

答案 0 :(得分:3)

您可以拥有哈希表的哈希表。第一个哈希表包含整数键。其中的值是散列集,即其键是字符串的散列表。

你也可以有一个散列集,其中键是整数和字符串对。

有许多库实现了这样的数据结构(在C ++中,标准库正在实现它们,如std::map& std::set)。对于C,我在想GTK的Glib

使用散列技术,内存使用与所考虑的集合(或关系)的大小成比例。例如,你可以接受30%的空虚率。

答案 1 :(得分:1)

如果条目从0到N且连续:使用数组。 (索引速度是否足够快?)

编辑:这些数字似乎不是连续的。有一个大数的{key,value}对,其中密钥是一个大数字(> 32位但<64位),值是一串字符串。

如果内存可用,哈希表很容易,如果一串字符串不是太大,你可以按顺序检查它们。如果相同的字符串多次出现(很多次),你可以枚举字符串(在char * array []中放入指针并使用索引到该数组中。找到给定字符串的索引可能涉及另一个哈希表)

对于“主”哈希表,条目可能是:

struct entry {
  struct entry *next; /* for overflow chain */
  unsigned long long key; /* the 33bits number */
  struct list *payload;
  } entries[big_enough_for_all] ; /* if size is known in advance
                          , preallocation avoids a lot of malloc overhead */

如果你有足够的内存来存储磁头阵列,你肯定会这样做:

struct entry *heads[SOME_SIZE] = {NULL, };

,否则您可以将头数组与条目数组合并。 (就像我在这里做Lookups on known set of integer keys

处理冲突很容易:当您走过溢出链时,只需将您的密钥与条目中的密钥进行比较即可。如果他们不平等:继续前进。如果他们是平等的:找到;现在去走吧。

答案 2 :(得分:1)

具有实现它的C库的Judy Array可能正是您所需要的基础。这是一个描述它的引用:

  

Judy是一个提供最先进核心技术的C库   实现稀疏动态数组。宣布Judy数组   只需使用空指针。 Judy数组仅在消耗内存时才消耗内存   已填充,但可以增长以利用所有可用内存   如果需要的话Judy的主要优势是可扩展性,高性能和高性能   记忆效率。 Judy数组是可扩展的,可以扩展到a   非常大量的元素,仅受机器内存的限制。以来   Judy被设计成一个无界数组,Judy数组的大小是   没有预先分配但随着数组动态增长和收缩   人口。 Judy将可扩展性与易用性相结合。 Judy API   可以通过简单的插入,检索和删除调用来访问   需要大量的编程。不需要调整和配置   (实际上甚至不可能)。另外,排序,搜索,计数和   顺序访问功能内置于Judy的设计中。

     只要开发人员需要动态大小的数组,就可以使用Judy,   关联数组或简单易用的接口,不需要   为扩张或收缩做的返工。

     

Judy可以替换许多常见的数据结构,例如数组,稀疏   数组,哈希表,B树,二叉树,线性列表,跳过列表,   其他排序和搜索算法,以及计数功能。

答案 3 :(得分:1)

大量字符串+快速查找+有限内存----&gt;你想要一个前缀trie,暴击位树或该家族的任何东西(非常类似的东西有许多不同的名字,例如PATRICIA ...... Judy也是这样的一个东西)。请参阅示例this

这些数据结构允许前缀压缩,因此它们能够非常有效地存储大量字符串(在某种程度上必然会有共同的前缀)。此外,查找速度非常快。由于常见的大O符号没有考虑的缓存和分页效应,它们可以比哈希快,甚至更快,只占内存的一小部分(即使根据big-O,除了数组之外什么都没有)可以打败哈希)。

答案 4 :(得分:1)

您可以使用单个二叉搜索树(AVL / Red-black / ...)从所有集合中包含所有字符串,方法是按字典顺序键入(set_number,string)。您无需在任何位置显式存储集。例如,定义树节点顺序的比较器可能如下所示:

function compare_nodes (node1, node2) {
    if (node1.set_number < node2.set_number) return LESS;
    if (node1.set_number > node2.set_number) return GREATER;
    if (node1.string < node2.string) return LESS;
    if (node1.string > node2.string) return GREATER;
    return EQUAL;
}

有了这样的结构,一些常见的操作是可能的(但可能不是直截了当的)。

要查找集合s中是否存在字符串set_number,只需在树中查找(set_numbers),即可获得完全匹配。

查找集合set_number中的所有字符串:

function iterate_all_strings_in_set (set_number) {
    // Traverse the tree from root downwards, looking for the given key. Return
    // wherever the search ends up, whether it found the value or not.
    node = lookup_tree_weak(set_number, "");

    // tree empty?
    if (node == null) {
        return;
    }

    // We may have gotten the greatest node from the previous set,
    // instead of the first node from the set we're interested in.
    if (node.set_number != set_number) {
        node = successor(node);
    }

    while (node != null && node.set_number == set_number) {
        do_something_with(node.string);
        node = successor(node);
    }
}

以上内容需要O((k+1)*log(n))时间,其中kset_number中的字符串数量,n是所有字符串的数量。

查找至少有一个相关字符串的所有集合编号:

function iterate_all_sets ()
{
    node = first_node_in_tree();

    while (node != null) {
        current_set = node.set_number;
        do_something_with(current_set);

        if (cannot increment current_set) {
            return;
        }

        node = lookup_tree_weak(current_set + 1, "");
        if (node.set_number == current_set) {
            node = successor(node);
        }
    }
}

以上内容需要O((k+1)*log(n))时间,其中k是至少包含一个字符串的集合数,n是所有字符串的数量。

请注意,上面的代码假定在“do_something”调用中未修改树;如果移除节点,它可能会崩溃。

Addidionally,这是一些使用my own generic AVL tree implemetation来演示这一点的真实C代码。要编译它,只需从BadVPN源中的某处复制misc/structure/文件夹,然后在那里添加包含路径。

注意我的AVL树在其节点中不包含任何“数据”,以及它如何不执行任何自己的内存分配。当您有大量数据可用时,这很方便。为了说清楚:下面的程序只有一个malloc(),它是分配节点数组的那个。

#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <assert.h>

#include <structure/BAVL.h>
#include <misc/offset.h>

struct value {
    uint32_t set_no;
    char str[3];
};

struct node {
    uint8_t is_used;
    struct value val;
    BAVLNode tree_node;
};

BAVL tree;

static int value_comparator (void *unused, void *vv1, void *vv2)
{
    struct value *v1 = vv1;
    struct value *v2 = vv2;

    if (v1->set_no < v2->set_no) {
        return -1;
    }
    if (v1->set_no > v2->set_no) {
        return 1;
    }

    int c = strcmp(v1->str, v2->str);
    if (c < 0) {
        return -1;
    }
    if (c > 0) {
        return 1;
    }
    return 0;
}

static void random_bytes (unsigned char *out, size_t n)
{
    while (n > 0) {
        *out = rand();
        out++;
        n--;
    }
}

static void random_value (struct value *out)
{
    random_bytes((unsigned char *)&out->set_no, sizeof(out->set_no));

    for (size_t i = 0; i < sizeof(out->str) - 1; i++) {
        out->str[i] = (uint8_t)32 + (rand() % 94);
    }
    out->str[sizeof(out->str) - 1] = '\0';
}

static struct node * find_node (const struct value *val)
{
    // find AVL tree node with an equal value
    BAVLNode *tn = BAVL_LookupExact(&tree, (void *)val);
    if (!tn) {
        return NULL;
    }

    // get node pointer from pointer to its value (same as container_of() in Linux kernel)
    struct node *n = UPPER_OBJECT(tn, struct node, tree_node);
    assert(n->val.set_no == val->set_no);
    assert(!strcmp(n->val.str, val->str));

    return n;
}

static struct node * lookup_weak (const struct value *v)
{
    BAVLNode *tn = BAVL_Lookup(&tree, (void *)v);
    if (!tn) {
        return NULL;
    }

    return UPPER_OBJECT(tn, struct node, tree_node);
}

static struct node * first_node (void)
{
    BAVLNode *tn = BAVL_GetFirst(&tree);
    if (!tn) {
        return NULL;
    }

    return UPPER_OBJECT(tn, struct node, tree_node);
}

static struct node * next_node (struct node *node)
{
    BAVLNode *tn = BAVL_GetNext(&tree, &node->tree_node);
    if (!tn) {
        return NULL;
    }

    return UPPER_OBJECT(tn, struct node, tree_node);
}

size_t num_found;

static void iterate_all_strings_in_set (uint32_t set_no)
{
    struct value v;
    v.set_no = set_no;
    v.str[0] = '\0';
    struct node *n = lookup_weak(&v);

    if (!n) {
        return;
    }

    if (n->val.set_no != set_no) {
        n = next_node(n);
    }

    while (n && n->val.set_no == set_no) {
        num_found++; // "do_something_with_string"
        n = next_node(n);
    }
}

static void iterate_all_sets (void)
{
    struct node *node = first_node();

    while (node) {
        uint32_t current_set = node->val.set_no;
        iterate_all_strings_in_set(current_set); // "do_something_with_set"

        if (current_set == UINT32_MAX) {
            return;
        }

        struct value v;
        v.set_no = current_set + 1;
        v.str[0] = '\0';
        node = lookup_weak(&v);

        if (node->val.set_no == current_set) {
            node = next_node(node);
        }
    }
}

int main (int argc, char *argv[])
{
    size_t num_nodes = 10000000;

    // init AVL tree, using:
    //   key=(struct node).val,
    //   comparator=value_comparator
    BAVL_Init(&tree, OFFSET_DIFF(struct node, val, tree_node), value_comparator, NULL);

    printf("Allocating...\n");

    // allocate nodes (missing overflow check...)
    struct node *nodes = malloc(num_nodes * sizeof(nodes[0]));
    if (!nodes) {
        printf("malloc failed!\n");
        return 1;
    }

    printf("Inserting %zu nodes...\n", num_nodes);

    size_t num_inserted = 0;

    // insert nodes, giving them random values
    for (size_t i = 0; i < num_nodes; i++) {
        struct node *n = &nodes[i];

        // choose random set number and string
        random_value(&n->val);

        // try inserting into AVL tree
        if (!BAVL_Insert(&tree, &n->tree_node, NULL)) {
            printf("Insert collision: (%"PRIu32", '%s') already exists!\n", n->val.set_no, n->val.str);
            n->is_used = 0;
            continue;
        }

        n->is_used = 1;
        num_inserted++;
    }

    printf("Looking up...\n");

    // lookup all those values
    for (size_t i = 0; i < num_nodes; i++) {
        struct node *n = &nodes[i];
        struct node *lookup_n = find_node(&n->val);

        if (n->is_used) { // this node is the only one with this value

            ASSERT(lookup_n == n)
        } else { // this node was an insert collision; some other
                 // node must have this value
            ASSERT(lookup_n != NULL) 
            ASSERT(lookup_n != n)
        }
    }

    printf("Iterating by sets...\n");

    num_found = 0;
    iterate_all_sets();
    ASSERT(num_found == num_inserted)

    printf("Removing all strings...\n");

    for (size_t i = 0; i < num_nodes; i++) {
        struct node *n = &nodes[i];
        if (!n->is_used) { // must not remove it it wasn't inserted
            continue;
        }
        BAVL_Remove(&tree, &n->tree_node);
    }

    return 0;
}