在Perl中从数组中删除值的最佳方法是什么?


80

该数组有很多数据,我需要删除两个元素。

以下是我正在使用的代码段,

my @array = (1,2,3,4,5,5,6,5,4,9);
my $element_omitted = 5;
@array = grep { $_ != $element_omitted } @array;

3
这将删除三个元素。
Medlock Perlman

所需的顶部删除所有非文件项目表单目录列表,“ array = grep {-f $ _} array”对我来说就像是一种魅力:)
taiko 2015年

Answers:


85

如果您已经知道要删除的元素的索引,请使用splice。

如果您正在搜索,则Grep可以工作。

如果您需要做很多这样的事情,那么将数组保持在已排序的顺序中,将会获得更好的性能,因为您可以进行二进制搜索来找到必要的索引。

如果在您的上下文中有意义,则可能要考虑对已删除的记录使用“魔术值”,而不是删除它们,以节省数据移动-例如,将已删除的元素设置为undef。当然,这有其自身的问题(如果您需要了解“实时”元素的数量,需要分别对其进行跟踪等),但是根据您的应用程序可能会遇到麻烦。

编辑实际上,现在让我再看一遍-不要使用上面的grep代码。找到要删除的元素的索引,然后使用splice删除它,效率会更高(您的代码会累积所有不匹配的结果。)

my $index = 0;
$index++ until $arr[$index] eq 'foo';
splice(@arr, $index, 1);

那将删除第一次出现。删除所有出现的事件非常相似,除了您将希望一次性获得所有索引:

my @del_indexes = grep { $arr[$_] eq 'foo' } 0..$#arr;

其余内容供读者练习-请记住,阵列在拼接时会发生变化!

Edit2 John Siracusa正确指出我的示例中有一个错误。.修复,对此感到抱歉。


13
如果找不到该字符串,则循环将卡住,我的$ index = 0; 我的$ count =标量@arr; $ index ++,直到$ arr [$ index] eq'foo'或$ index == $ count; splice(@arr,$ index,1);
2013年

1
my ($index) = grep { $arr[$_] eq 'foo' } 0..$#arr; if (defined $index) {splice(@arr, $index, 1); }-第一场比赛
反射性的

13

拼接将按索引删除数组元素。如您的示例一样,使用grep进行搜索和删除。


谢谢spoulson。我没有要删除的索引,因此我不得不求助于grep。
user21246

8

这是您要做的很多事情吗?如果是这样,您可能需要考虑其他数据结构。Grep每次都会搜索整个数组,而大型数组可能会非常昂贵。如果速度是一个问题,那么您可能要考虑使用哈希。

在您的示例中,键将是数字,值将是该数字的元素计数。


5

如果你改变

my @del_indexes = grep { $arr[$_] eq 'foo' } 0..$#arr;

my @del_indexes = reverse(grep { $arr[$_] eq 'foo' } 0..$#arr);

通过首先从阵列背面移除元素,可以避免阵列重新编号的问题。将splice()放入foreach循环中可以清除@arr。相对简单易读...

foreach $item (@del_indexes) {
   splice (@arr,$item,1);
}

5

您可以使用数组切片而不是拼接。Grep返回要保留的索引并使用切片:

my @arr = ...;
my @indicesToKeep = grep { $arr[$_] ne 'foo' } 0..$#arr;
@arr = @arr[@indiciesToKeep];

我特别喜欢这种方法的逻辑性和优雅性。
凯夫

是的,的确是的,您甚至可以将它写为“单线”:@arr = @arr[grep ...]我特别喜欢。我不确定它的效率如何,但是我将开始使用它,因为它不会比其他解决方案差。
soger

3

我认为您的解决方案是最简单,最可维护的。

文章的其余部分记录了将元素的测试转换为splice偏移量的难度。因此,使其成为更完整的答案。

查看您必须经过的旋转,以拥有有效的算法(即一次通过),以将列表项上的测试转换为索引。而且根本不是那么直观。

sub array_remove ( \@& ) { 
    my ( $arr_ref, $test_block ) = @_;
    my $sp_start  = 0;
    my $sp_len    = 0;
    for ( my $inx = 0; $inx <= $#$arr_ref; $inx++ ) {
        local $_ = $arr_ref->[$inx];
        next unless $test_block->( $_ );
        if ( $sp_len > 0 && $inx > $sp_start + $sp_len ) {
            splice( @$arr_ref, $sp_start, $sp_len );
            $inx    = $inx - $sp_len;
            $sp_len = 0;
        }
        $sp_start = $inx if ++$sp_len == 1;
    }
    splice( @$arr_ref, $sp_start, $sp_len ) if $sp_len > 0;
    return;
}

2
一个简单的“ grep”将比这更容易理解和更有效。
兰达·施瓦兹

5
有人删除了我的评论,即您显然没有阅读该文本。
Axeman

2

我用:

delete $array[$index];

Perldoc删除


9
数组值的删除可能已被弃用(请参阅您的文档)
e2-e4

3
这只会删除存储在该数组索引处的值。至少在我的perl版本中(5.14)
Rooster

这并没有真正删除您的想法。它仅删除值,使其成为undef。此外,在ringø链接的文档中:“警告:强烈建议不要对数组值调用delete。删除或检查Perl数组元素的存在的概念在概念上并不统一,可能导致令人惊讶的行为。” (文档的上一段包含了所有详细信息)。
mivk

2

如果数组,则删除所有出现的“某物”。

根据SquareCog的答案:

my @arr = ('1','2','3','4','3','2', '3','4','3');
my @dix = grep { $arr[$_] eq '4' } 0..$#arr;
my $o = 0;
for (@dix) {
    splice(@arr, $_-$o, 1);
    $o++;
}
print join("\n", @arr);

每次我们从中删除索引时@arr,下一个要删除的正确索引将为$_-current_loop_step


2

您可以使用非捕获组和要删除的管道delim列表。


perl -le '@ar=(1 .. 20);@x=(8,10,3,17);$x=join("|",@x);@ar=grep{!/^(?:$x)$/o} @ar;print "@ar"'

2

我发现最好的是“ undef”和“ grep”的组合:

foreach $index ( @list_of_indexes_to_be_skiped ) {
      undef($array[$index]);
}
@array = grep { defined($_) } @array;

绝招!费德里科


undef将元素值设置为null。总元素(大小)仍然相同。
Boontawee Home's

1
@BoontaweeHome,grep最后将其删除。
Deanna

1

可以肯定的是,我已经对grep和map解决方案进行了基准测试,首先搜索匹配元素的索引(要删除的元素),然后通过grep直接删除元素,而无需搜索索引。我看来,Sam提出问题时提出的第一个解决方案已经是最快的。

    use Benchmark;
    my @A=qw(A B C A D E A F G H A I J K L A M N);
    my @M1; my @G; my @M2;
    my @Ashrunk;
    timethese( 1000000, {
      'map1' => sub {
          my $i=0;
          @M1 = map { $i++; $_ eq 'A' ? $i-1 : ();} @A;
      },
      'map2' => sub {
          my $i=0;
          @M2 = map { $A[$_] eq 'A' ? $_ : () ;} 0..$#A;
      },
      'grep' => sub {
          @G = grep { $A[$_] eq 'A' } 0..$#A;
      },
      'grem' => sub {
          @Ashrunk = grep { $_ ne 'A' } @A;
      },
    });

结果是:

Benchmark: timing 1000000 iterations of grem, grep, map1, map2...
  grem:  4 wallclock secs ( 3.37 usr +  0.00 sys =  3.37 CPU) @ 296823.98/s (n=1000000)
  grep:  3 wallclock secs ( 2.95 usr +  0.00 sys =  2.95 CPU) @ 339213.03/s (n=1000000)
  map1:  4 wallclock secs ( 4.01 usr +  0.00 sys =  4.01 CPU) @ 249438.76/s (n=1000000)
  map2:  2 wallclock secs ( 3.67 usr +  0.00 sys =  3.67 CPU) @ 272702.48/s (n=1000000)
M1 = 0 3 6 10 15
M2 = 0 3 6 10 15
G = 0 3 6 10 15
Ashrunk = B C D E F G H I J K L M N

如经过的时间所示,尝试使用grep或map定义的索引来实现remove函数是没有用的。只需grep-remove直接。

在测试之前,我认为“ map1”将是最有效的……我猜应该更多地依赖于Benchmark。;-)


0

如果知道数组索引,则可以删除()。splice()和delete()之间的区别在于delete()不会对数组的其余元素重新编号。


我实际上是指重新编号,根据Perldoc,splice()确实可以。
Powerlord

0

我曾经写过一个类似的代码,用于从字符串数组中删除不是以SB.1开头的字符串

my @adoSymbols=('SB.1000','RT.10000','PC.10000');
##Remove items from an array from backward
for(my $i=$#adoSymbols;$i>=0;$i--) {  
    unless ($adoSymbols[$i] =~ m/^SB\.1/) {splice(@adoSymbols,$i,1);}
}

0

您可以简单地做到这一点:

my $input_Color = 'Green';
my @array = qw(Red Blue Green Yellow Black);
@array = grep {!/$input_Color/} @array;
print "@array";
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.