部分多键映射的数据结构?

时间:2013-09-01 07:55:55

标签: algorithm data-structures

我的数据由映射到值的键组成,如下所示:

---------------------
Key          | Value
---------------------
(0, 0, 0, 0) | a
(0, 0, 0, 1) | b
(0, 1, 0, 1) | c
(0, 1, 1, 0) | d
....

我正在寻找一种能够有效地通过密钥执行搜索查询的数据结构,其中查询可以是完整的或部分地指定密钥。例如:

(0, 0, 0, 1) -> a
(0, *, *, *) -> [a, b, c, d]
(0, 1, *, *) -> [c, d]

我现在的想法是使用常规树来实现它,类似于: tree 叶节点表示值,非叶节点是密钥的一部分(即,w,x,y和z节点分别是密钥的第一,第二,第三和第四部分)。可以使用简单的BFS算法来回答任何查询。但问题是这棵树随着密钥的每个新部分呈指数级增长。

哪种数据结构/算法更适合解决此问题?请注意,关键部分可以是数字或字符串。

3 个答案:

答案 0 :(得分:5)

一个数组。对真的!你将没有空间开销,没有“指针追逐”开销,计算索引只需要一点点,处理器真的很擅长。

假设您获得的部分密钥为maskbits,其中mask的通配符为0,其他位为1,bits为0表示通配符以及非通配符所需的任何内容。

收集具有与该模式匹配的键的所有项目的算法是:

int key = bits;
do {
    yield items[key];
    key = (key | mask) + 1 & ~mask | bits;
} while (key != bits);

key = (key | mask) + 1 & ~mask | bits部分看起来很有趣,这是它的工作原理。

|(按位OR)使所有非通配符为1.这确保了增量继续传递非通配符的位。在添加之后,应该被“修复”的位被破坏(如果进位通过它们则为0,否则为1),因此必须将它们屏蔽掉(& ~mask)然后重新设置为正确的价值(| bits)。运算符的优先级使得它可以在没有括号的情况下编写。你也可以把它写成

key = (((key | mask) + 1) & (~mask)) | bits;

这适用于任何类型的模式。如果您只需要“最后x位可变”,则可以优化位:

int wildcards = 0;
int invmask = ~mask;
do {
    yield items[wildcards++ | bits];
} while (wildcards & invmask);

它只从0到2 通配符数运行,然后放入顶部的固定位。

非二进制密钥

在最简单的非二进制情况下,密钥的部分仍然是一些整数位,即它们的范围从0到2 n -1。在这种情况下,您可以使用完全相同的代码,但掩码的解释是不同的:不是通配符使用单个0位,也不是通配符使用单个1位,具有一些其他位数(对应于键部分的位宽)。

对于非权力之二,需要更多的诡计。问题是为了满足关键部分小于某个值的约束,必须尽快生成进位。

例如,如果所有关键部分都可以是0,1或2(但不是3),则可以执行(未测试):

int key = bits;
int increment = (0x55555555 & ~mask) + 1;
do {
    yield items[key];
    int temp = (key | mask) + increment & ~mask;
    int fix = (temp | (temp >> 1)) & 0x55555555;
    key = temp - fix | bits;
} while (key != bits);

额外的increment是1加上“最近2的幂与一个关键部分的最大值之差”的掩码,在这种情况下每个关键部分为1,所以有每个“槽”中有1个(槽是2位宽,在这种情况下它们可以是最窄的)。它只是在通配符位置有那些“抵消”。

偏移关键部件,使其最高允许值映射到“全部”,确保进位传播通过它们。但是,这意味着它们通常处于无效状态(除非它接收到进位并且变为零)。然后是烦人的部分:对于未包装为零的关键部分,偏移量必须仅撤消

所以有fix进来。它计算一个非零的关键部分的掩码。如果关键部件更宽,则会变得更加烦人,如果关键部件的尺寸不同,则会变得非常糟糕。

然后最后一部分key = temp - fix | bits撤消偏移并将非通配符重新放入。该减法从不销毁任何东西,因为只从1位的组中减去1只至少1,因此,携带永远不会留下关键部分。

这种索引方式当然会浪费一些空间,不像两个幂的情况,因为数组中有“漏洞”,你永远无法索引。

答案 1 :(得分:1)

如果密钥的每个部分都存在最大(M)值,则可以通过将密钥解释为在基础M(或混合基础)中写入的数字来创建单个密钥树

  • 我假设通配符只出现在一个索引处,而所有的都是通配符,这样(x,*,*,*)将成为(x*M^3,(x+1)*M^3-1)的查询

表示字符串:

  • 您可以使用分隔字符和令牌粘贴密钥(使用|

('ax','bc','a','x') -> 'ax|bc|a|x'

分隔符不应出现在输入字符串中(可能会出现,但在这种情况下,它可能会干扰访问询问的结果)

但是......如果您的情况是difficult,您可以将对象用作keys,在java中我会为密钥创建class,并在它们之间定义一个比较oparator < / p>

我引用的例子: How to compare objects by multiple fields

答案 2 :(得分:1)

将每个关系编码为一行文本然后使用正则表达式'。'可以匹配键中该位置的任何单个字符。

这将消除对不关心的地方的任何限制。

这是一些Python:

>>> import re
>>> 
>>> map = '''
... 0000 a
... 0001 b
... 0101 c
... 0110 d
... '''
>>> 
>>> def search(query='0001'):
...     matches = re.findall(query + r' .', map)
...     return [match.split()[-1] for match in matches]
... 
>>> search('0001')
['b']
>>> search('0...')
['a', 'b', 'c', 'd']
>>> search('01..')
['c', 'd']
>>>