通过引用修改数组

时间:2015-04-26 14:39:16

标签: perl

我正在尝试编写一个接收数组引用的子例程,然后删除该数组的一些元素。例如:

use strict;
use warnings;

my @a = (1, 2, 3, 6);
func1 (\@a);

sub func1 {
    my $a = shift;

    my @b = (2, 6);

    for my $val_to_remove (@b) {
        for my $i (0..$#$a) {
            my $val = $a->[$i];
            if ( $val == $val_to_remove ) {
                splice @$a, $i, 1;
                last;
            }
        }
    }
}

至少可以说,使用两个for循环似乎有些尴尬。 有可能简化这个吗?

我也试过

use strict;
use warnings;

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

my $temp = \@a;
func2 (\$temp );

sub func2 {
    my $a = shift;

    $$a = [2, 6];
}

但后来@a未被修改,而$temp将是..

我还想避免传递对引用的引用,因为这会搞乱其他模块的调用语法。

4 个答案:

答案 0 :(得分:2)

使用哈希作为指标函数,有效识别要删除的项目;使用grep过滤它们:

sub func1 {
    my $a = shift;
    my %b = map { ($_ => 1) } (2, 6);
    @$a = grep { !$b{$_} } @$a;
}

答案 1 :(得分:1)

Loic's solution运作良好且非常易读。除非你使用大型数组导致grep占用大量内存,或者性能绝对至关重要,否则我会推荐它。

使用splice

可以提升性能
use strict;
use warnings;

use Data::Dump;

my @haystack = (1, 2, 3, 6);

my %needle = map { $_ => 1 } (2, 6);

foreach my $i (reverse 0 .. $#haystack) {
    splice @haystack, $i, 1 if exists $needle{ $haystack[$i] };
}

dd \@haystack;

输出:

[1, 3]

请注意,必须以相反的顺序迭代@haystack,因为每次删除元素时,其余元素都会向左移动,从而更改数组索引。

基准

以下是针对PerlMonks上的corrected benchmark编写的BrowserUk foreach array - delete current row ?的略微修改版本的结果。原始基准测试包括从阵列中删除元素的其他几种方法,为简单起见,我将其遗漏了。

$ ./benchmark -N=1e2
              Rate       grep for_splice
grep       40959/s         --       -37%
for_splice 65164/s        59%         --
$ ./benchmark -N=1e3
             Rate       grep for_splice
grep       4072/s         --       -38%
for_splice 6515/s        60%         --
$ ./benchmark -N=1e4
            Rate       grep for_splice
grep       366/s         --       -33%
for_splice 550/s        50%         --
$ ./benchmark -N=1e5
             Rate       grep for_splice
grep       32.7/s         --       -38%
for_splice 52.9/s        62%         --
$ ./benchmark -N=1e6
            (warning: too few iterations for a reliable count)

             Rate       grep for_splice
grep       2.36/s         --       -28%
for_splice 3.28/s        39%         --

基准代码本身:

#!/usr/bin/perl -sl

use strict;
use warnings;

use Benchmark 'cmpthese';

our $N //= 1e3;
our $I //= -1;

# 10% the size of the haystack
my $num_needles = int($N / 10) || 1;

our @as;
@{ $as[ $_ ] } = 1 .. $N for 0 .. 4;

our %needle = map { int(rand($N)) => 1 } 1 .. $num_needles;

cmpthese $I, {
    for_splice => q[
        my $ar = $as[0];
        foreach my $i (reverse 0 .. $#$ar) {
            splice @$ar, $i, 1 if exists $needle{ $ar->[$i] };
        }
        $I == 1 and print "0: ", "@$ar";
    ],
    grep => q[
        my $ar = $as[1];
        @$ar = grep { ! exists $needle{$_} } @$ar;
        $I == 1 and print "1: ", "@$ar";
    ],
};

答案 2 :(得分:1)

如果您还要修改数组的内容,则不能使用简单的for (LIST)循环来迭代数组的索引。这是因为最后一项的索引可能会改变,如果删除当前元素递增计数器,您也将跳过元素。

需要while循环,或等效的C风格for

此程序演示以及uing List::Util::any以检查是否应删除数组元素

use strict;
use warnings;

use List::Util 'any';

my @a = (1, 2, 3, 6);
func1 (\@a);

use Data::Dump;
dd \@a;

sub func1 {
    my ($a) = @_;
    my @b = (2, 6);

    for ( my $i = 0; $i < @$a; ) {
      if ( any { $a->[$i] == $_ } @b ) {
        splice @$a, $i, 1;
      }
      else {
        ++$i;
      }
    }
}

<强>输出

[1, 3]

答案 3 :(得分:0)

对于这种性质的问题,通常更容易构建所需的数组,而不是从现有数组中删除。

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

sub func1 {
  my $aref = shift @_;
  my @b = ( 2, 6 );

  my @results = ();
  for my $item ( @$aref ){
    if( grep { $item == $_ } @b ){
      next;
    }
    push @results, $item;
  }
  return @results;
}

my @results = func1( \@a );
say "@results";