Cách tốt nhất để lặp qua mảng Perl


93

Cách triển khai nào tốt nhất (về tốc độ và sử dụng bộ nhớ) để lặp qua mảng Perl? Có cách nào tốt hơn không? ( @Arraykhông cần giữ lại).

Thực hiện 1

foreach (@Array)
{
      SubRoutine($_);
}

Thực hiện 2

while($Element=shift(@Array))
{
      SubRoutine($Element);
}

Thực hiện 3

while(scalar(@Array) !=0)
{
      $Element=shift(@Array);
      SubRoutine($Element);
}

Thực hiện 4

for my $i (0 .. $#Array)
{
      SubRoutine($Array[$i]);
}

Thực hiện 5

map { SubRoutine($_) } @Array ;

2
Tại sao sẽ có một "tốt nhất"? Đặc biệt là cho rằng chúng tôi không có ý tưởng làm thế nào bạn sẽ đo một chống lại khác (là tốc độ quan trọng hơn sử dụng bộ nhớ là? mapVà câu trả lời có thể chấp nhận ?. vv)
Max Lybbert

2
Hai trong số ba người bạn đã đăng sẽ khiến tôi đi "WTH ?!" trừ khi có bối cảnh xung quanh bổ sung để biến chúng thành những lựa chọn thay thế hợp lý. Trong mọi trường hợp, câu hỏi này ở mức độ " Cách tốt nhất để cộng hai số là gì? " Hầu hết thời gian, chỉ có một cách. Sau đó, có những trường hợp, nơi bạn cần một cách khác. Biểu quyết đóng.
Sinan Ünür

4
@ SinanÜnür Tôi đồng cảm với ý kiến ​​của bạn (rằng chỉ có một cách để thêm hai số), nhưng phép loại suy không đủ mạnh để sử dụng một cách bác bỏ. Rõ ràng, có nhiều cách, và OP muốn hiểu đâu là ý kiến ​​hay và đâu là ý kiến ​​không hay.
CodeClown42

2
Chương 24 của ấn bản thứ ba của Perl Lập trình có một phần về hiệu quả là một điều nên đọc. Nó giải quyết các loại hiệu quả khác nhau như thời gian, người lập trình, người bảo trì. Phần này bắt đầu bằng tuyên bố "Lưu ý rằng việc tối ưu hóa theo thời gian đôi khi có thể khiến bạn tốn không gian hoặc hiệu quả của lập trình viên (được chỉ ra bởi các gợi ý mâu thuẫn bên dưới). Chúng chính là lỗi."

1
Một 1 cách để cộng hai số? Không, nếu bạn nhìn vào thấp hơn các cuộc gọi tốt nghiệp / Thực hiện thực .... nghĩ carry lookahead, carry tiết kiệm adders, vv
workwise

Câu trả lời:


76
  • Về tốc độ: # 1 và # 4, nhưng không nhiều trong hầu hết các trường hợp.

    Bạn có thể viết một điểm chuẩn để xác nhận, nhưng tôi nghi ngờ bạn sẽ thấy # 1 và # 4 nhanh hơn một chút vì công việc lặp được thực hiện bằng C thay vì Perl và không cần sao chép các phần tử mảng. ( $_được đặt biệt danh cho phần tử trong # 1, nhưng # 2 và # 3 thực sự sao chép các đại lượng vô hướng từ mảng.)

    # 5 có thể tương tự.

  • Về sử dụng bộ nhớ: Tất cả chúng đều giống nhau ngoại trừ # 5.

    for (@a)là lớp vỏ đặc biệt để tránh làm phẳng mảng. Vòng lặp lặp qua các chỉ mục của mảng.

  • Về khả năng đọc: # 1.

  • Về tính linh hoạt: # 1 / # 4 và # 5.

    # 2 không hỗ trợ các phần tử sai. # 2 và # 3 là hủy diệt.


3
Chà, bạn đã thêm vô số thông tin cho xe tải bằng những câu ngắn gọn và đơn giản.
jaypal singh

1
# 2 là tốt khi bạn xếp hàng (ví dụ: tìm kiếm theo chiều rộng-đầu tiên):my @todo = $root; while (@todo) { my $node = shift; ...; push @todo, ...; ...; }
ikegami

Việc triển khai 4 không tạo ra một mảng chỉ số trung gian, có thể giới thiệu một lượng lớn bộ nhớ được sử dụng? Nếu vậy, có vẻ như người ta không nên sử dụng cách tiếp cận đó. stackoverflow.com/questions/6440723/… rt.cpan.org/Public/Bug/Display.html?id=115863
Thorsten Schöning

@ikegami Đúng với phong cách vô địch của bạn - câu trả lời tuyệt vời :)
xiên que

26

Nếu bạn chỉ quan tâm đến các yếu tố của @Array, hãy sử dụng:

for my $el (@Array) {
# ...
}

hoặc là

Nếu các chỉ số quan trọng, hãy sử dụng:

for my $i (0 .. $#Array) {
# ...
}

Hoặc, kể từ perl5.12.1, bạn có thể sử dụng:

while (my ($i, $el) = each @Array) {
# ...
}

Nếu bạn cần cả phần tử và chỉ mục của nó trong phần thân của vòng lặp, Tôi mong chờ sử dụng each nhanh nhất, nhưng sau đóbạn sẽ từ bỏ khả năng tương thích với phiên bản trước 5.12.1 perl.

Một số mẫu khác ngoài những mẫu này có thể phù hợp trong những trường hợp nhất định.


Tôi sẽ mong đợi eachlà chậm nhất. Nó thực hiện tất cả công việc của những người khác trừ một bí danh, cộng với một phép gán danh sách, hai bản sao vô hướng và hai phân tách vô hướng.
ikegami

Và, theo khả năng đo lường tốt nhất của tôi, bạn đã đúng. Nhanh hơn khoảng 45% khi forlặp qua các chỉ số của một mảng và nhanh hơn 20% khi lặp qua các chỉ số của tham chiếu mảng (tôi thực hiện quyền truy cập $array->[$i]trong phần thân), sử dụng eachkết hợp với while.
Sinan Ünür

3

IMO, triển khai số 1 là điển hình và ngắn gọn và dễ hiểu đối với Perl hơn hẳn những người khác về điều đó. Điểm chuẩn của ba lựa chọn có thể cung cấp cho bạn cái nhìn sâu sắc về tốc độ, ít nhất.


2

1 về cơ bản khác với 2 và 3, vì nó giữ nguyên mảng, trong khi hai mảng còn lại để trống.

Tôi muốn nói rằng # 3 là khá lập dị và có lẽ kém hiệu quả hơn, vì vậy hãy quên điều đó đi.

Cái nào để lại cho bạn vị trí # 1 và # 2, và chúng không làm điều tương tự, vì vậy cái nào không thể "tốt hơn" cái kia. Nếu mảng lớn và bạn không cần phải giữ nó, nói chung phạm vi sẽ giải quyết nó ( nhưng xem LƯU Ý ), vì vậy , nói chung , # 1 vẫn là phương pháp rõ ràng và đơn giản nhất. Di chuyển từng phần tử sẽ không tăng tốc bất cứ điều gì. Ngay cả khi cần giải phóng mảng khỏi tham chiếu, tôi chỉ cần đi:

undef @Array;

khi hoàn thành.

  • LƯU Ý : Chương trình con chứa phạm vi của mảng thực sự giữ nguyên mảng và sử dụng lại khoảng trống vào lần sau. Nói chung , điều đó sẽ ổn (xem phần bình luận).

@Array = ();không giải phóng mảng bên dưới. Thậm chí không đi ra ngoài phạm vi sẽ làm điều đó. Nếu bạn muốn giải phóng mảng bên dưới, bạn phải sử dụng undef @Array;.
ikegami

2
Bản giới thiệu; perl -MDevel::Peek -e'my @a; Dump(\@a,1); @a=qw( a b c ); Dump(\@a,1); @a=(); Dump(\@a,1); undef @a; Dump(\@a,1);' 2>&1 | grep ARRAY
ikegami

GÌ??? Tôi đã nghĩ rằng toàn bộ điểm của GC đã từng là số tham chiếu == 0, bộ nhớ liên quan sẽ có thể tái chế.
CodeClown42

@ikegami: Tôi hiểu điều đó về ()vs undef, nhưng nếu đi ra khỏi phạm vi không giải phóng bộ nhớ được sử dụng bởi một mảng cục bộ vào phạm vi đó, điều đó có làm cho perl trở thành một thảm họa rò rỉ không? Điều đó không thể là sự thật.
CodeClown42

Chúng cũng không bị rò rỉ. Sub vẫn sở hữu chúng và sẽ sử dụng lại chúng vào lần sau khi sub được gọi. Tối ưu hóa cho tốc độ.
ikegami

1

Trong một dòng để in phần tử hoặc mảng.

print $ _ for (@array);

LƯU Ý: hãy nhớ rằng $ _ đang tham chiếu nội bộ đến phần tử của @array trong vòng lặp. Mọi thay đổi được thực hiện trong $ _ sẽ phản ánh trong @array; Ví dụ.

my @array = qw( 1 2 3 );
for (@array) {
        $_ = $_ *2 ;
}
print "@array";

đầu ra: 2 4 6


0

Cách tốt nhất để quyết định những câu hỏi như thế này để đánh giá chúng:

use strict;
use warnings;
use Benchmark qw(:all);

our @input_array = (0..1000);

my $a = sub {
    my @array = @{[ @input_array ]};
    my $index = 0;
    foreach my $element (@array) {
       die unless $index == $element;
       $index++;
    }
};

my $b = sub {
    my @array = @{[ @input_array ]};
    my $index = 0;
    while (defined(my $element = shift @array)) {
       die unless $index == $element;
       $index++;
    }
};

my $c = sub {
    my @array = @{[ @input_array ]};
    my $index = 0;
    while (scalar(@array) !=0) {
       my $element = shift(@array);
       die unless $index == $element;
       $index++;
    }
};

my $d = sub {
    my @array = @{[ @input_array ]};
    foreach my $index (0.. $#array) {
       my $element = $array[$index];
       die unless $index == $element;
    }
};

my $e = sub {
    my @array = @{[ @input_array ]};
    for (my $index = 0; $index <= $#array; $index++) {
       my $element = $array[$index];
       die unless $index == $element;
    }
};

my $f = sub {
    my @array = @{[ @input_array ]};
    while (my ($index, $element) = each @array) {
       die unless $index == $element;
    }
};

my $count;
timethese($count, {
   '1' => $a,
   '2' => $b,
   '3' => $c,
   '4' => $d,
   '5' => $e,
   '6' => $f,
});

Và chạy điều này trên perl 5, phiên bản 24, subversion 1 (v5.24.1) được xây dựng cho x86_64-linux-gnu-thread-multi

Tôi có:

Benchmark: running 1, 2, 3, 4, 5, 6 for at least 3 CPU seconds...
         1:  3 wallclock secs ( 3.16 usr +  0.00 sys =  3.16 CPU) @ 12560.13/s (n=39690)
         2:  3 wallclock secs ( 3.18 usr +  0.00 sys =  3.18 CPU) @ 7828.30/s (n=24894)
         3:  3 wallclock secs ( 3.23 usr +  0.00 sys =  3.23 CPU) @ 6763.47/s (n=21846)
         4:  4 wallclock secs ( 3.15 usr +  0.00 sys =  3.15 CPU) @ 9596.83/s (n=30230)
         5:  4 wallclock secs ( 3.20 usr +  0.00 sys =  3.20 CPU) @ 6826.88/s (n=21846)
         6:  3 wallclock secs ( 3.12 usr +  0.00 sys =  3.12 CPU) @ 5653.53/s (n=17639)

Vì vậy, 'foreach (@Array)' nhanh gấp đôi so với những cái khác. Tất cả những cái khác đều rất giống nhau.

@ikegami cũng chỉ ra rằng có khá nhiều điểm khác biệt trong những áp lực này ngoài tốc độ.


1
So sánh $index < $#arraythực sự nên là $index <= $#array$#arraykhông phải là độ dài của mảng mà là chỉ số cuối cùng của nó.
josch
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.