【问题标题】:How can I take queries from one file, search another, and output to a third, in Perl?如何在 Perl 中从一个文件中获取查询、搜索另一个文件并输出到第三个文件?
【发布时间】:2011-10-18 23:21:22
【问题描述】:

编辑:我原来的标题已经改变了。我怀疑现在的标题并没有揭示我最初的目的:让 Perl 自动使用一个文件的内容作为搜索关键字的来源来搜索另一个文件,然后将匹配结果输出到第三个文件。。 p>

这意味着如果没有这种自动化,我将不得不手动输入FILE1 中列出的那些查询词,并通过简单地编写类似while(<FILE2>){if (/query terms/){print FILE3 $_}} 的内容一次从FILE2 获取匹配项。

更具体地说,FILE1 应该如下所示:

azure
Byzantine
cystitis
dyspeptic
eyrie
fuzz

FILE2 可能(或可能不会)看起来像这样:

azalea        n.  flowering shrub of the rhododendron family
azure         adj. bright blue, as of the sky 
byte          n. fixed number of binary digits, often representing a single character
Byzantine     adj. of Byzantium or the E Roman Empire
cystitis      n. inflammation of the bladder
Czech         adj. of the Czech Republic or Bohemia
dyslexic      adj. suffering from dyslexia
dyspeptic     adj. suffering from dyspepsia
eyelet        n. small hole in cloth, in a sail, etc for a rope, etc to go through; 
eyrie         n. eagle's nest
fuzz          n. mass of soft light particle
fuzzy         adj. like fuzz

如果FILE2 是上面的样子,FILE3 应该看起来像这样:

azure         adj. bright blue, as of the sky 
Byzantine     adj. of Byzantium or the E Roman Empire
cystitis      n. inflammation of the bladder
dyspeptic     adj. suffering from dyspepsia
eyrie         n. eagle's nest
fuzz          n. mass of soft light particle

我花了几个小时的反复试验才最终找出一个看似可行的解决方案,但我的代码可能有问题,更不用说效率低下了。如果我错了,我希望你们能把我送到正确的轨道上,请给我一些指导并与我分享一些解决问题的不同方法(嗯,必须有)。正如 daotoad 所建议的,我试图注释掉每一行代码的作用。如果我误解了什么,请纠正我。

#!perl  #for Windows, simply perl suffices. I'm reading *Learning Perl*.    
use warnings; #very annoying I've always been receiving floods of error messages
use strict;   #I often have to look here and there because of my carelessness

open my $dic,'<', 'c:/FILE2.txt' or die "Cannot open dic.txt ;$!"; # 3-argument version of open statement helps avoid possible confusion; Dunno why when I replace dic.txt with $dic in the death note, I'll receive "needs explicit package name" warning. Any ideas?
open my $filter,'<','c:/FILE1.txt' or die "Cannot open new_word.txt :$!"; 
my @filter=<$filter>; #store the entire contents of FILE1 into @filter.
close $filter;        #FILE1 is useless so close the connection between FILE1 and perl
open my $learn,'>','c:/FILE3.txt'; #This file is where I output matching lines.
my $candidate="";     #initialize the candidate to empty string. It will be used to store matching lines. Learnt this from Jeff.

while(<$dic>){    #let perl read the contents of FILE2 line by line.
for (my $n=0; $n<=$#filter; $n++){ #let perl go through each line of FILE1 too
my $entry = $filter[$n];
chomp($entry);   #Figured out this line must be added after many fruitless attempts
if (/^$entry\s/){  #let perl compare each line of FILE2 with any line of FILE1.
$candidate.= $_ ; } #every time a match is found, store the line into $candidate
}
}
print $learn $candidate; #output the results to FILE3

更新 1:

非常感谢您的指导!我真的很感激:)

我相信我现在正朝着与我最初打算的方向有些不同的方向前进。哈希的概念超出了我当时的 Perl 知识储备。完成了学习 Perl 的哈希部分后,我现在在想:虽然使用哈希可以有效地解决我上面发布的示例问题,但如果定义文件 (@987654332) 中的词条(不是整个条目),情况可能会变得复杂@) 有重复。

但另一方面,我看到哈希在 Perl 编程中非常重要。所以今天早上我尝试实现@mobrule 的想法:将FILE1 的内容加载到哈希中,然后检查FILE2 中每一行的第一个单词是否在您的哈希表中。但后来我决定我应该加载@ 987654335@ 转换为哈希而不是 FILE1,因为 FILE2 包含字典条目,将 HEADWORDS 视为 KEYS 并将 DEFINITIONS 视为 VALUES 是有意义的。现在我想出了以下代码。似乎离成功很近了。

#!perl

open my $learn,'>','c:/file3.txt' or die "Cannot open Study Note;$!";
open my $dic,"<",'c:/file2.txt' or die "Cannot open Dictionary: $!";
my %hash = map {split/\t+/} <$dic>; # #I did some googling on how to load a file into a hash and found this works. But actually I don't quite understand why. I figured the pattern out by myself. /\t+/ seems to be working because the headwords and the main entries in FILE2 are separated by tabs.  

open my $filter,'<','c:/file1.txt' or die "Cannot open Glossary: $!";
while($line=<$filter>){
chomp ($line);
if (exists $hash{$line}){
print "$learn $hash{$line}"; # this line is buggy. first it won't output to FILE3. second, it only prints the values of the hash but I want to include the keys.
}
}

代码在屏幕上输出以下结果:

GLOB(0x285ef8) adj. bright blue, as of the sky
GLOB(0x285ef8) adj. of Byzantium or the E Roman Empire
GLOB(0x285ef8) n. inflammation of the bladder
GLOB(0x285ef8) adj. suffering from dyspepsia
GLOB(0x285ef8) n. eagle's nest
GLOB(0x285ef8) n. mass of soft light particle

更新 2:

解决了一个问题。我现在可以通过对最后一行进行小修改来打印键和值。

print "$learn $line: $hash{$line}";

更新 3:

哈哈:我做到了!我做到了:)我再次修改了代码,现在它输出了东西到FILE3

#!perl

open my $learn,'>','c:/file3.txt' or die $!;
open my $dic,"<",'c:/file2.txt' or die $!;
my %hash = map {split/\t+/} <$dic>; #the /\t+/ pattern works because the entries in my FILE2 are separated into the headwords and the definition by two tab spaces. 

open my $filter,'<','c:/file1.txt' or die $!;
while($line=<$filter>){
chomp ($line);
if (exists $hash{$line}){
print $learn "$line: $hash{$line}";
}
}

更新 4:

我在想,如果我的FILE2 的内容完全不同,比如包含FILE1 中的查询词的句子,那么我们使用哈希方法会很困难,即使不是不可能,对吧?

更新 5:

仔细阅读了关于拆分运算符的 perlfunc 页面,现在我知道如何改进我的代码了 :)

#!perl

open my $learn,'>','c:/file3.txt' or die $!;
open my $dic,"<",'c:/file2.txt' or die $!;
my %hash = map {split/\s+/,$_,2} <$dic>; # sets the limit of separate fields to 2
open my $filter,'<','c:/file1.txt' or die $!;
while($line=<$filter>){
chomp ($line);
if (exists $hash{$line}){
print $learn "$line: $hash{$line}";
}
}

【问题讨论】:

  • 没有用这个评论回答你的问题,只是想指出大多数 perl 黑客识别句柄(包括文件句柄),所有大写的裸词。例如,您将拥有 DIC、FILTER 和 LEARN。此外,读写字符可以包含在文件名中,例如 ">c:/file3.txt"。
  • 在 Perl 中,文件句柄只是一个文件句柄,而不是文件名,因此在错误消息中打印 $dic 将没有用。它给出错误的原因是my $dic 声明了一个变量并返回它(因此在您的代码中打开将能够设置它)但语句中其他提及的 $dic 仍将引用 $dic 之前的任何内容宣言;直到以下语句之前,对 $dic 的额外提及才与刚刚声明的词法相关联。这允许 my $dic = $dic; 之类的东西(声明一个新词法,设置为以前的词法或全局的值。)
  • @fatcat1111,perl 黑客?啊,那么这是深奥的东西。感谢分享:)
  • @ysth,感谢您的澄清!我还记得在《Learning Perl》这本书中,有句话说 filehand 不一定是文件名,而是 Perl 与外部世界之间联系的名称。我想我误会了什么。
  • @fatcat1111 全大写的bareword 文件句柄和双参数的open 都源于旧的做事方式,如果你想编写好的代码,它们都是坏习惯。迈克的开局代表了一种的方法。

标签: perl hash


【解决方案1】:

通过一次考虑所有问题而不是将其分解为可管理的部分,您正在使问题变得比它需要的更难。

您在这里似乎不需要正则表达式。您只需要查看第一列中的术语是否在列表中:

open my($patterns), '<', 'patterns.txt' or die "Could not get patterns: $!"; 

my %hash = map { my $p = $_; chomp $p; $p, 1 } <$patterns>;

open my($lines), '<', 'file.txt' or die "Could not open file.txt: $!";

while ( <$lines> ) {
    my( $term ) = split /\s+/, $_, 2;
    print if exists $hash{$term};
    }

如果您真的需要正则表达式来查找术语,您也许可以只使用 grep:

 grep -f patterns.txt file.txt

【讨论】:

  • 嗯,我想这是关于哈希的东西,我还不太了解。但是非常感谢,布赖恩!还要再次感谢您对我上一篇文章的解决方案:)好吧,我正在保存代码以供以后学习。必须仔细阅读学习 Perl 中的哈希部分。
  • @Mike,如果正在查找的项目有可能不在定义文件中,您可以在找到时从哈希中删除这些项目,然后报告仍然存在的项目搜索后留在哈希中。
  • @brian,我现在正在尝试理解你的代码,因为现在我已经学到了一些关于哈希的东西。您的代码就像魔术一样完成这项工作。但是有很多东西我不遵循。我想知道您是否可以向我提供每行的一些解释。我也升级了我的代码,但我发现我在 map 函数中使用的模式已经失效。它不适用于不同的定义文件。
  • 再看你的代码,我觉得它很像我的升级代码。我不太明白 "my $p = $_; chomp $p; $p, 1" 和 "split /\s+/, $_, 2;" 的功能。
  • @brian,我自己弄清楚了什么是“map { my $p = $_; chomp $p; $p, 1 } ;”做。此行将“patterns.txt”的每一行更改为类似于 word1 => 1 word2=>1 word3=>1 ----
【解决方案2】:

您是否已进入 Learning Perl 中学习哈希的部分?您可以将 FILE1 的内容加载到哈希表中,然后检查 FILE2 中每一行的第一个单词是否在您的哈希表中。

【讨论】:

  • 这对我来说似乎是最自然的解决方案。用读取 f1 的每一行的块替换您进入 @filter 的位,设置 $filter{$_} = 1 或类似的值,然后稍后根据该哈希检查候选者,例如 if($filter{$candidate} = = 1) { 打印学习 $candidate; }.
  • @mobrule,感谢您的指点。这种方法似乎要好得多。实际上我已经粗略浏览了一些关于哈希的页面,但还没有完全掌握这个概念。我会再努力一点。
【解决方案3】:

如果您实际上不必使用 Perl,(并且您安装了 cygwin 或其他 unixy),您可以使用 grep -f new_word.txt dic.txt。但是让我们假设你想在这里学习一些关于 Perl 的东西.. :)

use strictuse warnings 对于发现问题(以及培养良好习惯)非常有用。请记住,如果您不确定警告消息的含义,可以在 perldoc perldiag 中查找。

关于您的评论“不知道为什么当我在死亡笔记中将 dic.txt 替换为 $dic 时,我会收到“需要明确的包名称”警告。有什么想法吗?” -- $dic 不是文件名,而是文件句柄,不是您通常想要打印的内容。为避免使用两次文件名(例如,为了以后更容易更改),请在文件顶部定义它,就像我所做的那样。

使用子程序在每个文件中推进位置感觉有点粗糙,但是这种算法只对每个文件循环一次,并且不会将任何一个文件读入内存,因此即使对于巨大的输入文件也可以工作。 (这取决于两个文件都被排序,它们似乎在您提供的示例中。)

代码已编辑和修复。我不应该在睡觉前敲掉一个版本然后不测试它(我责怪配偶):D

use warnings;
use strict;

my $dictFile = 'dict.txt';
my $wordsFile = 'words.txt';
my $outFile = 'out.txt';

open my $dic, '<', $dictFile or die "Cannot open $dictFile: $!";
open my $filter, '<', $wordsFile or die "Cannot open $wordsFile: $!";
open my $learn, '>', $outFile or die "Cannot open $outFile: $!";

# create variables before declaring subs, which creates closures
my ($word, $key, $sep, $definition);
sub nextWord {
    $word = <$filter>;
    done() unless $word;
    chomp $word;
};
sub nextEntry {
    # use parens around pattern to capture it into the list for later use
    ($key, $sep, $definition) = split(/(\s+)/, <$dic>, 2);
    done() unless $key;
}
sub done
{
    close $filter or warn "can't close $wordsFile: $!";
    close $dic or warn "can't close $dictFile: $!";
    close $learn or warn "can't close $outFile: $!";
    exit;
}

nextWord();
nextEntry();

# now let's loop until we hit the end of one of the input files
for (;;)
{
    if ($word lt $key)
    {
        nextWord();
    }
    elsif ($word gt $key)
    {
        nextEntry();
    }
    else    # word eq $key
    {
        # newline is still in definition; no need to append another
        print $learn ($key . $sep . $definition);
        nextWord();
        nextEntry();
    }
}

【讨论】:

  • @Ether,谢谢!我正在保存代码以供以后研究。顺便说一句,我还没有学会如何使用 SUB。我一直在尝试使用从解决方案中学到的知识来解决我以前的帖子。虽然在 Learning Perl 的早期章节中触及了 SUB 的一些内容,但必须通过更多像这样的真实示例重新学习它。再次感谢。
  • @Ether:您可能希望对您的代码进行一些处理,直到它可以工作并且不会出错。提示:您将需要一个循环,以及在您的 subs 中定义的变量以某种方式在它们之外使用。
  • @Ether,感谢您升级代码 :) 嗯..似乎有什么问题。运行代码后没有任何反应。我正在使用这个配置:“我的 $dictFile = 'c:/FILE2.txt'; 我的 $wordsFile = 'c:/FILE1.txt'; 我的 $outFile = 'c:/FILE3.txt';”跨度>
  • @Ether,谢谢,我已经从头到尾读了两遍代码。它有很多潜艇,但我想我能够完全理解它的每一行。它非常易读,特别是因为现在我已经提高了我的 Perl 知识储备:) 谢谢。
【解决方案4】:

在我看来,假设要查找的单词数量相对于字典的大小来说会很小,这似乎是合理的。因此,您可以将FILE1.txt 读入内存,将每个单词放入哈希中。

然后,阅读字典,仅输出该术语在散列中的行。我还将输出到STDOUT,然后可以将其从命令行重定向到您想要的任何文件。

#!/usr/bin/perl

use strict; use warnings;
use autodie qw(open close);

my ($words_file, $dict_file) = @ARGV;

my %words;
read_words(\%words, $words_file);

open my $dict_fh, '<', $dict_file;

while ( my $line = <$dict_fh> ) {
    # capturing match in list context returns captured matches
    if (my ($term) = ($line =~ /^(\w+)\s+\w/)) {
        print $line if exists $words{$term};
    }
}

close $dict_fh;

sub read_words {
    my ($words, $filename) = @_;

    open my $fh, '<', $filename;
    while ( <$fh> ) {
        last unless /^(\w+)/;
        $words->{$1} = undef;
    }
    close $fh;
    return;
}

调用:

C:\Temp>lookup.pl FILE1.txt FILE2.txt > FILE3.txt

输出:

C:\Temp> 键入 FILE3.txt 蔚蓝的 adj.明亮的蓝色,如同天空 拜占庭 adj.拜占庭或E罗马帝国 n. 膀胱炎膀胱发炎 消化不良的形容词患有消化不良 艾里岛鹰巢 fuzz n.软光粒子的质量

【讨论】:

  • @Sinan,谢谢。在我完成学习 Perl 的哈希部分后,我将尝试对代码进行评论。
  • @Sinan,我不确定这行代码的作用:$words ->{$1} = undef.还有这一行: my ($words, $filename) = @_;
  • @Sinan,是“if (my ($term) = ($line =~ /^(\w+)\s+\w/))”的缩写,是“if ($line=~/ ^(\w+)\s+\w/) {我的 $term = $line; ...} "?
  • @Mike $words-&gt;{$1} = undef 将第一个捕获的匹配设置为 $words 指向的 hashref 的一个元素,其值未定义。我不关心散列中的值,我只想能够快速检查我拥有的定义的单词是否在我要打印的定义列表中(即它是%$words 的键之一吗? )
  • @Mike 关于:my ($term) = ($line =~ /^(\w+)\s+\w/)。在列表上下文中捕获匹配项会返回匹配项。因此,将捕获的行首单词分配给$term是一种快速的方法。
【解决方案5】:

FILE1 和 FILE2 最初是否已排序?如果是这样,您只需要一个循环,而不是嵌套循环:

use 5.010;
use warnings;
use strict;

my $dictFile = 'c:/FILE2.txt';
my $wordsFile = 'c:/FILE1.txt';
my $outFile = 'c:/FILE3.txt';

open my $dic, '<', $dictFile or die "Cannot open $dictFile: $!";
open my $filter, '<', $wordsFile or die "Cannot open $wordsFile: $!";
open my $learn, '>', $outFile or die "Cannot open $outFile: $!";

my $dic_line;
my $dic_word;
my $filter_word;

# loop forever (or until last'ing out of the loop, anyway)
while (1) {
    # if we don't have a word from the filter list, get one
    if ( ! defined $filter_word ) {
        # get a line from the filter file, bailing out of the loop if at the end
        $filter_word = <$filter> // last;
        # remove the newline so we can string compare
        chomp($filter_word);
    }
    # if we don't have a word from the dictionary, get one
    if ( ! defined $dic_line ) {
        # get a line from the dictionary, bailing out of the loop if at the end
        $dic_line = <$dic> // last;
        # get the first word on the line
        ($dic_word) = split ' ', $dic_line;
    }
    # if we have a match, print it
    if ( $dic_word eq $filter_word ) { print $learn $dic_line }
    # only keep considering this dictionary line if it is beyond the filter word we had
    if ( lc $dic_word le lc $filter_word ) { undef $dic_line }
    # only keep considering this filter word if it is beyond the dictionary line we had
    if ( lc $dic_word ge lc $filter_word ) { undef $filter_word }
}

【讨论】:

  • @ysth,是的,在我的示例中,它们最初是排序的。我发明了要解决的问题,以便提高我的 Perl 知识。但我不知道如何避免嵌套循环。我可以吗?
  • @ysth,谢谢。但是运行代码会给我“未初始化的连接”错误。
  • @Mike:在哪一行?我省略了设置内容;会添加,以防你做了不同的事情。
  • @Mike:我感觉你打错了什么;那里没有串联。
  • @ysth,我在运行代码之前添加了设置内容。但我想我现在开始看到问题出在哪里了:) 这可能不是我打错了什么。运行当前代码给了我相同的“第 16 行的连接或字符串中的未初始化值”错误。我的activeperl 的版本是5.8.8。必须将其升级到 5.010。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-08-05
  • 2011-12-28
  • 2020-08-28
  • 1970-01-01
  • 2020-01-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多