วิธีที่ดีที่สุดในการวนซ้ำผ่านอาร์เรย์ Perl


96

การใช้งานแบบใดที่ดีที่สุด (ในแง่ของความเร็วและการใช้หน่วยความจำ) สำหรับการทำซ้ำผ่านอาร์เรย์ Perl มีวิธีไหนที่ดีกว่านี้ไหม? (@Arrayไม่จำเป็นต้องเก็บรักษาไว้)

การนำไปใช้ 1

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

การนำไปใช้งาน 2

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

การนำไปใช้ 3

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

การนำไปใช้งาน 4

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

การนำไปใช้ 5

map { SubRoutine($_) } @Array ;

2
ทำไมถึงมี "ดีที่สุด"? โดยเฉพาะอย่างยิ่งเนื่องจากเราไม่รู้ว่าคุณจะวัดกันอย่างไร (ความเร็วสำคัญกว่าการใช้หน่วยความจำหรือไม่เป็นmapและคำตอบที่ยอมรับได้หรือไม่ ฯลฯ )
Max Lybbert

2
สองในสามที่คุณโพสต์จะทำให้ฉันไป "WTH ?!" เว้นแต่จะมีบริบทรอบข้างเพิ่มเติมเพื่อให้เป็นทางเลือกที่สมเหตุสมผล ไม่ว่าในกรณีใดคำถามนี้จะอยู่ที่ระดับ " อะไรคือวิธีที่ดีที่สุดในการบวกตัวเลขสองตัว " โดยส่วนใหญ่มีเพียงวิธีเดียว จากนั้นมีสถานการณ์เหล่านั้นที่คุณต้องการวิธีอื่น ปิดการลงคะแนน
Sinan Ünür

4
@ SinanÜnürฉันเห็นอกเห็นใจกับความคิดเห็นของคุณ (มีเพียงวิธีเดียวที่จะเพิ่มตัวเลขสองตัว) แต่การเปรียบเทียบนั้นไม่หนักแน่นพอที่จะใช้อย่างไม่สนใจ เห็นได้ชัดว่ามีมากกว่าหนึ่งวิธีและ OP ต้องการทำความเข้าใจว่าอะไรเป็นความคิดที่ดีและอะไรที่ไม่ใช่
CodeClown42

2
บทที่ 24 ของการเขียนโปรแกรม Perl รุ่นที่สามมีส่วนเกี่ยวกับประสิทธิภาพที่น่าอ่าน กล่าวถึงประสิทธิภาพประเภทต่างๆเช่นเวลาโปรแกรมเมอร์ผู้ดูแลรักษา ส่วนนี้เริ่มต้นด้วยข้อความว่า "โปรดทราบว่าบางครั้งการปรับให้เหมาะสมกับเวลาอาจทำให้คุณเสียค่าใช้จ่ายในพื้นที่หรือประสิทธิภาพของโปรแกรมเมอร์ (ระบุโดยคำใบ้ที่ขัดแย้งกันด้านล่าง) พวกเขาคือช่วงพัก"

1
วิธีหนึ่ง 1 ในการบวกสองจำนวน? ไม่ใช่ถ้าคุณมองไปที่การโทร / การใช้งานระดับล่าง .... ให้คิดว่ามีคนมองข้ามพกบันทึกแอดเดอร์ ฯลฯ
วิธีการทำงาน

คำตอบ:


76
  • ในแง่ของความเร็ว: # 1 และ # 4 แต่ไม่มากในกรณีส่วนใหญ่

    คุณสามารถเขียนเกณฑ์มาตรฐานเพื่อยืนยันได้ แต่ฉันสงสัยว่าคุณจะพบว่า # 1 และ # 4 จะเร็วขึ้นเล็กน้อยเนื่องจากงานการวนซ้ำจะทำใน C แทน Perl และไม่มีการคัดลอกองค์ประกอบอาร์เรย์โดยไม่จำเป็น ( $_มีนามแฝงเป็นองค์ประกอบใน # 1 แต่ # 2 และ # 3 คัดลอกสเกลาร์จากอาร์เรย์จริงๆ)

    # 5 อาจจะคล้ายกัน.

  • ในแง่การใช้หน่วยความจำ: เหมือนกันทั้งหมดยกเว้น # 5

    for (@a)เป็นแบบพิเศษเพื่อหลีกเลี่ยงการแบนอาร์เรย์ ลูปจะวนซ้ำบนดัชนีของอาร์เรย์

  • ในแง่ของการอ่าน: # 1.

  • ในแง่ของความยืดหยุ่น: # 1 / # 4 และ # 5

    # 2 ไม่สนับสนุนองค์ประกอบที่เป็นเท็จ # 2 และ # 3 คือการทำลายล้าง


3
ว้าวคุณได้เพิ่มข้อมูลรถบรรทุกเป็นประโยคสั้น ๆ และเรียบง่าย
jaypal singh

1
# 2 เป็นสิ่งที่ดีเมื่อคุณทำคิว (เช่นการค้นหาก่อนกว้าง):my @todo = $root; while (@todo) { my $node = shift; ...; push @todo, ...; ...; }
ikegami

การใช้งาน 4 ไม่สร้างอาร์เรย์ของดัชนีกลางซึ่งอาจทำให้ใช้หน่วยความจำจำนวนมากได้หรือไม่? ถ้าเป็นเช่นนั้นดูเหมือนว่าเราไม่ควรใช้แนวทางนั้น stackoverflow.com/questions/6440723/… rt.cpan.org/Public/Bug/Display.html?id=115863
Thorsten Schöning

@ikegami สไตล์แชมป์เปี้ยนของคุณ - คำตอบที่ดี :)
skeetastax

26

หากคุณสนใจเฉพาะองค์ประกอบของ@Arrayให้ใช้:

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

หรือ

หากดัชนีมีความสำคัญให้ใช้:

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

หรือ ณperl5.12.1 คุณสามารถใช้:

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

หากคุณต้องการทั้งองค์ประกอบและดัชนีในเนื้อหาของลูป ฉันคาดหวัง โดยใช้ each จะเร็วที่สุด แต่แล้วคุณจะเลิกใช้ความเข้ากันได้กับรุ่นก่อน 5.12.1 perlวินาที

รูปแบบอื่นนอกเหนือจากนี้อาจเหมาะสมในบางสถานการณ์


ฉันคาดหวังว่าeachจะช้าที่สุด มันทำงานทั้งหมดของคนอื่น ๆ ลบนามแฝงรวมถึงการกำหนดรายการสำเนาสเกลาร์สองชุดและการล้างสเกลาร์สองรายการ
ikegami

และเพื่อความสามารถในการวัดผลที่ดีที่สุดของฉันคุณพูดถูก เร็วขึ้นประมาณ 45% ด้วยการforวนซ้ำบนดัชนีของอาร์เรย์และเร็วขึ้น 20% เมื่อทำซ้ำบนดัชนีของการอ้างอิงอาร์เรย์ (ฉันเข้าถึง$array->[$i]ในเนื้อหา) มากกว่าการใช้eachร่วมกับwhile.
Sinan Ünür

3

IMO การใช้งาน # 1 เป็นเรื่องปกติและสั้นและเป็นสำนวนสำหรับ Perl สำคัญกว่าคนอื่น ๆ สำหรับสิ่งนั้นเพียงอย่างเดียว เกณฑ์มาตรฐานของสามทางเลือกอาจให้ข้อมูลเชิงลึกเกี่ยวกับความเร็วอย่างน้อย


2

1 แตกต่างจาก 2 และ 3 อย่างมากเนื่องจากมันออกจากอาร์เรย์อย่างมีชั้นเชิงในขณะที่อีกสองตัวปล่อยให้ว่างไว้

ฉันจะบอกว่า # 3 ค่อนข้างแปลกและอาจมีประสิทธิภาพน้อยกว่าดังนั้นอย่าลืมว่า

ซึ่งทำให้คุณมี # 1 และ # 2 และพวกเขาไม่ได้ทำสิ่งเดียวกันดังนั้นจึงไม่สามารถ "ดีกว่า" ไปกว่าอีกอันได้ หากอาร์เรย์มีขนาดใหญ่และคุณไม่จำเป็นต้องเก็บไว้โดยทั่วไปขอบเขตจะจัดการกับมัน ( แต่ดู หมายเหตุ ) ดังนั้นโดยทั่วไปแล้ว # 1 ยังคงเป็นวิธีที่ชัดเจนและง่ายที่สุด การเลื่อนแต่ละองค์ประกอบออกจะไม่เร่งความเร็ว แต่อย่างใด แม้ว่าจะมีความจำเป็นที่จะต้องปลดปล่อยอาร์เรย์จากการอ้างอิงฉันจะไป:

undef @Array;

เมื่อทำเสร็จแล้ว.

  • หมายเหตุ : รูทีนย่อยที่มีขอบเขตของอาร์เรย์จะเก็บอาร์เรย์ไว้และใช้พื้นที่ใหม่ในครั้งต่อไป โดยทั่วไปควรจะใช้ได้ (ดูความคิดเห็น)

@Array = ();ไม่ทำให้อาร์เรย์พื้นฐานเป็นอิสระ ไม่แม้แต่จะออกนอกขอบเขตก็สามารถทำได้ undef @Array;ถ้าคุณอยากจะเป็นอิสระอาร์เรย์พื้นฐานที่คุณจะต้องใช้
ikegami

2
การสาธิต; 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

อะไร??? ฉันคิดว่าจุดรวมของ GC ครั้งหนึ่งเคยเป็นจำนวนการอ้างอิง == 0 หน่วยความจำที่เกี่ยวข้องจะสามารถรีไซเคิลได้
CodeClown42

@ikegami: ฉันเห็นสิ่งที่เกี่ยวกับ()vs undefแต่ถ้าการออกนอกขอบเขตไม่ปล่อยหน่วยความจำที่ใช้โดยอาร์เรย์ภายในไปยังขอบเขตนั้นไม่ทำให้ perl เป็นภัยพิบัติที่รั่วไหลหรือไม่? นั่นไม่สามารถเป็นจริงได้
CodeClown42

พวกเขาไม่รั่วไหลเช่นกัน ย่อยยังคงเป็นเจ้าของและจะนำมาใช้ใหม่ในครั้งต่อไปที่มีการเรียกใช้ย่อย เหมาะสำหรับความเร็ว
ikegami

1

ในบรรทัดเดียวเพื่อพิมพ์องค์ประกอบหรืออาร์เรย์

พิมพ์ $ _ สำหรับ (@array);

หมายเหตุ: โปรดจำไว้ว่า $ _ ภายในหมายถึงองค์ประกอบของ @array ในลูป การเปลี่ยนแปลงใด ๆ ที่ทำใน $ _ จะแสดงใน @array; เช่น

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

เอาต์พุต: 2 4 6


0

วิธีที่ดีที่สุดในการตัดสินคำถามเช่นนี้เพื่อเปรียบเทียบคำถามเหล่านี้:

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

และรันสิ่งนี้บน perl 5 เวอร์ชัน 24 การโค่นล้ม 1 (v5.24.1) ที่สร้างขึ้นสำหรับ x86_64-linux-gnu-thread-multi

ฉันเข้าใจ:

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)

ดังนั้น 'foreach (@Array)' จึงเร็วกว่าตัวอื่นประมาณสองเท่า คนอื่น ๆ ทั้งหมดคล้ายกันมาก

@ikegami ยังชี้ให้เห็นว่ามีความแตกต่างเล็กน้อยในการเลียนแบบเหล่านี้นอกเหนือจากความเร็ว


1
การเปรียบเทียบ$index < $#arrayควรเป็น$index <= $#arrayเพราะ$#arrayไม่ใช่ความยาวของอาร์เรย์ แต่เป็นดัชนีสุดท้ายของมัน
josch
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.