【问题标题】:How can I check if all elements of an array are identical in Perl?如何检查 Perl 中数组的所有元素是否相同?
【发布时间】:2011-01-19 07:34:38
【问题描述】:

我有一个数组@test。检查数组的每个元素是否是同一个字符串的最佳方法是什么?

我知道我可以使用foreach 循环来做到这一点,但有没有更好的方法来做到这一点?我检查了地图功能,但我不确定这是否是我需要的。

【问题讨论】:

  • 解决方案的质量实际上取决于您在 foreach 循环中所做的工作。是的,有很多方法可以做到,但为什么你觉得你目前的解决方案缺乏?
  • 您为什么不编写并发布“foreach”解决方案,以便我们发表评论?
  • 对于所有关心 undef 的发帖者,我不会假设“检查数组的每个元素是否是同一个字符串”包括 undefs 的可能性 - 定义为“字符串”:) . (虽然这肯定是要考虑的事情)
  • 您只是想知道它们是否都相同,或者您还想知道哪些不同?

标签: perl arrays


【解决方案1】:

如果字符串已知,则可以在标量上下文中使用grep

if (@test == grep { $_ eq $string } @test) {
 # all equal
}

否则,使用哈希:

my %string = map { $_, 1 } @test;
if (keys %string == 1) {
 # all equal
}

或更短的版本:

if (keys %{{ map {$_, 1} @test }} == 1) {
 # all equal
}

注意:当在 Perl 中用作字符串时,undefined 值的行为类似于空字符串 ("")。因此,如果数组仅包含空字符串和undefs,则检查将返回 true。

这是一个考虑到这一点的解决方案:

my $is_equal = 0;
my $string   = $test[0]; # the first element

for my $i (0..$#test) {
    last unless defined $string == defined $test[$i];
    last if defined $test[$i] && $test[$i] ne $string;
    $is_equal = 1 if $i == $#test;
}

【讨论】:

  • 不好。即使第一个元素不匹配,您也总是需要遍历整个数组。
  • 在看到这个答案之前我所做的是对数组进行排序并检查第一个和最后一个元素是否相同。感谢您提供带有哈希的代码。我还在学习如何正确使用地图。
  • @Quick Joe Smith:并不是我目前的解决方案缺乏。我只是在寻找不同的方法来做同样的事情,并且基本上使用最短的代码,所以看起来不错:P
  • @codeholic:仅当数组可能异常大时才为“坏”。对于较短的数组,grep 是要走的路。
  • @eugene 你不知道 Wireless 的阵列大小。
【解决方案2】:

如果@test = (undef, ''),接受的帖子中的两种方法都会给你错误的答案。也就是说,它们声明一个未定义的值等于空字符串。

这可能是可以接受的。此外,即使早期发现不匹配,使用grep 也会遍历数组的所有元素,并且使用哈希会使数组元素使用的内存增加一倍以上。如果您有小型阵列,这些都不是问题。而且,grep 对于合理的列表大小可能足够快。

但是,这里有一个替代方法,1) 为 (undef, '')(undef, 0) 返回 false,2) 不会增加程序的内存占用,3) 一旦发现不匹配就会短路:

#!/usr/bin/perl

use strict; use warnings;

# Returns true for an empty array as there exist
# no elements of an empty set that are different
# than each other (see
# http://en.wikipedia.org/wiki/Vacuous_truth)

sub all_the_same {
    my ($ref) = @_;
    return 1 unless @$ref;
    my $cmpv = \ $ref->[-1];
    for my $i (0 .. $#$ref - 1)  {
        my $this = \ $ref->[$i];
        return unless defined $$cmpv == defined $$this;
        return if defined $$this
            and ( $$cmpv ne $$this );
    }
    return 1;
}

但是,使用List::MoreUtils::first_index 可能会更快:

use List::MoreUtils qw( first_index );

sub all_the_same {
    my ($ref) = @_;
    my $first = \ $ref->[0];
    return -1 == first_index {
        (defined $$first != defined)
            or (defined and $_ ne $$first)
    } @$ref;
}

【讨论】:

  • +1 有趣的答案。我在您的第一种方法上发布了一个变体(但如果我忽略了某些内容,请告诉我)。
  • 嗯,你真的应该在发布之前尝试你的代码。你想要'我的 $ref = \@_;'在 vour sub 'my ($ref) = @_;'正在将第一个元素或传递的数组放入 $ref。
  • @htaccess 正是...您应该调用 all_the_same 并引用您正在检查的数组作为唯一的参数。
  • 我明白了,我传递的是一个数组而不是一个引用。使用 @_ 而不是 shift 表明它是一个正在传递的数组。
  • @htaccess 另一方面,分配给名称为$ref 的标量可能被认为足以表明该函数需要一个引用。另外,请注意,自从我回答这个问题以来的四年多时间里,您似乎是唯一对此感到困惑的人。让你假设我发布代码而不运行它有点荒谬。
【解决方案3】:

TIMTOWTDI,我最近读了很多Mark Jason Dominus

use strict;
use warnings;

sub all_the_same {
    my $ref = shift;
    return 1 unless @$ref;
    my $cmp = $ref->[0];
    my $equal = defined $cmp ?
        sub { defined($_[0]) and $_[0] eq $cmp } :
        sub { not defined $_[0] };
    for my $v (@$ref){
        return 0 unless $equal->($v);
    }
    return 1;
}

my @tests = (
    [ qw(foo foo foo) ],
    [ '', '', ''],
    [ undef, undef, undef ],
    [ qw(foo foo bar) ],
    [ '', undef ],
    [ undef, '' ]
);

for my $i (0 .. $#tests){
    print "$i. ", all_the_same($tests[$i]) ? 'equal' : '', "\n";
}

【讨论】:

  • 我用过那个,效果很好。然而,它有一个微小的缺陷。返回除非 $equal->($v);应该返回 0 除非 $equal->($v);
  • @user2050516 在手头的情况下,返回undef0 都没有关系(两者都评估为假)。
  • 直到昨天我也有同样的看法,但正是这个缺陷没有返回 undef 或 0 搞砸了。看下面这两件事,undef 是一个值,什么都不是。返回 undef;返回;它在哪里发挥作用?如果你这样做:@array=(all_the_same([0,1]), all_the_same([1,1]));打印@array,"\n";大惊喜它将在数组中包含一个元素。
  • @user2050516 点数。返回 0 确实使函数在列表上下文中表现得更明智。
【解决方案4】:

您可以通过在哈希 (%seen) 中计数来检查数组 (@test) 中的元素重复了多少次。您可以检查哈希 (%seen) 中存在多少键 ($size)。如果存在超过 1 个键,则表明数组中的元素不相同。

sub all_the_same {
    my @test = @_;
    my %seen;
    foreach my $item (@test){
      $seen{$item}++
    }
    my $size = keys %seen;
    if ($size == 1){
        return 1;
    }
    else{
        return 0;
    }
}

【讨论】:

    【解决方案5】:

    我认为,我们可以使用 List::MoreUtils qw(uniq)

    my @uniq_array = uniq @array;
    my $array_length = @uniq_array;
    $array_length == 1 ? return 1 : return 0;
    

    【讨论】:

      【解决方案6】:

      我将List::Util::first 用于所有类似目的。

      # try #0: $ok = !first { $_ ne $string } @test;
      # try #1: $ok = !first { (defined $_ != defined $string) || !/\A\Q$string\E\z/ } @test;
      
      # final solution
      use List::Util 'first';
      my $str = shift @test;
      my $ok = !first { defined $$_ != defined $str || defined $str && $$_ ne $str } map \$_, @test;
      

      我在这里使用map \$_, @test 来避免计算结果为假的值出现问题。

      注意。正如 cjm 所指出的,使用 map 会破坏第一次短路的优势。所以我用他的first_index 解决方案向思南致敬。

      【讨论】:

      • 但是如果@test 包含空字符串(或0,或undef)怎么办?当$ok 应该为假时,您的测试会将其设置为真。
      • 但这不是错误的测试。事实是first 返回了通过测试的@test 的元素。无法区分 @test 中的 undef 和找不到匹配项。
      • 我已经解决了所有问题。并且不需要正则表达式。虽然解决方案不是很简洁。
      • 我会写那个defined($$_) != defined($str) || defined($str) && $$_ ne $str
      • 现在唯一的问题是使用map 会破坏first 短路的优势。但代码确实有效,所以我删除了我的反对票。
      猜你喜欢
      • 2012-05-04
      • 1970-01-01
      • 1970-01-01
      • 2012-08-21
      • 1970-01-01
      • 2015-02-12
      • 2018-05-13
      • 2011-04-20
      • 1970-01-01
      相关资源
      最近更新 更多