Performance 仅筛选包含给定数字集的数字序列
我有一个像这样的数字字符串的大列表。单个字符串相对较短(比如少于50位) 我需要找到一种高效的数据结构(速度第一,内存第二)和算法,它只返回由给定数字集组成的字符串 示例结果:Performance 仅筛选包含给定数字集的数字序列,performance,algorithm,data-structures,filtering,lookup,Performance,Algorithm,Data Structures,Filtering,Lookup,我有一个像这样的数字字符串的大列表。单个字符串相对较短(比如少于50位) 我需要找到一种高效的数据结构(速度第一,内存第二)和算法,它只返回由给定数字集组成的字符串 示例结果: filter(data, [0,3,4]) = ['300303334'] filter(data, [0,1,2,3,4,5]) = ['300303334', '53210234'] 数据列表通常会放入内存。考虑一个函数f,如果字符串中有数字i,它会为每个字符串构造一个位掩码,并设置位i 比如说, f('0')
filter(data, [0,3,4]) = ['300303334']
filter(data, [0,1,2,3,4,5]) = ['300303334', '53210234']
数据列表通常会放入内存。考虑一个函数f,如果字符串中有数字i,它会为每个字符串构造一个位掩码,并设置位i 比如说,
f('0') = 0b0000000001
f('00') = 0b0000000001
f('1') = 0b0000000010
f('1100') = 0b0000000011
Bitmask 0b0000000001 -> ['0','00']
然后我建议为每个位掩码存储一个字符串列表
比如说,
f('0') = 0b0000000001
f('00') = 0b0000000001
f('1') = 0b0000000010
f('1100') = 0b0000000011
Bitmask 0b0000000001 -> ['0','00']
准备好此数据结构(与原始列表大小相同)后,就可以通过访问所有列表轻松访问特定筛选器的所有字符串,其中位掩码是筛选器中数字的子集
因此,对于过滤器[0,3,4]的示例,您可以从以下位置返回列表:
Strings containing just 0
Strings containing just 3
Strings containing just 4
Strings containing 0 and 3
Strings containing 0 and 4
Strings containing 3 and 4
Strings containing 0 and 3 and 4
Python代码示例
从集合导入defaultdict
进口itertools
原始数据=[
'300303334',
'53210234',
'123456789',
'5374576807063874'
]
def预处理(原始数据):
数据=默认DICT(列表)
对于原始数据中的s:
位掩码=0
对于s中的数字:
位掩码|=1对于每个数字,预计算不包含该数字的帖子列表
postings = [[] for _ in xrange(10)]
for i, d in enumerate(data):
for j in xrange(10):
digit = str(j)
if digit not in d:
postings[j].append(i)
现在,要查找所有只包含数字[1,3,5]的字符串,您可以合并其他数字的帖子列表(即:0,2,4,6,7,8,9)
def intersect_过账(p0,p1):
i0,i1=下一个(p0),下一个(p1)
尽管如此:
如果i0==i1:
产量i0
i0,i1=下一个(p0),下一个(p1)
elif i0
字符串可以用10位数字编码。有2^10或1024个可能的值
因此,创建一个字典,使用整数作为键,使用字符串列表作为值
计算每个字符串的值,并将该字符串添加到该值的字符串列表中
总体思路:
Dictionary Lookup;
for each (string in list)
value = 0;
for each character in string
set bit N in value, where N is the character (0-9)
Lookup[value] += string // adds string to list for this value in dictionary
然后,要获得符合条件的字符串列表,只需计算该值并进行直接字典查找
因此,如果用户要求仅包含3、5和7的字符串:
value = (1 << 3) || (1 << 5) || (1 << 7);
list = Lookup[value];
value=(1只是为了好玩,我编写了一个可爱的算法,如果有人想玩它,Perl就在这里。请不要把这当作一个答案或任何东西,把所有的功劳都传给Jim:
#!/usr/bin/perl
use strict;
use warnings;
my $Debug=1;
my $Nwords=1000;
my ($word,$N,$value,$i,$j,$k);
my (@dictionary,%Lookup);
################################################################################
# Generate "words" with random number of characters 5-30
################################################################################
print "DEBUG: Generating $Nwords word dictionary\n" if $Debug;
for($i=0;$i<$Nwords;$i++){
$j = rand(25) + 5; # length of this word
$word="";
for($k=0;$k<$j;$k++){
$word = $word . int(rand(10));
}
$dictionary[$i]=$word;
print "$word\n" if $Debug;
}
# Add some obvious test cases
$dictionary[++$i]="0" x 50;
$dictionary[++$i]="1" x 50;
$dictionary[++$i]="2" x 50;
$dictionary[++$i]="3" x 50;
$dictionary[++$i]="4" x 50;
$dictionary[++$i]="5" x 50;
$dictionary[++$i]="6" x 50;
$dictionary[++$i]="7" x 50;
$dictionary[++$i]="8" x 50;
$dictionary[++$i]="9" x 50;
$dictionary[++$i]="0123456789";
################################################################################
# Encode words
################################################################################
for $word (@dictionary){
$value=0;
for($i=0;$i<length($word);$i++){
$N=substr($word,$i,1);
$value |= 1 << $N;
}
push(@{$Lookup{$value}},$word);
print "DEBUG: $word encoded as $value\n" if $Debug;
}
################################################################################
# Do lookups
################################################################################
while(1){
print "Enter permitted digits, separated with commas: ";
my $line=<STDIN>;
my @digits=split(",",$line);
$value=0;
for my $d (@digits){
$value |= 1<<$d;
}
print "Value: $value\n";
print join(", ",@{$Lookup{$value}}),"\n\n" if defined $Lookup{$value};
}
!/usr/bin/perl
严格使用;
使用警告;
我的$Debug=1;
我的$Nwords=1000;
我的($word,$N,$value,$i,$j,$k);
我的(@dictionary,%Lookup);
################################################################################
#生成随机字符数为5-30的“单词”
################################################################################
如果$DEBUG,则打印“调试:生成$Nwords单词词典\n”;
对于($i=0;$i我喜欢的方法。它具有非常高效的查找和有限的内存使用。C代码如下:
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <readline/readline.h>
#include <readline/history.h>
enum {
zero = '0',
nine = '9',
numbers = nine - zero + 1,
masks = 1 << numbers,
};
typedef uint16_t mask;
struct list {
char *s;
struct list *next;
};
typedef struct list list_cell;
typedef struct list *list;
static inline int is_digit(char c) { return c >= zero && c <= nine; }
static inline mask char2mask(char c) { return 1 << (c - zero); }
static inline mask add_char2mask(mask m, char c) {
return m | (is_digit(c) ? char2mask(c) : 0);
}
static inline int is_set(mask m, mask n) { return (m & n) != 0; }
static inline int is_set_char(mask m, char c) { return is_set(m, char2mask(c)); }
static inline int is_submask(mask sub, mask m) { return (sub & m) == sub; }
static inline char *sprint_mask(char buf[11], mask m) {
char *s = buf;
char i;
for(i = zero; i <= nine; i++)
if(is_set_char(m, i)) *s++ = i;
*s = 0;
return buf;
}
static inline mask get_mask(char *s) {
mask m=0;
for(; *s; s++)
m = add_char2mask(m, *s);
return m;
}
static inline int is_empty(list l) { return !l; }
static inline list insert(list *l, char *s) {
list cell = (list)malloc(sizeof(list_cell));
cell->s = s;
cell->next = *l;
return *l = cell;
}
static void *foreach(void *f(char *, void *), list l, void *init) {
for(; !is_empty(l); l = l->next)
init = f(l->s, init);
return init;
}
struct printer_state {
int first;
FILE *f;
};
static void *prin_list_member(char *s, void *data) {
struct printer_state *st = (struct printer_state *)data;
if(st->first) {
fputs(", ", st->f);
} else
st->first = 1;
fputs(s, st->f);
return data;
}
static void print_list(list l) {
struct printer_state st = {.first = 0, .f = stdout};
foreach(prin_list_member, l, (void *)&st);
putchar('\n');
}
static list *init_lu(void) { return (list *)calloc(sizeof(list), masks); }
static list *insert2lu(list lu[masks], char *s) {
mask i, m = get_mask(s);
if(m) // skip string without any number
for(i = m; i < masks; i++)
if(is_submask(m, i))
insert(lu+i, s);
return lu;
}
int usage(const char *name) {
fprintf(stderr, "Usage: %s filename\n", name);
return EXIT_FAILURE;
}
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
static inline void chomp(char *s) { if( (s = strchr(s, '\n')) ) *s = '\0'; }
list *load_file(FILE *f) {
char *line = NULL;
size_t len = 0;
ssize_t read;
list *lu = init_lu();
for(; (read = getline(&line, &len, f)) != -1; line = NULL) {
chomp(line);
insert2lu(lu, line);
}
return lu;
}
void read_reqs(list *lu) {
char *line;
char buf[11];
for(; (line = readline("> ")); free(line))
if(*line) {
add_history(line);
mask m = get_mask(line);
printf("mask: %s\nstrings: ", sprint_mask(buf, m));
print_list(lu[m]);
};
putchar('\n');
}
int main(int argc, const char* argv[] ) {
const char *name = argv[0];
FILE *f;
list *lu;
if(argc != 2) return usage(name);
f = fopen(argv[1], "r");
if(!f) handle_error("open");
lu = load_file(f);
fclose(f);
read_reqs(lu);
return EXIT_SUCCESS;
}
测试和运行:
$ cat data.txt
300303334
53210234
123456789
5374576807063874
$ ./digitfilter data.txt
> 034
mask: 034
strings: 300303334
> 0,1,2,3,4,5
mask: 012345
strings: 53210234, 300303334
> 0345678
mask: 0345678
strings: 5374576807063874, 300303334
将每个值放入一个集合--例如:“300303334”={3,0,4}
由于数据项的长度受常量(50)的限制,
您可以在O(1)时间使用JavaHashSet为每个项目执行这些操作。此阶段的总体复杂性加起来为O(n)
对于每个筛选器集,使用哈希集的containsAll()查看
这些数据项中的每一项都是筛选器的子集。采用O(n)
取O(m*n)在整体中,n是数据项的数量,m是过滤器的数量。数据是否经常更改?不,它很少更改。然后使用哈希表,遍历每个数字中的所有数字,并将其放在每个哈希0-9下。数字123应放在1、2和3下,以此类推。这需要som是时候创建它了,但是检索速度会很快。如果你想检索包含1,6,7的数字,只需合并1,6和7中的列表即可。@BjørnBråthen的答案是,如果你要求包含1,2和3的字符串,它将包括“1234”、“197832”,而不是只包含请求的数字的字符串。您必须将此方法与最终筛选器相结合,该筛选器将检查最终结果中的每个字符串,以查看它是否只包含请求的数字。我不知道Bloom筛选器在这方面如何帮到您。起初听起来不错,但实际上构建了“包含x和y的字符串以及。。。"集本身就是一个挑战,即使只对10个数字来说也是如此。我把它简化为只有数字——我会用几千个其他符号代替数字。如果你有很多符号,那么我同意这种方法不是很好,你最好只计算每个符号集的交集。谢谢。我会这样做的在接下来的几天里,我会进行性能测试。在这个阶段,我只是在寻找好方法的集思广益,所以你的输入帮助很大。回答得很好!恕我直言,我认为如果用户要求只包含数字2的字符串,你的值中会设置第2位,这意味着你应该在查找时将列表还给他[(1或者,换一种说法,如果他要求字符串包含零,将设置零位,这需要零移位。@MarkSetchell:谢谢你的更正。我不知道我在想什么。这难道不能处理他的第二个示例:过滤器(数据,[0,1,2,3,4,5])=['300303334','53210234']-您将只返回完全包含请求的数字的数字,而不是子集。@mattnewport请查看我的数字。当您将字符串插入到所有l中时
gcc -lreadline -o digitfilter digitfilter.c
$ cat data.txt
300303334
53210234
123456789
5374576807063874
$ ./digitfilter data.txt
> 034
mask: 034
strings: 300303334
> 0,1,2,3,4,5
mask: 012345
strings: 53210234, 300303334
> 0345678
mask: 0345678
strings: 5374576807063874, 300303334