我有一个很大的数字字符串列表,就像这个。单个字符串相对较短(比如少于50位)。
data = [
'300303334',
'53210234',
'123456789',
'5374576807063874'
]
我需要找到一个有效的数据结构(速度优先,内存秒)和算法,它只返回由给定的一组数字组成的字符串。
示例结果:
filter(data, [0,3,4]) = ['300303334']
filter(data, [0,1,2,3,4,5]) = ['300303334', '53210234']
数据列表通常适合内存。
答案 0 :(得分: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_postings(p0, p1):
i0, i1 = next(p0), next(p1)
while True:
if i0 == i1:
yield i0
i0, i1 = next(p0), next(p1)
elif i0 < i1: i0 = next(p0)
else: i1 = next(p1)
def find_all(digits):
p = None
for d in xrange(10):
if d not in digits:
if p is None: p = iter(postings[d])
else: p = intersect_postings(p, iter(postings[d]))
return (data[i] for i in p) if p else iter(data)
print list(find_all([0, 3, 4]))
print list(find_all([0, 1, 2, 3, 4, 5]))
答案 1 :(得分:1)
字符串可以用10位数字编码。有2 ^ 10或1,024个可能的值。
因此,创建一个字典,该字典使用键的整数和值的字符串列表。
计算每个字符串的值,并将该字符串添加到该值的字符串列表中。
总体思路:
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];
请注意,正如Matt在下面的评论中指出的那样,这只会返回包含所有三位数字的字符串。所以,例如,它不会返回37.这对我来说似乎是一个致命的缺陷。
如果您需要处理的符号数量非常大,那么可能的组合数量会变得太大而无法使此解决方案变得切实可行。
对于大量符号,我建议使用注释中建议的反向索引,并结合使用辅助过滤器来删除包含无关数字的字符串。
答案 2 :(得分:0)
考虑一个函数f,如果数字i在字符串中,则为每个字符串构造一个位掩码,其中第i位设置。
例如,
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
from collections import defaultdict
import itertools
raw_data = [
'300303334',
'53210234',
'123456789',
'5374576807063874'
]
def preprocess(raw_data):
data = defaultdict(list)
for s in raw_data:
bitmask = 0
for digit in s:
bitmask |= 1<<int(digit)
data[bitmask].append(s)
return data
def filter(data,mask):
for r in range(len(mask)):
for m in itertools.combinations(mask,r+1):
bitmask = sum(1<<digit for digit in m)
for s in data[bitmask]:
yield s
data = preprocess(raw_data)
for a in filter(data, [0,1,2,3,4,5]):
print a
答案 3 :(得分:0)
只是为了踢,我编写了Jim's可爱的算法,如果有人想玩它,Perl就在这里。请不要接受这个作为答案或任何事情,将所有功劳归功于吉姆:
#!/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};
}
答案 4 :(得分:0)
我喜欢Jim Mischel的方法。它具有非常高效的查找和有限的内存使用。 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;
}
编译使用
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
答案 5 :(得分:0)
将每个值放入一个集合中 - 例如:'300303334'= {3,0,4}。
由于数据项的长度受常量(50)的约束, 您可以使用Java HashSet 在 O(1)时间为每个项目执行这些操作。此阶段的总体复杂性加起来为 O(n)。
对于每个过滤器集,使用 HashSet 的 containsAll()来查看是否 每个数据项都是过滤器的子集。采取 O(n)。
总体上 O(m * n),其中 n 是数据项的数量, m 是过滤器的数量。< / p>