仅过滤包含给定数字集的数字序列

时间:2013-11-29 12:02:35

标签: performance algorithm data-structures filtering lookup

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

data = [
  '300303334',
  '53210234',
  '123456789',
  '5374576807063874'
]

我需要找到一个有效的数据结构(速度优先,内存秒)和算法,它只返回由给定的一组数字组成的字符串。

示例结果:

filter(data, [0,3,4]) = ['300303334']
filter(data, [0,1,2,3,4,5]) = ['300303334', '53210234']

数据列表通常适合内存。

6 个答案:

答案 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

Python代码示例

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>