Làm cách nào để kiểm tra xem một mảng Perl có chứa một giá trị cụ thể không?


239

Tôi đang cố gắng tìm ra cách kiểm tra sự tồn tại của một giá trị trong một mảng mà không lặp qua mảng đó.

Tôi đang đọc một tập tin cho một tham số. Tôi có một danh sách dài các tham số mà tôi không muốn xử lý. Tôi đặt các tham số không mong muốn này trong một mảng @badparams.

Tôi muốn đọc một tham số mới và nếu nó không tồn tại @badparams, hãy xử lý nó. Nếu nó tồn tại trong @badparams, đi đến đọc tiếp theo.


3
Đối với hồ sơ, câu trả lời không phụ thuộc vào tình huống của bạn. Có vẻ như bạn muốn thực hiện tra cứu lặp đi lặp lại, vì vậy sử dụng hàm băm như jkramer gợi ý là tốt. Nếu bạn chỉ muốn thực hiện một lần tra cứu, bạn cũng có thể chỉ cần lặp đi lặp lại. (Và trong một số trường hợp, bạn có thể muốn tìm kiếm nhị phân thay vì sử dụng hàm băm!)
Cascabel


6
Đối với hồ sơ (và điều này có thể hoàn toàn không thể áp dụng cho tình huống của bạn), nói chung nên xác định 'giá trị tốt' và bỏ qua phần còn lại thay vì cố gắng loại bỏ 'giá trị xấu' đã biết. Câu hỏi bạn cần đặt ra là liệu có thể có một số giá trị xấu mà bạn chưa biết.
Cấp cho McLean

Câu trả lời:


187

Đơn giản chỉ cần biến mảng thành hàm băm:

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

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

Bạn cũng có thể thêm nhiều thông số (duy nhất) vào danh sách:

$params{$newparam} = 1;

Và sau đó nhận được một danh sách các thông số (duy nhất) trở lại:

@badparams = keys %params;

38
Đối với bản ghi, mã này vẫn lặp đi lặp lại qua mảng. Cuộc gọi {} bản đồ chỉ đơn giản làm cho việc lặp đó rất dễ gõ.
Kenny Wyland

3
Tôi chỉ làm điều này nếu các giá trị của bạn trong @badparams là giả tĩnh và bạn dự định thực hiện nhiều kiểm tra đối với bản đồ. Tôi sẽ không đề nghị điều này cho một kiểm tra duy nhất.
Aaron T Harris

Điều này có xảy ra với một mảng có nhiều mục có cùng giá trị không?
Rob Wells

3
@RobWells không, nó sẽ hoạt động tốt. Lần sau khi thấy giá trị tương tự, nó sẽ ghi đè mục nhập trong hàm băm, trong trường hợp này sẽ đặt 1lại thành giá trị đó.
andrewrjones 18/03/13

222

Mục đích chung tốt nhất - Đặc biệt là các mảng ngắn (1000 mục trở xuống) và các lập trình viên không chắc chắn về những gì tối ưu hóa phù hợp nhất với nhu cầu của họ.

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

Nó đã được đề cập rằng grep đi qua tất cả các giá trị ngay cả khi giá trị đầu tiên trong mảng khớp. Điều này là đúng, tuy nhiên grep vẫn cực kỳ nhanh đối với hầu hết các trường hợp . Nếu bạn đang nói về mảng ngắn (dưới 1000 mục) thì hầu hết các thuật toán sẽ khá nhanh. Nếu bạn đang nói về mảng rất dài (1.000.000 mục) thì grep có thể chấp nhận nhanh chóng bất kể mục đó là mục đầu tiên hay phần giữa hoặc phần cuối trong mảng.

Các trường hợp tối ưu hóa cho các mảng dài hơn:

Nếu mảng của bạn được sắp xếp , sử dụng "tìm kiếm nhị phân".

Nếu cùng một mảng được tìm kiếm nhiều lần, hãy sao chép nó vào hàm băm trước rồi kiểm tra hàm băm. Nếu bộ nhớ là một mối quan tâm, sau đó di chuyển từng mục từ mảng vào hàm băm. Bộ nhớ hiệu quả hơn nhưng phá hủy mảng ban đầu.

Nếu các giá trị tương tự được tìm kiếm lặp đi lặp lại trong mảng, lười biếng xây dựng bộ đệm. (vì mỗi mục được tìm kiếm, trước tiên hãy kiểm tra xem kết quả tìm kiếm có được lưu trong hàm băm không. Nếu kết quả tìm kiếm không được tìm thấy trong hàm băm, sau đó tìm kiếm mảng và đưa kết quả vào hàm băm bền tìm nó trong hàm băm và bỏ qua tìm kiếm).

Lưu ý: những tối ưu hóa này sẽ chỉ nhanh hơn khi xử lý các mảng dài. Đừng tối ưu hóa quá mức.


12
Dấu ngã kép được giới thiệu trong Perl 5.10
Tạm dừng cho đến khi có thông báo mới.


5
Tránh smartmatch trong mã sản xuất. Đó là không ổn định / thử nghiệm chờ thông báo thêm.
Vector Gorgoth

1
Tôi thấy nó cũng dễ đọc hơn nhưng Đừng sử dụng nói rằng nó không hiệu quả và kiểm tra mọi yếu tố ngay cả khi đó là lần đầu tiên.
giordano

7
Không sử dụng if ("value" ~ ~ @array). ~ ~ là một tính năng thử nghiệm được gọi là Smartmatch. Thí nghiệm dường như được coi là thất bại và sẽ bị xóa hoặc sửa đổi trong phiên bản tương lai của Perl.
yahermann

120

Bạn có thể sử dụng tính năng smartmatch trong Perl 5.10 như sau:

Đối với tra cứu giá trị theo nghĩa đen, làm dưới đây sẽ thực hiện các mẹo.

if ( "value" ~~ @array ) 

Đối với tra cứu vô hướng, làm dưới đây sẽ làm việc như trên.

if ($val ~~ @array)

Đối với mảng nội tuyến làm dưới đây, sẽ làm việc như trên.

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

Trong Perl 5.18 smartmatch được gắn cờ là thử nghiệm, do đó bạn cần tắt các cảnh báo bằng cách bật pragma thử nghiệm bằng cách thêm bên dưới vào tập lệnh / mô-đun của bạn:

use experimental 'smartmatch';

Ngoài ra, nếu bạn muốn tránh sử dụng smartmatch - thì như Aaron đã nói hãy sử dụng:

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

4
Điều này là tốt nhưng dường như là mới đối với Perl 5.10. Mất một thời gian trước khi tôi hiểu tại sao tôi bị lỗi cú pháp.
Igor Skochinsky

17
Cảnh báo: bạn có thể muốn tránh điều này, vì nhà điều hành rõ ràng có hành vi khác nhau trong các phiên bản khác nhau và trong khi đó đã được đánh dấu là thử nghiệm . Vì vậy, trừ khi bạn có toàn quyền kiểm soát phiên bản perl của mình (và ai có cái đó), bạn có lẽ nên tránh nó.
Mê cung

1
Tôi thích giải thích này về lý do tại sao cài đặt use experimental 'smartmatch'được khuyến nghị. Khi tôi có quyền kiểm soát phiên bản perl của mình (hệ thống nội bộ), tôi sử dụng no warnings 'experimental::smartmatch';câu lệnh.
Lepe

43

Bài đăng trên blog này thảo luận các câu trả lời tốt nhất cho câu hỏi này.

Tóm lại, nếu bạn có thể cài đặt các mô-đun CPAN thì các giải pháp dễ đọc nhất là:

any(@ingredients) eq 'flour';

hoặc là

@ingredients->contains('flour');

Tuy nhiên, một thành ngữ phổ biến hơn là:

any { $_ eq 'flour' } @ingredients

Nhưng xin vui lòng không sử dụng first()chức năng! Nó hoàn toàn không thể hiện ý định của mã của bạn. Không sử dụng ~~toán tử "Kết hợp thông minh": nó bị hỏng. Và không sử dụng grep()cũng không phải là giải pháp với hàm băm: chúng lặp đi lặp lại trong toàn bộ danh sách.

any() sẽ dừng lại ngay khi nó tìm thấy giá trị của bạn.

Kiểm tra bài viết trên blog để biết thêm chi tiết.


8
bất kỳ nhu cầuuse List::Util qw(any);. List::Utillà trong các mô-đun Core .
Onlyjob

13

Phương pháp 1: grep (có thể cẩn thận trong khi giá trị được dự kiến ​​là regex).

Cố gắng tránh sử dụng grep, nếu nhìn vào tài nguyên.

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

Phương pháp 2: Tìm kiếm tuyến tính

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

Phương pháp 3: Sử dụng hàm băm

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

Phương pháp 4: smartmatch

(được thêm vào trong Perl 5.10, được đánh dấu là thử nghiệm trong Perl 5.18).

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

Phương pháp 5: Sử dụng mô-đun List::MoreUtils

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

12

Điểm chuẩn của @ eakssjo bị hỏng - các biện pháp tạo băm trong vòng lặp so với tạo biểu thức chính quy trong vòng lặp. Đã sửa phiên bản (cộng với tôi đã thêm List::Util::firstList::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} );
        },
    });

Và kết quả (đó là cho 100_000 lần lặp, gấp mười lần so với câu trả lời của @ 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
Nếu bạn muốn kiểm tra nhiều yếu tố, thì việc tạo ra hàm băm trước giúp bạn tiết kiệm thời gian. Nhưng nếu bạn chỉ muốn biết liệu nó có chứa một yếu tố hay không, thì bạn chưa có hàm băm. Do đó, việc tạo ra hàm băm nên là một phần của thời gian tính toán. Thậm chí nhiều hơn cho biểu thức chính quy: bạn cần một biểu thức chính quy mới cho mỗi phần tử bạn tìm kiếm.

1
@fishinear Đúng, nhưng nếu bạn chỉ quan tâm đến một kiểm tra, không phải nhiều kiểm tra, thì rõ ràng đó là sự vi mô hóa để thậm chí tự hỏi về phương pháp nào nhanh hơn bởi vì những micro giây đó không quan trọng. Nếu bạn muốn làm lại kiểm tra này, băm là cách để thực hiện, vì chi phí tạo băm một lần là đủ nhỏ để bỏ qua sau đó. Các điểm chuẩn trên chỉ đo các cách kiểm tra khác nhau, không bao gồm bất kỳ thiết lập nào. Có, điều này có thể không hợp lệ trong trường hợp sử dụng của bạn, nhưng một lần nữa - nếu bạn chỉ thực hiện một kiểm tra duy nhất, bạn nên sử dụng bất cứ thứ gì dễ đọc nhất cho bạn và bạn bè của bạn.
Xaerxess

10

Mặc dù nó thuận tiện để sử dụng, nhưng có vẻ như giải pháp convert-to-hash tốn khá nhiều hiệu năng, đây là một vấn đề đối với tôi.

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

Đầu ra của bài kiểm tra điểm chuẩn:

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
Sử dụng List::Util::firstnhanh hơn vì nó dừng lặp lại khi tìm thấy kết quả khớp.
RobEarl

1
-1 điểm chuẩn của bạn có khiếm khuyết, grepđáng kể chậm hơn so với việc tạo ra băm và làm tra cứu, kể từ khi cựu là O (n) và O sau (1). Chỉ cần thực hiện tạo băm chỉ một lần (bên ngoài vòng lặp) và tính toán lại biểu thức chính quy để đo các phương thức mà thôi ( xem câu trả lời của tôi ).
Xaerxess

4
@Xaerxess: Trong trường hợp của tôi, tôi muốn thực hiện một lần tra cứu, vì vậy tôi nghĩ thật công bằng khi tính cả việc tạo băm / regex và tra cứu / grep. Nhiệm vụ sẽ là thực hiện nhiều tra cứu, tôi đoán giải pháp của bạn là tốt hơn.
aksel

3
Nếu bạn chỉ muốn thực hiện một lần lặp, sự khác biệt là không thể phân biệt giữa bất kỳ phương pháp nào bạn chọn, do đó, bất kỳ điểm chuẩn nào đều sai vì đó là một sự vi mô hóa xấu trong trường hợp này.
Xaerxess

2
Regex chỉ được biên dịch một lần, vì nó có cờ 'o'.
Apoc

3

@files là một mảng hiện có

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

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

print "\n";

/ ^ 2 [\ dTHER.[\d[[A-za-zTHER?/ = vaues bắt đầu từ 2 ở đây, bạn có thể đặt bất kỳ biểu thức chính quy nào


2

Bạn chắc chắn muốn một hàm băm ở đây. Đặt các tham số xấu làm khóa trong hàm băm, sau đó quyết định xem một tham số cụ thể có tồn tại trong hàm băm không.

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

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

Nếu bạn thực sự thích làm việc đó với một mảng, hãy nhìn vào List::UtilhoặcList::MoreUtils


0

Có hai cách bạn có thể làm điều này. Bạn có thể sử dụng ném các giá trị vào hàm băm cho bảng tra cứu, như được đề xuất bởi các bài đăng khác. (Tôi sẽ chỉ thêm một thành ngữ.)

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

Nhưng nếu đó là dữ liệu của hầu hết các ký tự từ và không có quá nhiều meta, bạn có thể chuyển nó thành một thay thế 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/;

Giải pháp này sẽ phải được điều chỉnh cho các loại "giá trị xấu" mà bạn đang tìm kiếm. Và một lần nữa, nó có thể là hoàn toàn không phù hợp cho một số loại dây, vì vậy caveat emptor .


1
Bạn cũng có thể viết @bad_param_lookup{@bad_params} = (), nhưng bạn cần sử dụng existsđể kiểm tra tư cách thành viên.
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";
    }
}

Bạn có thể muốn kiểm tra tính nhất quán của không gian hàng đầu số

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.