Cách tốt nhất để xóa một giá trị khỏi một mảng trong Perl là gì?


81

Mảng có nhiều dữ liệu và tôi cần xóa hai phần tử.

Dưới đây là đoạn mã tôi đang sử dụng,

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

3
Thao tác này sẽ xóa ba phần tử.
Medlock Perlman

đầu cần thiết loại bỏ tất cả các thư mục biểu mẫu đặt hàng phi tập tin danh sách và "mảng = grep {-f $ _} mảng" làm việc như một nét duyên dáng cho tôi :)
taiko

Câu trả lời:


86

Sử dụng mối nối nếu bạn đã biết chỉ mục của phần tử bạn muốn xóa.

Grep hoạt động nếu bạn đang tìm kiếm.

Nếu bạn cần thực hiện nhiều điều này, bạn sẽ nhận được hiệu suất tốt hơn nhiều nếu bạn giữ mảng của mình theo thứ tự được sắp xếp, vì sau đó bạn có thể thực hiện tìm kiếm nhị phân để tìm chỉ mục cần thiết.

Nếu nó có ý nghĩa trong ngữ cảnh của bạn, bạn có thể muốn xem xét sử dụng một "giá trị ma thuật" cho các bản ghi đã xóa, thay vì xóa chúng, để tiết kiệm cho việc di chuyển dữ liệu - chẳng hạn như đặt các phần tử đã xóa thành undef. Đương nhiên, điều này có những vấn đề riêng (nếu bạn cần biết số lượng phần tử "sống", bạn cần theo dõi riêng, v.v.), nhưng có thể đáng gặp rắc rối tùy thuộc vào ứng dụng của bạn.

Chỉnh sửa Thực ra bây giờ tôi xem xét lại lần thứ hai - không sử dụng mã grep ở trên. Sẽ hiệu quả hơn nếu tìm chỉ mục của phần tử bạn muốn xóa, sau đó sử dụng mối nối để xóa nó (mã bạn đã tích lũy tất cả các kết quả không khớp ..)

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

Điều đó sẽ xóa lần xuất hiện đầu tiên. Việc xóa tất cả các lần xuất hiện rất giống nhau, ngoại trừ bạn sẽ muốn nhận tất cả các chỉ mục trong một lần vượt qua:

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

Phần còn lại được để lại như một bài tập cho người đọc - hãy nhớ rằng mảng thay đổi khi bạn nối nó!

Edit2 John Siracusa đã chỉ ra một cách chính xác rằng tôi có một lỗi trong ví dụ của mình .. đã sửa, xin lỗi về điều đó.


13
nếu chuỗi không được tìm thấy, vòng lặp sẽ bị kẹt, vì vậy $ index = 0 của tôi; $ count của tôi = vô hướng @arr; $ index ++ cho đến khi $ arr [$ index] eq 'foo' hoặc $ index == $ count; splice (@arr, $ index, 1);
Amir.F

1
hoặc my ($index) = grep { $arr[$_] eq 'foo' } 0..$#arr; if (defined $index) {splice(@arr, $index, 1); }- cho trận đấu đầu tiên
Reflective

13

splice sẽ loại bỏ (các) phần tử mảng theo chỉ mục. Sử dụng grep, như trong ví dụ của bạn, để tìm kiếm và xóa.


Cảm ơn spoulson. Tôi không có các chỉ số mà tôi phải xóa và vì vậy tôi phải dùng đến grep.
user21246

8

Đây có phải là điều bạn sẽ làm nhiều không? Nếu vậy, bạn có thể muốn xem xét một cấu trúc dữ liệu khác. Grep sẽ tìm kiếm toàn bộ mảng mọi lúc và đối với một mảng lớn có thể khá tốn kém. Nếu tốc độ là một vấn đề thì bạn có thể cân nhắc sử dụng Hash thay thế.

Trong ví dụ của bạn, khóa sẽ là số và giá trị sẽ là số phần tử của số đó.


5

nếu bạn thay đổi

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

đến

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

Điều này tránh được vấn đề đánh số lại mảng bằng cách xóa các phần tử ở phía sau mảng trước. Đặt một splice () trong một vòng lặp foreach sẽ xóa @arr. Tương đối đơn giản và dễ đọc ...

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

5

Bạn có thể sử dụng phương pháp cắt mảng thay vì ghép nối. Grep để trả về các chỉ số bạn muốn giữ lại và sử dụng tính năng cắt:

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

Tôi đặc biệt thích sự logic và sang trọng của cách tiếp cận này.
Keve

Đúng vậy, bạn thậm chí có thể viết nó dưới dạng một chữ cái lót như: @arr = @arr[grep ...]cái mà tôi đặc biệt thích. Tôi không chắc nó hiệu quả đến mức nào, nhưng tôi sẽ bắt đầu sử dụng nó vì nó không thể tệ hơn các giải pháp khác.
soger

3

Tôi nghĩ rằng giải pháp của bạn là đơn giản nhất và dễ bảo trì nhất.

Phần còn lại của bài viết ghi lại sự khó khăn của việc biến các bài kiểm tra trên các phần tử thành splicehiệu số. Do đó, làm cho nó trở thành một câu trả lời đầy đủ hơn .

Nhìn vào các vòng quay mà bạn phải trải qua để có một thuật toán hiệu quả (tức là một lần vượt qua) để biến các bài kiểm tra trên các mục danh sách thành chỉ mục. Và nó không trực quan chút nào.

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
Một "grep" đơn giản sẽ dễ hiểu và hiệu quả hơn rất nhiều.
Randal Schwartz

5
Ai đó đã xóa bình luận của tôi rằng bạn rõ ràng không đọc văn bản.
Axeman 8/10/08

2

Tôi sử dụng:

delete $array[$index];

Perldoc xóa .


9
xóa trên giá trị mảng có khả năng được phản (xem doc của bạn)
e2-e4

3
điều này chỉ xóa giá trị được lưu trữ tại chỉ mục mảng đó. ít nhất là trong phiên bản perl của tôi, (5.14)
Quý Dậu

Điều này KHÔNG thực sự xóa những gì bạn nghĩ. Nó chỉ xóa giá trị, tạo ra nó undef. Bên cạnh đó, từ tài liệu được liên kết bởi ringø: "CẢNH BÁO: Việc gọi xóa trên các giá trị mảng không được khuyến khích. Khái niệm xóa hoặc kiểm tra sự tồn tại của các phần tử mảng Perl không nhất quán về mặt khái niệm và có thể dẫn đến hành vi đáng ngạc nhiên." (đoạn trước trong tài liệu có tất cả các chi tiết đẫm máu).
mivk

2

Xóa tất cả các lần xuất hiện của mảng if 'something'.

Dựa trên câu trả lời của 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);

Mỗi lần chúng tôi xóa chỉ mục khỏi @arr, chỉ mục chính xác tiếp theo để xóa sẽ là $_-current_loop_step.


2

Bạn có thể sử dụng nhóm không chụp và danh sách các mục được phân tách bằng dấu sổ đứng để xóa.


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

2

Điều tốt nhất tôi tìm thấy là sự kết hợp của "undef" và "grep":

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

Đó là thủ thuật! Federico


undef đặt giá trị phần tử thành null. Tổng số phần tử (kích thước) vẫn như nhau.
Boontawee Home

1
@BoontaweeHome, phần grepcuối sau đó xóa chúng.
Deanna

1

Chỉ để chắc chắn rằng tôi đã đánh giá các giải pháp bản đồ và grep, trước tiên tìm kiếm chỉ mục của các phần tử phù hợp (những phần tử cần loại bỏ) và sau đó trực tiếp loại bỏ các phần tử bằng grep mà không cần tìm kiếm chỉ mục. Tôi có vẻ rằng giải pháp đầu tiên do Sam đề xuất khi hỏi câu hỏi của anh ấy đã là nhanh nhất.

    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;
      },
    });

Kết quả là:

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

Như được hiển thị theo thời gian đã trôi qua, thật vô ích nếu cố gắng triển khai chức năng loại bỏ bằng cách sử dụng chỉ mục được xác định bởi bản đồ hoặc bản đồ. Chỉ cần loại bỏ trực tiếp grep.

Trước khi thử nghiệm, tôi đã nghĩ rằng "map1" sẽ là hiệu quả nhất ... Tôi đoán tôi nên thường xuyên dựa vào Điểm chuẩn hơn. ;-)


0

Nếu bạn biết chỉ số mảng, bạn có thể xóa () nó. Sự khác biệt giữa splice () và delete () là delete () không đánh số lại các phần tử còn lại của mảng.


Tôi thực sự có ý định đánh số lại, theo Perldoc, splice () có.
Powerlord 06/10/08

0

Một mã tương tự mà tôi đã từng viết để loại bỏ các chuỗi không bắt đầu bằng SB.1 khỏi một mảng chuỗi

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

Bạn chỉ có thể làm điều này:

my $input_Color = 'Green';
my @array = qw(Red Blue Green Yellow Black);
@array = grep {!/$input_Color/} @array;
print "@array";
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.