【问题标题】:How can I check if two arrays contain the same elements in Perl?如何在 Perl 中检查两个数组是否包含相同的元素?
【发布时间】:2012-08-21 01:44:04
【问题描述】:

所以我只需要一种简单的方法来判断 perl 中两个数组是否相同。顺序无关紧要,所以我正在寻找这样的东西:

my @a = (1, 2, 3);

my @b = (2, 3, 1);

my @c = (1, 2, 4);

&identical(@a, @b) 返回 1

&identical(@a, @c) 返回 0

谢谢!

【问题讨论】:

  • 您可能想选择一个更好的名称。根据定义,这些数组中没有一个实际上是相同的(包括以相同的顺序具有相同的元素)。如果您不关心顺序,则名称应该反映这一点。
  • 元素是否可以多次出现在数组中?

标签: arrays perl


【解决方案1】:

您可以在哈希中计算元素的计数。有一个 (element => count) 散列,并且每次第一个数组具有该元素时计数增加,并且每次另一个数组具有它时降低计数(反之亦然)。如果两个数组的元素全部相同,则哈希中的每个值都将为 0。

sub have_same_elements {
    my ($arr1, $arr2) = @_;
    my %counts = ();
    $counts{$_} += 1 foreach (@$arr1);
    $counts{$_} -= 1 foreach (@$arr2);
    return !(grep { $_ != 0 } values %counts);
}


$a_and_b_same = have_same_elements(\@a, \@b);  # will be true
$a_and_c_same = have_same_elements(\@a, \@c);  # will be false

(注意,这可能会也可能不适用于自己进行字符串化的对象。哈希键不能是引用,因此 Perl 在使用它们时会将引用字符串化。它的默认字符串化符将引用转换为类似 ARRAY(0x12345678) 的东西,它使引用不同,除非它们指向同一个东西。但是如果一个对象进行自己的字符串化并且不为不同的引用返回不同的字符串,这可能会中断。你知道的。)

【讨论】:

    【解决方案2】:

    如果您使用Perl 5.10 或更高版本(如果不是,您真的应该升级),您可以使用smart match operator

    use strict;
    use warnings;
    
    my @a = (1, 2, 3);
    my @b = (2, 3, 1);
    my @c = (1, 2, 4);
    
    #sort each of them (numerically)
    @a = sort { $a <=> $b } @a;
    @b = sort { $a <=> $b } @b;
    @c = sort { $a <=> $b } @c;
    
    if ( @a ~~ @b ) {
    
        print "\@a and \@b are the same! (after sorting)\n";
    }
    else {
    
        print "nope\n";
    }
    
    if ( @a ~~ @c ) {
    
        print "\@a and \@c are the same! (after sorting)\n";
    }
    else {
    
        print "nope\n";
    }
    

    你也可以滚动你自己的函数:

    use strict;
    use warnings;
    
    my @a = (1, 2, 3);
    my @b = (2, 3, 1);
    my @c = (1, 2, 4);
    
    print same_elements(\@a, \@b) . "\n";
    print same_elements(\@a, \@c) . "\n";
    
    #arguments are two array references
    sub same_elements {
    
        my $array_ref_1 = shift;
        my $array_ref_2 = shift;
        my @arr1 = @$array_ref_1;
        my @arr2 = @$array_ref_2;
    
        #If they are not the same length, we are done.
        if( scalar(@arr1) != scalar(@arr2) ) {
    
           return 0;
        }
    
        #sort them!
        @arr1 = sort { $a <=> $b } @arr1;
        @arr2 = sort { $a <=> $b } @arr2;
    
        foreach my $i( 0 .. $#arr1 ) {
    
            if ( $arr1[$i] != $arr2[$i] ) {
                return 0;
            }
        }
        return 1;
    }
    

    【讨论】:

      【解决方案3】:

      首先,您将不得不重新考虑您的功能。

       identical(@a, @b);
      

      不向函数传递两个数组,而是传递一个数组,其中包含两个数组中的所有元素。好像你说:

      identical(1, 2, 3, 2, 3, 1);
      

      为了使您的函数正常工作,您必须将 references 传递给您的数组:

      identical(\@a, \@b);
      

      我会告诉prototype 你的子程序,但这可能会导致你more problems 它会解决。

      如果顺序不重要,请先对数组进行排序,然后再进行比较。你甚至可以作弊......

      sub identical {
         my $array_ref_1 = shift;
         my $array_fef_2 = shift;
      
         use Digest::SHA qw(sha1_hex);
      
         if ( ref( $array_ref_1 ) ne "ARRAY") or ( ref( $array_ref_2 ) ne "ARRAY") {
            return;   #Error, need two array references
        }
      
        # Dereference Arrays
        my @array_1 = @{$array_ref_1};
        my @array_2 = @{$array_ref_2};
      
        # Setup Arrays to be one big scalar
        my $scalar_1 = join "\n", sort @array_1;
        my $scalar_2 = join "\n", sort @array_2;
      
        my $checksum_1 = sha1_hex $scalar_1;
        my $checksum_2 = sha1_hex $scalar_2;
      
        if ($checksum_1 eq $checksum_2) {
           return 1;
        }
        else {
           return 0_but_true;
      

      几点说明:

      • 我可以在一个语句中取消引用、加入、生成校验和并进行比较。我分别做了它们,以便更清楚地说明我在做什么。以编程方式,它可能没有任何区别。 Perl 无论如何都会优化整个事情。我总是追求清晰。
      • 0_but_true 返回一个 0,但同时返回一个真值。这样,您可以执行if ( identical( \@A, \@B ) ) { 之类的操作来确保该功能正常工作。然后,您可以测试零或一。
      • 请务必测试您的参数。我使用了ref 函数来执行此操作。
      • 作弊。我首先将两个排序数组转换为标量。然后,我使用 sha1 校验和来验证它们是否相同。使用sha1 函数的校验和应该相当不错。失败的可能性很小。

      真正的问题是如果你有这样的多行数组:

       @a = ("this", "that", "the\nother");
       @b = ("this", "that\nthe", "other");
      

      按照我的方式使用join 会导致结果标量相等。

      【讨论】:

        【解决方案4】:

        我想你可以这样写,对你正在处理的输入类型做出最少的假设(只需传递适当的比较子):

        use List::Util;
        sub identical {
          my @this = @{ +shift };
          my @that = @{ +shift };
          my $cmp = shift // sub { shift eq shift };
          return '' unless @this == @that;
          for my $idx (List::Util::shuffle keys @this) {
            return '' unless $cmp->($this[$idx], $that[$idx]);
          }
          return 1;
        }
        

        行为如下:

        0> identical([0..100], [0..100])
        $res[0] = 1
        
        1> identical([0..100], ['0.0', 1..100])
        $res[1] = ''
        
        2> identical([0..100], ['0.0', 1..100], sub {shift == shift})
        $res[2] = 1
        
        3> identical(['0.66666666666', 0..10], [2/3, 0..10], sub {shift == shift})
        $res[3] = ''
        
        4> identical(['0.66666666666', 0..10], [2/3, 0..10], sub {shift() - shift() < 1e-5})
        $res[4] = 1
        
        # if you need this to be true check out https://stackoverflow.com/a/12127428/13992
        5> identical([0..100], [List::Util::shuffle(0..100)])
        $res[5] = ''
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2023-03-18
          • 2019-08-24
          • 2012-05-17
          • 2019-04-27
          • 2011-01-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多