如何确定一个元素是否存在于一个数组中(Perl)

13
我正在遍历一个数组,并且我想测试另一个数组中是否存在该元素。
伪代码如下所示:
foreach $term (@array1) {
    if ($term is found in @array2) { 
        #do something here
    }
}

我已经掌握了"foreach"和"do something here"的部分......但是所有我尝试的用于"if term is found in array"测试的方法都不起作用......
我曾尝试使用grep:
if grep {/$term/} @array2 { #do something }
# this test always succeeds for values of $term that ARE NOT in @array2

if (grep(/$term/, @array2)) { #do something }
# this test likewise succeeds for values NOT IN the array

我尝试了几种不同的“将数组转换为哈希”的方法,许多之前的帖子都表明这很简单易行...但是它们都没有起作用。

我是Perl的长期低级用户,我只理解Perl的基础知识,不理解99%的解决方案中包含的所有花哨的混淆代码...我真的非常、真诚地感激任何提供具体代码和逐步解释代码操作的答案...

...我真的不理解 $_ 和任何其他类型的隐藏、理解或暗示值、变量或函数。如果任何示例或样本都使用清晰术语命名所有变量和函数(例如$term而不是$_)并使用注释描述代码正在执行的操作,那么我会非常感激,这样我就可以在我的智力缺陷荣耀中希望有一天能够理解它。拜托了。 :-)

...

我有一个现有的脚本,其中使用了'grep':

$rc=grep(/$term/, @array);
if ($rc eq 0) { #something happens here }

但是,我将完全相同的代码应用到我的新脚本上,它根本无法成功...也就是说,当它测试一个我知道不在被测试数组中的$term值时,它会“成功”(rc=零)。我就是不明白。

我的“grep”方法在“旧”脚本和“新”脚本之间唯一的区别是如何构建数组...在旧脚本中,我通过从文件中读取来构建数组:

  @array=`cat file`;

在新脚本中,我将数组放在脚本内部(因为它很小)...就像这样:

  @array=("element1","element2","element3","element4");

如何导致grep函数的输出不同?它们都是普通的数组!我不明白!!! :-(
附加说明...一些我的实际代码的澄清或示例:
我试图匹配/查找/grep的术语是一个单词元素,例如"word123"。
这个练习只是为了从一个充满垃圾的文件中找到一些重要信息而设计的一个快速脏脚本,所以我选择跳过所有的好处(使用严格、警告、模块、子程序)...这不必优雅,只要简单即可。
我正在搜索的术语存储在通过split实例化的变量中:
foreach $line(@array1) {
  chomp($line);  # habit

  # every line has multiple elements that I want to capture
  ($term1,$term2,$term3,$term4)=split(/\t/,$line);  

  # if a particular one of those terms is found in my other array 'array2'
  if (grep(/$term2/, @array2) { 
    # then I'm storing a different element from the line into a 3rd array which eventually will be outputted
    push(@known, $term1) unless $seen{$term1}++;
  }
}

看到上面的grep了吗?它不能正常工作…即使$term2显然不在array2中,它也对所有$term2的值成功匹配…array1是一个几千行的文件。我在这里调用的元素$term2是一个离散的术语,可能出现在多行中,但在任何给定的行中都不会重复(或成为更大字符串的一部分)。Array2大约有几十个元素,我需要将其“过滤”以获得输出。

...

我刚刚尝试了以下建议:

if (grep $_ eq $term2, @array2) 

对于所有的$term2值,这个grep都失败了......我从grep中得到的是全有或全无的响应......所以我想我需要停止使用grep。尝试其中一种哈希解决方案......但我真的需要更多关于它们的解释和说明。


1
你能提供一个简短的脚本(在pastebin或等效网站上)来重现你的问题吗?这将有助于我们诊断出问题所在。 - Dancrumb
2
如何判断一个列表或数组中是否包含某个元素? - Eugene Yarmash
1
https://dev59.com/l3E85IYBdhLWcg3wRBRU - matthias krull
是的...在这里使用哈希是正确的选择,否则你会得到一个对于大数组不会有好性能的解决方案(因为你需要扫描array2中的每个元素来匹配array1)。 - Dancrumb
1
$term的价值是什么?请提供有关您搜索术语的示例以及您期望匹配和不匹配的内容。您是否寻找精确匹配(“foo”仅匹配“foo”)或部分匹配(“foo”匹配“food”)? - mob
@array =\cat $file`可能被认为是一个无用的cat用法。Perl 有一个非常好(更好)的open` 命令可以使用。 - TLP
8个回答

9

这段内容出自perlfaq,快速完成该操作的方法如下:

my %seen;
$seen{$_}++ for @array1;
for my $item (@array2) {
    if ($seen{$item}) {
        # item is in array2, do something
    }
}

如果字母大小写不重要,你可以使用$seen{lc($_)}来设置键,并使用if($seen{lc($item)})进行检查。
注:根据修改后的问题:如果任务是将@array2中的单个单词与@array1中的整行匹配,则该任务更加复杂。 尝试拆分行并与哈希键匹配可能会不安全,因为存在标点符号和其他类似情况。 因此,正则表达式解决方案可能是最安全的选择。
除非@array2非常大,否则您可以尝试以下方法:
my $rx = join "|", @array2;
for my $line (@array1) {
    if ($line =~ /\b$rx\b/) {  # use word boundary to avoid partial matches
        # do something
    }
}

如果@array2包含了元字符,例如*?+|,你需要确保对它们进行转义。此时你需要执行以下操作:
my $rx = join "|", map quotemeta, @array2;
# etc

2
这样做的优点是它是O(N)。天真的解决方案是O(N^2)。choroba和cdarke的是O(N^2)。 - ikegami
我认为这个例子不会像现在这样工作,而且我不理解它足够好,无法看出如何修改以适应。Array1是文件的内容,数组的每个元素都是文件中的整行数据 -- 由多个元素组成的数据行。我必须将其分割以获取我需要测试的单个元素,以便与Array2进行比较,Array2是一个更简单的数组,由单个单词的纯列表组成。我不能将Array1中的整行与Array2中的单个单词进行比较,那样行不通。 - MuleHeadJoe
@TLP... 大小写不敏感。我认为数据集的来源也不太重要,因为所有数据都被简化为数组和元素(标量变量)。我手头上有这个元素,它是$term。我想查找$term是否存在于Array2中。我不需要从Array2中取出任何东西,这只是一个存在性检查。如果$term存在于Array2中,则我必须在$term所在的行上做一些工作(即来自Array1的原始元素形式)。那也已经完成了,没有问题。 - MuleHeadJoe
我只是想在一个脚本中完成所有工作。对文件进行 grep 操作并未将文件数据放入我想要操作的 perl 脚本中。我想,我可以通过使用 "for i in list do grep $i oldfile >>newfile done" 减少初始文件,然后将 newfile 中的所有数据都变得相关,并可以针对该文件运行我的 perl 脚本,从而完全跳过在 perl 中进行 grep 操作的问题。但这至少需要执行一次比我想要的多的步骤。两个脚本而不是一个。不,perl 应该做这件事,而不是我。 - MuleHeadJoe
哦,问题没有改变,只是你对它的理解变了;-) ... 我仍然只是想看看一个存储为标量变量的元素是否存在于数组中 :-) - MuleHeadJoe
显示剩余5条评论

6

如果你的版本号是5.10或者之后的版本,你可以使用(臭名昭著的)"智能匹配"运算符:

#!/usr/bin/perl
use strict;
use warnings;

my @array1 = qw/a b c d e f g h/; 
my @array2 = qw/a c e g z/; 

print "a in \@array1\n" if 'a' ~~ @array1;
print "z in \@array1\n" if 'z' ~~ @array1;
print "z in \@array2\n" if 'z' ~~ @array2;

这个例子非常简单,但如果需要的话,您也可以使用正则表达式。值得注意的是,并不是每个人都喜欢使用它,因为有一些模糊和未记录的特性。尽管如此,在这种情况下应该没有问题。


我尝试了这个:if ($term1 ~~ @array2) { print "$term在array2中找到\n"; } - MuleHeadJoe
抱歉,我中途分心了,所以评论超时了...但是我正在尝试使用指定的“智能匹配操作符”,但它对我不起作用。我在Cygwin上使用perl 5.10.1。没有出现错误,只是没有提供预期的结果。 - MuleHeadJoe
@MuleHeadJoe - RE -> 正则表达式。我更喜欢cdarke的智能匹配解决方案。然而,我不确定为什么这对你不起作用。例如,使用cdarke的数组:do{print "$_ found in \@array2\n" if $_ ~~ @array2} for @array1;会在@array2中显示a、c、e、g。 - Kenosis
@MuleheadJoe - 关于宗教教育或正则表达式,我猜你需要确定哪个适合上下文。 - cdarke
@Kenosis: 对于那个测试,你的结果在我看来似乎是正确的,你还期望什么呢?a、c、e、g都在@array1@array2中(z在@array2中而不在@array1中)。 - cdarke
@cdarke:确实,我想与MuleHeadJoe分享这个,因为我更喜欢你的智能匹配解决方案来解决他的“测试元素是否在另一个数组中找到”的问题。 - Kenosis

5
这应该可以正常工作。
#!/usr/bin/perl
use strict;
use warnings;

my @array1 = qw/a b c d e f g h/;
my @array2 = qw/a c e g z/;

for my $term (@array1) {
    if (grep $_ eq $term, @array2) {
        print "$term found.\n";
    }
}

输出:

a found.
c found.
e found.
g found.

OP正在进行正则表达式匹配,而不是精确匹配。但是不清楚他/她真正想要哪种类型的匹配。 - mob
你的示例中使用 "for" 而不是 "foreach" 是否有特殊原因?是个人喜好还是技术上的原因?在我的脚本中,我使用 "foreach $line(@array1)" ... 我的 array1 是一个文本文件(@array1=`cat myfile`;),我将每行内容分割开来以便重新排列元素,最终输出一个 CSV 文件,可以在 Excel 中打开并操作。 - MuleHeadJoe
4
forforeach 是同义词,可以使用你认为更富表现力的那个。 - RickF
@RickF ... 谢谢,我以为它们有不同的功能。我很少使用'for',所以不确定。 - MuleHeadJoe
我认为foreach在这个本来很好的语言设计中是一个意外。还有什么能解释它呢?许多其他语言使用for,所以我认为for比foreach更清晰。(除了更短之外) - Kjetil S.

2
#!/usr/bin/perl

@ar = ( '1','2','3','4','5','6','10' );
@arr = ( '1','2','3','4','5','6','7','8','9' ) ;

foreach $var ( @arr ){
    print "$var not found\n " if ( ! ( grep /$var/, @ar )) ;
}

1

模式匹配是匹配元素最有效的方法。这样就可以解决问题了。干杯!

print "$element found in the array\n" if ("@array" =~ m/$element/);

0

你的“实际代码”甚至不应该编译:

if (grep(/$term2/, @array2) { 

应该是:

if (grep (/$term2/, @array2)) { 

你的代码中有不平衡的括号。你可能会发现使用带有回调函数(代码引用)的grep更容易操作其参数(数组)。它有助于避免括号混淆在一起。这是可选的,但是可以这样写:

if (grep {/$term2/} @array2) { 

你可能想要使用 strict; 和 use warnings; 来捕捉类似这样的问题。


我的错,我没有剪切/粘贴代码...代码在一个物理上分离的机器上...真正的问题是匹配的括号,在那个世界里我总是通过执行"perl -cw [脚本名称]"来检查语法错误... - MuleHeadJoe

0

下面的示例可能会有所帮助,它尝试查看@array_sp中的任何元素是否存在于@my_array中:

#! /usr/bin/perl -w

@my_array = qw(20001 20003);

@array_sp = qw(20001 20002 20004);
print "@array_sp\n";

foreach $case(@my_array){
    if("@array_sp" =~ m/$case/){
    print "My God!\n";
    }

}

使用模式匹配可以解决这个问题。希望有所帮助。-QC

0
1. grep with eq , then 
    if (grep {$_ eq $term2} @array2) { 
    print "$term2 exists in the array";
    }

2. grep with regex , then 
    if (grep {/$term2/} @array2) {
    print "element with pattern $term2 exists in the array";
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接