Очень быстрая проверка, существует ли одна из множества строк в одной из многих других строк, в Perl - PullRequest
3 голосов
/ 14 апреля 2020

Допустим, у меня есть набор из 100 000 строк, каждая из которых имеет в среднем 20-50 байтов.

Тогда, скажем, у меня есть еще один набор из 100 000 000 строк, каждая из которых также имеет в среднем около 20-50 байтов .

Я бы хотел go просмотреть все 100 000 000 строк из второго набора и проверить, существует ли какая-либо из строк из первого набора в какой-либо одной строке из второго набора.

Пример: строка из первого набора: «ab c», строка из второго набора: «123abc123» = match!

Я пытался использовать индекс Perl (), но это не быстро достаточно. Есть ли лучший способ сделать этот тип соответствия?

Ответы [ 5 ]

4 голосов
/ 15 апреля 2020

Я нашел Алгоритм :: AhoCorasik :: XS в CPAN, который реализует класс c, очень эффективный поиск по нескольким строкам Алгоритм Aho-Corasick (Тот же, который используется grep -F), и это кажется достаточно быстрым (Примерно в два раза меньше эквивалентного grep вызова):

Пример сценария:

#!/usr/bin/env perl
use warnings;
use strict;
use autodie;
use feature qw/say/;
use Algorithm::AhoCorasick::XS;

open my $set1, "<", "set1.txt";
my @needles = <$set1>;
chomp @needles;
my $search = Algorithm::AhoCorasick::XS->new(\@needles);

open my $set2, "<", "set2.txt";
while (<$set2>) {
    chomp;
    say if defined $search->first_match($_);
}

и его использование (со случайным сгенерированные тестовые файлы):

$ wc -l set1.txt set2.txt
  10000 set1.txt
500000 set2.txt
510000 total
$ time perl test.pl | wc -l
458414

real    0m0.403s
user    0m0.359s
sys     0m0.031s
$ time grep -Ff set1.txt set2.txt | wc -l
458414

real    0m0.199s
user    0m0.188s
sys     0m0.031s
3 голосов
/ 14 апреля 2020

Вы должны использовать чередование регулярных выражений, например:

my @string = qw/abc def ghi/;
my $matcher = qr/@{[join '|', map quotemeta, sort @string]}/;

Это должно быть быстрее, чем при использовании индекса. Но это можно сделать еще быстрее:

До определенного предела, в зависимости как от длины, так и от числа строк, perl создаст tr ie для эффективного сопоставления; см. например https://perlmonks.org/?node_id=670558. Вы захотите поэкспериментировать с тем, сколько строк вы можете включить в одно регулярное выражение для создания массива регулярных выражений. Затем объедините эти отдельные регулярные выражения в одно (непроверенное):

my @search_strings = ...;
my @matchers;
my $string_limit = 3000; # a guess on my part
my @strings = sort @search_strings;
while (my @subset = splice @strings, 0, $string_limit) {
    push @matchers, qr/^.*?@{[join '|', map quotemeta, sort @subset]}/s;
}
my $matcher = '(?:' . join('|', map "(??{\$matchers[$_]})", 0..$#matchers) . ')';
$matcher = do { use re 'eval'; qr/$matcher/ };
/$matcher/ and print "line $. matched: $_" while <>;

Конструкция (??{...}) необходима для объединения отдельных регулярных выражений; без этого все под-выражения просто интерполируются, а объединенное регулярное выражение компилируется все вместе, что исключает оптимизацию tr ie. Каждый подгрекс начинается с ^.*?, поэтому он ищет всю строку; без этого объединенное регулярное выражение должно было бы вызывать каждый подрегекс отдельно для каждой позиции в строке.

Используя искусственные данные, я вижу около 3000 строк, ищущих в секунду с этим подходом в не очень быстром vm. Использование наивного регулярного выражения составляет менее 50 строк в секунду. Использование grep, как предлагается в комментарии Шона, быстрее (для меня это примерно 4200 строк в секунду), но дает вам меньше контроля, если вы хотите делать такие вещи, как определение, какие строки соответствуют или в каких позициях.

0 голосов
/ 20 апреля 2020

Вот идея: вы можете разбить словарь на списки слов с одинаковым 2 или 3 буквенным префиксом. Затем вы должны выполнить итерацию для большого набора и для каждой позиции в каждой строке, извлечь префикс и попытаться сопоставить строки, имеющие этот префикс.

Для хранения списков с помощью O (вы использовали бы хеш-таблицу) 1) время поиска.

Если некоторые слова в словаре короче длины префикса, вам придется использовать короткие слова в специальном регистре.

Увеличение длины префиксов увеличит хеш-таблицу но списки короче, что сокращает время тестирования.

Я понятия не имею, может ли это быть эффективно реализовано в Perl.

0 голосов
/ 20 апреля 2020

Возможно, вы захотите взглянуть на https://github.com/leendo/hello-world. Его параллельная обработка делает его действительно быстрым. Либо просто введите все условия поиска индивидуально, либо как || союзы, или (лучше) адаптировать его для программного запуска второго сета в один go.

0 голосов
/ 15 апреля 2020

Задача довольно проста и, возможно, используется ежедневно по всему миру.

Пожалуйста, смотрите следующий фрагмент кода для одной из многих возможных реализаций

use strict;
use warnings;
use feature 'say';

use Getopt::Long qw(GetOptions);
use Pod::Usage;

my %opt;
my $re;

GetOptions(
            'sample|s=s'    => \$opt{sample},
            'debug|d'       => \$opt{debug},
            'help|h'        => \$opt{help},
            'man|m'         => \$opt{man}
    ) or pod2usage(2);

pod2usage(1) if $opt{help};
pod2usage(-verbose => 2) if $opt{man};

pod2usage("$0: No files given.")  if ((@ARGV == 0) && (-t STDIN));

$re = read_re($opt{sample});

say "DEBUG: search pattern is ($re)" if $opt{debug};

find_in_file($re);

sub find_in_file {
    my $search = shift;

    while( <> ) {
        chomp;
        next unless /$search/;
        say;
    }
}

sub read_re {
    my $filename = shift;

    open my $fh, '<', $filename
        or die "Couldn't open $filename";

    my @data = <$fh>;

    close $fh;

    chomp @data;

    my $re = join('|', @data);

    return $re;
}

__END__

=head1 NAME

file_in_file.pl - search strings of one file in other 

=head1 SYNOPSIS

yt_video_list.pl [options] -s sample.txt dbfile.txt

 Options:
    -s,--sample search pattern file
    -d,--debug  debug flag
    -h,--help   brief help message
    -m,--man    full documentation

=head1 OPTIONS

=over 4

=item B<-s,--sample>

Search pattern file

=item B<-d,--debug>

Print debug information.

=item B<-h,--help>

Print a brief help message and exits.

=item B<-m,--man>

Prints the manual page and exits.

=back

B<This program> seaches patterns from B<sample> file in B<dbfile>

=cut
...