ฉันจะตรวจสอบว่าอาร์เรย์ Perl มีค่าเฉพาะได้อย่างไร


239

ฉันพยายามหาวิธีการตรวจสอบการมีอยู่ของค่าในอาร์เรย์โดยไม่ต้องวนซ้ำผ่านอาร์เรย์

ฉันกำลังอ่านไฟล์เพื่อหาพารามิเตอร์ ฉันมีรายการพารามิเตอร์จำนวนมากฉันไม่ต้องการจัดการ @badparamsฉันวางพารามิเตอร์ที่ไม่พึงประสงค์เหล่านี้ในอาร์เรย์

ฉันต้องการอ่านพารามิเตอร์ใหม่และหากไม่มีอยู่@badparamsให้ประมวลผล หากไม่มีอยู่@badparamsให้ไปที่การอ่านถัดไป


3
สำหรับบันทึกคำตอบนั้นขึ้นอยู่กับสถานการณ์ของคุณ ดูเหมือนว่าคุณต้องการทำการค้นหาซ้ำดังนั้นการใช้แฮชเป็น jkramer แนะนำเป็นสิ่งที่ดี หากคุณต้องการค้นหาเพียงครั้งเดียวคุณอาจทำซ้ำได้เช่นกัน (และในบางกรณีคุณอาจต้องการค้นหาแบบไบนารีแทนการใช้แฮช!)
Cascabel


6
สำหรับบันทึก (และสิ่งนี้อาจไม่เหมาะสมกับสถานการณ์ของคุณโดยสิ้นเชิง) โดยทั่วไปแล้วความคิดที่ดีกว่าคือการระบุ 'ค่านิยมที่ดี' และเพิกเฉยต่อส่วนที่เหลือแทนที่จะพยายามกำจัดวัชพืชที่รู้จักกันในชื่อ คำถามที่คุณต้องถามคือเป็นไปได้หรือไม่ที่อาจจะมีคุณค่าบางอย่างที่คุณยังไม่รู้
Grant McLean

คำตอบ:


187

เพียงเปลี่ยนอาร์เรย์เป็นแฮช:

my %params = map { $_ => 1 } @badparams;

if(exists($params{$someparam})) { ... }

นอกจากนี้คุณยังสามารถเพิ่มพารามิเตอร์พิเศษ (ไม่ซ้ำกัน) ลงในรายการ:

$params{$newparam} = 1;

และต่อมาก็จะได้รายการรายการที่ไม่ซ้ำกัน

@badparams = keys %params;

38
สำหรับเรกคอร์ดรหัสนี้ยังทำซ้ำผ่านอาร์เรย์ การเรียกใช้แผนที่ {} ทำให้การพิมพ์ซ้ำง่ายมาก
Kenny Wyland

3
ฉันจะทำเช่นนี้ก็ต่อเมื่อค่าของคุณใน @badparams เป็นค่าคงที่หลอกและคุณวางแผนที่จะตรวจสอบแผนที่จำนวนมาก ฉันจะไม่แนะนำสิ่งนี้สำหรับการตรวจสอบครั้งเดียว
Aaron T Harris

สิ่งนี้จะไม่เป็นผลสำหรับอาร์เรย์ที่มีหลายรายการที่มีค่าเท่ากันหรือไม่
Rob Wells

3
@ RobWells ไม่มันจะทำงานได้ดี ครั้งต่อไปที่มันเห็นค่าเดียวกันมันจะเขียนทับรายการในแฮชซึ่งในกรณีนี้จะตั้งค่า1อีกครั้ง
andrewrjones

222

วัตถุประสงค์ทั่วไปที่ดีที่สุด - โดยเฉพาะอย่างยิ่งอาร์เรย์สั้น (1,000 รายการหรือน้อยกว่า) และตัวแปลงสัญญาณที่ไม่แน่ใจว่าการเพิ่มประสิทธิภาพเหมาะสมกับความต้องการของพวกเขามากที่สุด

# $value can be any regex. be safe
if ( grep( /^$value$/, @array ) ) {
  print "found it";
}

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

กรณีการเพิ่มประสิทธิภาพสำหรับอาร์เรย์ที่ยาวกว่า:

หากอาร์เรย์ของคุณถูกจัดเรียงให้ใช้ "การค้นหาแบบไบนารี"

หากมีการค้นหาอาร์เรย์ซ้ำหลายครั้งให้คัดลอกลงในแฮชก่อนจากนั้นตรวจสอบแฮช หากมีปัญหาเกี่ยวกับหน่วยความจำให้ย้ายแต่ละรายการจากอาร์เรย์ไปยังแฮช หน่วยความจำมีประสิทธิภาพมากขึ้น แต่ทำลายอาเรย์ดั้งเดิม

หากค่าเดียวกันถูกค้นหาซ้ำ ๆภายในอาเรย์ให้สร้างแคชอย่างเกียจคร้าน (ในขณะที่ค้นหาแต่ละไอเท็มอันดับแรกให้ตรวจสอบว่าผลการค้นหาถูกเก็บไว้ในแฮชที่คงอยู่หรือไม่หากผลการค้นหาไม่พบในแฮชแล้วค้นหาอาเรย์และวางผลลัพธ์ในแฮชที่คงอยู่ ค้นหาในแฮชและข้ามการค้นหา)

หมายเหตุ: การเพิ่มประสิทธิภาพเหล่านี้จะเร็วขึ้นเมื่อจัดการกับอาร์เรย์ที่ยาวนาน เพิ่มประสิทธิภาพไม่เกิน


12
ตัวหนอนคู่ถูกนำมาใช้ใน Perl 5.10
หยุดชั่วคราวจนกว่าจะมีประกาศ

15
@DennisWilliamson ... และใน 5.18 มัน experimantal
Xaerxess

5
หลีกเลี่ยง smartmatch ในรหัสการผลิต มันไม่เสถียร / ทดลองอยู่ระหว่างรอการแจ้งเตือนเพิ่มเติม
Vector Gorgoth

1
ฉันพบว่ามันอ่านได้มากกว่า แต่อย่าใช้บอกว่ามันไม่มีประสิทธิภาพและตรวจสอบทุกองค์ประกอบแม้ว่าจะเป็นครั้งแรก
giordano

7
อย่าใช้ถ้า ("ค่า" ~~ @array) ~~ เป็นคุณสมบัติทดลองที่เรียกว่า Smartmatch การทดลองดูเหมือนว่าจะล้มเหลวและจะถูกลบหรือแก้ไขในรุ่นอนาคตของ Perl
yahermann

120

คุณสามารถใช้คุณสมบัติ smartmatch ในPerl 5.10 ได้ดังนี้:

สำหรับการค้นหาค่าที่แท้จริงที่ทำด้านล่างจะทำเคล็ดลับ

if ( "value" ~~ @array ) 

สำหรับการค้นหาสเกลาร์การทำด้านล่างจะได้ผลเหมือนด้านบน

if ($val ~~ @array)

สำหรับอินไลน์อาร์เรย์ที่ทำด้านล่างจะทำงานดังด้านบน

if ( $var ~~ ['bar', 'value', 'foo'] ) 

ในPerl 5.18 smartmatch ถูกตั้งค่าสถานะเป็นทดลองดังนั้นคุณต้องปิดคำเตือนโดยเปิดใช้ pragma ทดลองโดยเพิ่มด้านล่างลงในสคริปต์ / โมดูลของคุณ:

use experimental 'smartmatch';

อีกทางเลือกหนึ่งถ้าคุณต้องการหลีกเลี่ยงการใช้สมาร์ตแมช - แล้วแอรอนบอกว่าใช้:

if ( grep( /^$value$/, @array ) ) {
  #TODO:
}

4
นี่เป็นสิ่งที่ดี แต่ดูเหมือนจะใหม่สำหรับ Perl 5.10 เอาเวลาก่อนที่ฉันจะหาสาเหตุที่ฉันได้รับข้อผิดพลาดทางไวยากรณ์
Igor Skochinsky

17
คำเตือน:คุณอาจต้องการที่จะหลีกเลี่ยงคนนี้เนื่องจากผู้ประกอบการมีพฤติกรรมที่แตกต่างที่เห็นได้ชัดในรุ่นที่แตกต่างกันและมีในขณะเดียวกันการทำเครื่องหมายว่าการทดลอง ดังนั้นหากคุณไม่สามารถควบคุมเวอร์ชัน perl ของคุณได้เต็มที่ (และใครมีสิ่งนั้น) คุณควรหลีกเลี่ยงมัน
เขาวงกต

1
ฉันชอบคำอธิบายนี้เกี่ยวกับสาเหตุที่use experimental 'smartmatch'แนะนำให้ตั้งค่า เนื่องจากฉันมีการควบคุมเวอร์ชัน perl ของฉัน (ระบบภายใน) ฉันจึงใช้no warnings 'experimental::smartmatch';คำสั่ง
lepe

43

โพสต์บล็อกนี้กล่าวถึงคำตอบที่ดีที่สุดสำหรับคำถามนี้

โดยสรุปหากคุณสามารถติดตั้งโมดูล CPAN ได้โซลูชั่นที่อ่านได้มากที่สุดคือ:

any(@ingredients) eq 'flour';

หรือ

@ingredients->contains('flour');

อย่างไรก็ตามสำนวนที่พบบ่อยคือ:

any { $_ eq 'flour' } @ingredients

แต่โปรดอย่าใช้first()ฟังก์ชั่น! มันไม่ได้แสดงเจตนาของรหัสของคุณเลย อย่าใช้ตัวดำเนินการ~~"จับคู่อัจฉริยะ": มันเสีย และอย่าใช้grep()หรือแก้ปัญหาด้วยแฮช: มันวนซ้ำไปเรื่อย ๆ ในรายการทั้งหมด

any() จะหยุดทันทีที่พบมูลค่าของคุณ

ตรวจสอบการโพสต์บล็อกสำหรับรายละเอียดเพิ่มเติม


8
ใด ๆuse List::Util qw(any);ความต้องการ List::Utilอยู่ในโมดูลหลัก
เฉพาะ

13

วิธีที่ 1: grep (อาจระมัดระวังในขณะที่ค่าคาดว่าจะเป็น regex)

พยายามหลีกเลี่ยงการใช้grepหากมองไปที่แหล่งข้อมูล

if ( grep( /^$value$/, @badparams ) ) {
  print "found";
}

วิธีที่ 2: การค้นหาเชิงเส้น

for (@badparams) {
    if ($_ eq $value) {
       print "found";
       last;
    }
}

วิธีที่ 3: ใช้แฮช

my %hash = map {$_ => 1} @badparams;
print "found" if (exists $hash{$value});

วิธีที่ 4: smartmatch

(เพิ่มใน Perl 5.10 เครื่องหมายถูกทดสอบใน Perl 5.18)

use experimental 'smartmatch';  # for perl 5.18
print "found" if ($value ~~ @badparams);

วิธีที่ 5: ใช้โมดูล List::MoreUtils

use List::MoreUtils qw(any);
@badparams = (1,2,3);
$value = 1;
print "found" if any {$_ == $value} @badparams;

12

เกณฑ์มาตรฐานของ @ eakssjoเสีย - การวัดการสร้างแฮชในลูปเทียบกับการสร้างเรกซ์ในลูป รุ่นที่แก้ไขแล้ว (รวมทั้งที่ฉันเพิ่มList::Util::firstและList::MoreUtils::any):

use List::Util qw(first);
use List::MoreUtils qw(any);
use Benchmark;

my @list = ( 1..10_000 );
my $hit = 5_000;
my $hit_regex = qr/^$hit$/; # precompute regex
my %params;
$params{$_} = 1 for @list;  # precompute hash
timethese(
    100_000, {
        'any' => sub {
            die unless ( any { $hit_regex } @list );
        },
        'first' => sub {
            die unless ( first { $hit_regex } @list );
        },
        'grep' => sub {
            die unless ( grep { $hit_regex } @list );
        },
        'hash' => sub {
            die unless ( $params{$hit} );
        },
    });

และผลลัพธ์ (สำหรับการวนซ้ำ 100_000 ครั้งมากกว่าคำตอบของ @ eakssjo สิบเท่า):

Benchmark: timing 100000 iterations of any, first, grep, hash...
       any:  0 wallclock secs ( 0.67 usr +  0.00 sys =  0.67 CPU) @ 149253.73/s (n=100000)
     first:  1 wallclock secs ( 0.63 usr +  0.01 sys =  0.64 CPU) @ 156250.00/s (n=100000)
      grep: 42 wallclock secs (41.95 usr +  0.08 sys = 42.03 CPU) @ 2379.25/s (n=100000)
      hash:  0 wallclock secs ( 0.01 usr +  0.00 sys =  0.01 CPU) @ 10000000.00/s (n=100000)
            (warning: too few iterations for a reliable count)

6
หากคุณต้องการทดสอบองค์ประกอบหลายรายการการสร้างแฮชล่วงหน้าจะช่วยคุณประหยัดเวลา แต่ถ้าคุณแค่อยากรู้ว่ามันมีองค์ประกอบเดียวหรือไม่คุณก็ไม่มีแฮชอยู่แล้ว ดังนั้นการสร้างแฮชควรเป็นส่วนหนึ่งของเวลาคำนวณ ยิ่งกว่านั้นสำหรับนิพจน์ทั่วไป: คุณต้องการ regexp ใหม่สำหรับแต่ละองค์ประกอบที่คุณค้นหา
fishinear

1
@fishinear True แต่ถ้าคุณสนใจเพียงแค่เช็คเดียวไม่ใช่เช็คหลาย ๆ อันก็เห็นได้ชัดว่ามันเป็น microoptimization ที่จะสงสัยว่าวิธีใดเร็วกว่าเพราะไมโครวินาทีนั้นไม่สำคัญ หากคุณต้องการทำซ้ำการตรวจสอบนี้แฮชเป็นหนทางไปทำให้ค่าใช้จ่ายในการสร้างแฮชเพียงครั้งเดียวนั้นเล็กพอที่จะถูกเพิกเฉย มาตรฐานข้างต้นวัดเฉพาะวิธีการทดสอบที่แตกต่างกันเท่านั้นไม่รวมถึงการตั้งค่าใด ๆ ใช่สิ่งนี้อาจไม่ถูกต้องในกรณีที่ใช้งานของคุณ แต่อีกครั้ง - หากคุณทำเครื่องหมายเพียงครั้งเดียวคุณควรใช้สิ่งที่คุณและเพื่อนของคุณสามารถอ่านได้มากที่สุด
Xaerxess

10

แม้ว่ามันจะสะดวกในการใช้งาน แต่ดูเหมือนว่าโซลูชันแปลงเป็นแฮชจะมีประสิทธิภาพค่อนข้างมากซึ่งเป็นปัญหาสำหรับฉัน

#!/usr/bin/perl
use Benchmark;
my @list;
for (1..10_000) {
    push @list, $_;
}

timethese(10000, {
  'grep'    => sub {
            if ( grep(/^5000$/o, @list) ) {
                # code
            }
        },
  'hash'    => sub {
            my %params = map { $_ => 1 } @list;
            if ( exists($params{5000}) ) {
                # code
            }
        },
});

ผลลัพธ์ของการทดสอบเกณฑ์มาตรฐาน:

Benchmark: timing 10000 iterations of grep, hash...
          grep:  8 wallclock secs ( 7.95 usr +  0.00 sys =  7.95 CPU) @ 1257.86/s (n=10000)
          hash: 50 wallclock secs (49.68 usr +  0.01 sys = 49.69 CPU) @ 201.25/s (n=10000)

5
การใช้List::Util::firstเร็วขึ้นเนื่องจากจะหยุดการวนซ้ำเมื่อพบการแข่งขัน
RobEarl

1
-1 มาตรฐานของคุณมีข้อบกพร่องgrepเป็นอย่างมีนัยสำคัญช้ากว่าการสร้างกัญชาและการทำค้นหาตั้งแต่อดีตคือ O (n) และ O หลัง (1) เพียงสร้าง hash เพียงครั้งเดียว (นอกวง) และ precompute regex เพื่อวัดวิธีเท่านั้น ( ดูคำตอบของฉัน )
Xaerxess

4
@ Xaerxess: ในกรณีของฉันฉันต้องการค้นหาหนึ่งครั้งดังนั้นฉันคิดว่ามันยุติธรรมที่จะนับทั้งการสร้าง hash / regex และการค้นหา / grep มันเป็นงานที่ต้องทำการค้นหาหลายอย่างฉันคิดว่าทางออกของคุณดีกว่า
aksel

3
หากคุณต้องการทำซ้ำเพียงครั้งเดียวความแตกต่างนั้นไม่สามารถแยกได้ระหว่างวิธีการใด ๆ ที่คุณเลือกดังนั้นการเปรียบเทียบใด ๆ จึงผิดเนื่องจากเป็น microoptimization ชั่วร้ายในกรณีนี้
Xaerxess

2
regex รวบรวมเพียงครั้งเดียวเนื่องจากมีการตั้งค่าสถานะ 'o'
Apoc

3

@files เป็นอาร์เรย์ที่มีอยู่แล้ว

my @new_values =  grep(/^2[\d].[\d][A-za-z]?/,@files);

print join("\n", @new_values);

print "\n";

/^2 [2] ที่เหมือนกันของที่ --A-za-zurrency?/ = vaues เริ่มจาก 2 ที่นี่คุณสามารถใส่นิพจน์ปกติใด ๆ


2

แน่นอนคุณต้องการแฮชที่นี่ วางพารามิเตอร์ที่ไม่ดีเป็นคีย์ในแฮชแล้วตัดสินใจว่ามีพารามิเตอร์เฉพาะในแฮชหรือไม่

our %bad_params = map { $_ => 1 } qw(badparam1 badparam2 badparam3)

if ($bad_params{$new_param}) {
  print "That is a bad parameter\n";
}

หากคุณสนใจที่จะทำมันด้วยอาร์เรย์ดูList::UtilหรือList::MoreUtils


0

มีสองวิธีที่คุณสามารถทำได้ คุณสามารถใช้การโยนค่าลงในแฮชสำหรับตารางการค้นหาตามที่โพสต์อื่นแนะนำ (ฉันจะเพิ่มสำนวนอื่น)

my %bad_param_lookup;
@bad_param_lookup{ @bad_params } = ( 1 ) x @bad_params;

แต่ถ้าเป็นข้อมูลของตัวอักษรส่วนใหญ่และไม่มากเกินไปเมตาดาต้าคุณสามารถถ่ายโอนลงในการสลับ regex:

use English qw<$LIST_SEPARATOR>;

my $regex_str = do { 
    local $LIST_SEPARATOR = '|';
    "(?:@bad_params)";
 };

 # $front_delim and $back_delim being any characters that come before and after. 
 my $regex = qr/$front_delim$regex_str$back_delim/;

การแก้ปัญหานี้จะต้องมีการปรับสำหรับประเภทของ "ค่าที่ไม่ดี" ที่คุณกำลังมองหา และอีกครั้งก็อาจจะโดยสิ้นเชิงไม่เหมาะสมสำหรับบางประเภทของสตริงเพื่อข้อแม้ emptor


1
คุณสามารถเขียน@bad_param_lookup{@bad_params} = ()ได้ แต่คุณต้องใช้existsเพื่อทดสอบการเป็นสมาชิก
Greg Bacon

-1
my @badparams = (1,2,5,7,'a','zzz');

my $badparams = join('|',@badparams);   # '|' or any other character not present in params

foreach my $par (4,5,6,7,'a','z','zzz')
{
    if ($badparams =~ /\b$par\b/)
    {
        print "$par is present\n";
    }
    else
    {
        print "$par is not present\n";
    }
}

คุณอาจต้องการตรวจสอบความสอดคล้องกันของช่องว่างนำหน้าด้วยตัวเลข

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