Performance 仅筛选包含给定数字集的数字序列

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')

我有一个像这样的数字字符串的大列表。单个字符串相对较短(比如少于50位)

我需要找到一种高效的数据结构(速度第一,内存第二)和算法,它只返回由给定数字集组成的字符串

示例结果:

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