使用Perl,使用哈希映射在数组中搜索单词

时间:2016-10-13 01:40:08

标签: regex perl hashmap

我正在尝试使用哈希映射在数组中搜索一个单词,如下例所示,但它找不到它。下面的代码会将一本书读入@bookArray。然后,我想搜索@bookArray以找到某个单词。书中的文字可以被没有边界的其他词包围。

open( SEARCHWORDS, "< $encoding", $_folder . "searchwords.txt" ) or die("Input file not found.\n");
open( BOOK, "< $encoding", $_folder . "book.txt" ) or die("Input file not found.\n");

while (  $_ = <BOOK> ) {            
    push @bookArray, $_;
}
my %thebook = map {$_ => 1} @bookArray;

while (  my $searchWords = <SEARCHWORDS> ) {    

    if (exists $thebook{$searchWords}) {
        print "yeppie";
    }
}

#example of words in Book "I want to go to the store andbuy some food";
#example of search words "buy";

2 个答案:

答案 0 :(得分:2)

更新已经澄清,目标是识别具有给定单词的句子。

%thebook哈希中的每个键都是本书中的整行。因此,当您搜索一个单词时,它就不存在。

使用您已拥有的内容识别包含单词的行的简单方法

foreach my $line (@bookArray)
{
    while (my $searchWords = <SEARCHWORDS>) 
    {   
        chomp $searchWords; 
        if ($line =~ /($searchWords)/) {
            print "Found $1 in: $line\n";
        }
    }
}

这是低效的,因为每行搜索所有单词,并且已经处理了书籍文件。但对于一个给定的目标,它仍然可以接受。

当你从那个文件中读取时,最好转过来并在书的每一行中查找单词。

use warnings 'all';
use strict;

my $words_file = '...';
my $book_file  = '...';

open my $w_fh, '<', $words_file  or die "Can't open $words_file: $!";
my @search_words = <$w_fh>;
close $w_fh;
chomp(@search_words);

open my $bk_fh, '<', $book_file  or die "Can't open $book_file: $!";

while (my $line = <$bk_fh>) 
{
    chomp $line;
    foreach my $word (@search_words)
    {
        if ($line =~ /$word/) 
        {
            print "Found $word in line $.: $line\n";
        }
    }
}

每次找到一个单词时,都会在每一行都打印出来。

您还可以在书中构建单词的哈希值,其中每个单词都是一个键,其值为 arrayref (数组引用),其中包含找到它的行号。

迭代行并将每个行分成单词,将它们添加为键。对于每个键,将该行的编号($.)添加到其arrayref值。即使在同一行上重复找到该单词,也会添加该数字,这是多行出现的合理记录。如果不需要,这很容易改变。我们还在数组中存储行。

while (<$bk_fh>) { 
    push @bookArray, $_;
    push @{$book_word{$_}}, $.  for split;
}

say "$_ => [ @{$freq{$_}} ]" for sort keys %freq;   # print all (long!)

默认情况下,split会按空格分割$_,默认情况下<>运算符会分配给$_。因此,for split会迭代该行上的字词,为每个字段执行push ...$.是当前从$bk_fh读取的行的行号。

如果单词已经被看到并且因此存在为键,则push只是将此行的数字添加到arrayref中,该数字是该键的值。

Perl的 autovivification 在遇到新单词时会变得简单。当使用表达式$book{$_}时,新单词(在$_变量中)将自动添加为键。同样,@{$book{$_}}表达式引用的arrayref是该新单词的键的值,因此创建了arrayref 。然后push$.置于其上。我们不必先手动创建它们。

有关参考资料,请参阅perlreftut;有关复杂数据结构,请参阅perldsc

然后,您可以使用exists的好主意检查每个单词,如果确实存在,则使用该单词的值打印数组中的行,该单词是数组中其行的索引。

原帖

你可以将书的每一行分解为单词并将其输入哈希,一个大哈希

while ( <BOOK> ) {        
    chomp;    
    push @bookArray, split;
}
my %thebook = map {$_ => 1} @bookArray;   # potentially very big

默认情况下,拆分会按空格分割$_,而默认情况下<>会分配给$_

或者,如果你想让数组保持整行,那就做哈希

my @bookArray = <BOOK>;
chomp @bookArray;

my %thebook = map {$_ => 1} map { split } @bookArray;  # potentially very big

然后单个单词$searchWords将(可能)成为键。此外,行必须chomp - ed以删除换行符。否则有些词会有,有些则不会。

我想补充一下 - 为什么要按照你的方式打开文件?

有什么不足之处
my $bookfile = $_folder . 'book.txt';
open my $bk_fh, '<', $bookfile  or die "Can't open $bookfile: $!";

另外,$_folder确实是一个包含文件夹名称的变量吗?对于变量名称来说,这是一个冒险的选择。

答案 1 :(得分:1)

我正在看这个并且和Term::ANSIColor玩得很开心......最后会告诉你我做了什么......

现在关于问题本身:

步骤0)通常要做的事情,严格和警告等......

use strict;
use warnings;

my $wordlist = 'search.txt';
my $bookfile = 'book.txt';

步骤1)首先阅读词汇表:

open ( LIST, '<:encoding(UTF-8)', $wordlist ) or die $!;
my @list = <LIST>;           # slurp the entire file in an array
chomp @list;                 # chomps each element in the list
@list = grep { $_ } @list;   # only keep 'valid' elements

步骤2)将其转换为已编译的正则表达式,以便以后重复使用:

my $list = join '|', @list;  # search words seperated by '|'
my $regx = qr/($list)/i;     # turn it into compiled regex
                             # now this will look something like
                             # /(word1|word2|...)/i

注1:这是不区分大小写的匹配/i

注意2:正则表达式会匹配任何地方的字符串,而不只是一个单词,如果你只想匹配一个单词,请使用以下代码:

my $regx = qr/\b($list)\b/i; # \b boundary of a word

步骤3)阅读本书并打印

open ( BOOK, '<:encoding(UTF-8)', $bookfile ) or die $!;
while (<BOOK>) {             # reads each line into $_ one by one
    print("yeppie\n") && last if /$regx/
}

现在,这是你最初要求的,打印一个快乐的东西,但是,似乎你想要打印每一行:

    print if /$regx/         # if matches with $_ ?

到目前为止,这就是你所要求的答案。没有可怕的缓慢的循环嵌套,没有任何哈希,一个简单的while - 语句和一个预编译的正则表达式。

现在,让我们享受一些乐趣并更进一步......

use strict;
use warnings;

use Term::ANSIColor qw(:constants :pushpop);
my $OPEN = PUSHCOLOR . BOLD . BRIGHT_BLUE . ON_YELLOW;
my $STOP = POPCOLOR;

my $wordlist = 'search.txt';
my $bookfile = 'book.txt';

open ( LIST, '<:encoding(UTF-8)', $wordlist ) or die $!;
my @list = <LIST>;           # slurp the entire file in an array
chomp @list;                 # chomps each element in the list
@list = grep { $_ } @list;   # get rid of empty elements

my $list = join '|', @list;  # search words seperated by '|'
my $regx = qr/($list)/i;     # turn it into compiled regexp

open ( BOOK, '<:encoding(UTF-8)', $bookfile ) or die $!;
while (<BOOK>) {             # reads each line into $_
    s/$regx/$OPEN$1$STOP/g;  # wrap match inside $OPEN and $STOP
    print;                   # prints the (modified) $_
}

use Term::ANSIColor开头的三行希望有点自我解释,但以防万一:

my $OPEN = PUSHCOLOR . BOLD . BRIGHT_BLUE . ON_YELLOW;

创建一个开头标记&#39;并且&#39;推送终端颜色/字体变化&#39;到堆栈,

my $STOP = POPCOLOR;

创建了一种关闭标记&#39;。 Term::ANSIColor知道如何处理这些问题。

棘手的一点是:

    s/$regx/$OPEN$1$STOP/g;  # wrap match inside $OPEN and $STOP

您可能还记得,我们将单词列表括在括号内,因此正则表达式捕获单词并将其存储在$1中。在这里,我们全球&#39;将所有出现的单词替换为已打开和结束标记内的单词。 (当然,如果你在顶部更改它们,可能是HTML)。

所以,这两个文件都在这里:

book.txt

Hello,

This is a nice book about searching inside books,
which is an interesting topic when you want to do
that with Perl.

Read books about perl and understand how this works.

THE END

NB. I have added some nice colour options and print ALL

search.txt

book
perl
colou?r

如您所见,我们还可以在这里添加(小)正则表达式。小心使用元字符,如果需要查找它们,可能需要转义它们。对.*非常谨慎,因为它是绿色的!

玩得开心!