Bagaimana saya bisa memeriksa apakah array Perl mengandung nilai tertentu?

239

Saya mencoba mencari cara untuk memeriksa keberadaan suatu nilai dalam array tanpa iterasi melalui array.

Saya membaca file sebagai parameter. Saya memiliki daftar panjang parameter yang tidak ingin saya tangani. Saya menempatkan parameter yang tidak diinginkan ini dalam sebuah array @badparams.

Saya ingin membaca parameter baru dan jika tidak ada @badparams, proseskan. Jika ada @badparams, buka bacaan berikutnya.

Mel
sumber
3
Sebagai catatan, jawabannya tergantung pada situasi Anda. Sepertinya Anda ingin melakukan pencarian berulang, jadi menggunakan hash seperti yang disarankan jkramer itu bagus. Jika Anda hanya ingin melakukan satu pencarian, Anda mungkin perlu mengulanginya saja. (Dan dalam beberapa kasus Anda mungkin ingin pencarian biner daripada menggunakan hash!)
Cascabel
6
Sebagai catatan (dan ini mungkin sama sekali tidak berlaku untuk situasi Anda) umumnya merupakan ide yang lebih baik untuk mengidentifikasi 'nilai-nilai baik' dan mengabaikan yang lain daripada mencoba menyingkirkan 'nilai-nilai buruk' yang diketahui. Pertanyaan yang perlu Anda tanyakan adalah apakah mungkin ada beberapa nilai buruk yang belum Anda ketahui.
Grant McLean

Jawaban:

187

Cukup ubah array menjadi hash:

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

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

Anda juga dapat menambahkan lebih banyak params (unik) ke daftar:

$params{$newparam} = 1;

Dan kemudian dapatkan daftar params (unik) kembali:

@badparams = keys %params;
Jkramer
sumber
38
Sebagai catatan, kode ini masih melakukan iterate melalui array. Panggilan peta {} hanya membuat iterasi sangat mudah diketik.
Kenny Wyland
3
Saya hanya akan melakukan ini jika nilai Anda di @badparams pseudo-statis dan Anda berencana untuk melakukan banyak pemeriksaan terhadap peta. Saya tidak akan merekomendasikan ini untuk cek tunggal.
Aaron T Harris
Tidakkah ini akan menghasilkan array dengan banyak item dengan nilai yang sama?
Rob Wells
3
@RobWells tidak, itu akan berfungsi dengan baik. Lain kali melihat nilai yang sama, itu hanya akan menimpa entri dalam hash, yang dalam hal ini mengaturnya 1lagi.
andrewrjones
222

Tujuan umum terbaik - Terutama array pendek (1000 item atau kurang) dan coders yang tidak yakin tentang optimasi apa yang paling sesuai dengan kebutuhan mereka.

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

Telah disebutkan bahwa grep melewati semua nilai bahkan jika nilai pertama dalam array cocok. Ini benar, namun grep masih sangat cepat untuk sebagian besar kasus . Jika Anda berbicara tentang array pendek (kurang dari 1000 item) maka sebagian besar algoritma akan menjadi sangat cepat. Jika Anda berbicara tentang array yang sangat panjang (1.000.000 item), grep dapat diterima dengan cepat terlepas dari apakah item tersebut adalah yang pertama atau tengah atau terakhir dalam array.

Kasus Optimasi untuk array yang lebih panjang:

Jika array Anda diurutkan , gunakan "pencarian biner".

Jika array yang sama berulang kali dicari berkali-kali, salin ke hash dulu dan kemudian periksa hash. Jika memori menjadi masalah, maka pindahkan setiap item dari array ke hash. Lebih banyak memori efisien tetapi menghancurkan array asli.

Jika nilai yang sama dicari berulang kali dalam array, malas buat cache. (karena setiap item dicari, periksa terlebih dahulu apakah hasil pencarian disimpan dalam hash tetap. Jika hasil pencarian tidak ditemukan dalam hash, lalu cari array dan masukkan hasilnya dalam hash persisten sehingga waktu berikutnya kita akan temukan di hash dan lewati pencarian).

Catatan: optimasi ini hanya akan lebih cepat ketika berhadapan dengan array panjang. Jangan terlalu mengoptimalkan.

Aaron T Harris
sumber
12
Double tilde diperkenalkan di Perl 5.10
Dijeda sampai pemberitahuan lebih lanjut.
15
@ DennisWilliamson ... dan di 5.18 itu dianggap eksperimen .
Xaerxess
5
Hindari smartmatch dalam kode produksi. Tidak stabil / eksperimental menunggu pemberitahuan lebih lanjut.
Vector Gorgoth
1
Saya menemukan itu juga lebih mudah dibaca tetapi Jangan gunakan mengatakan bahwa itu tidak efisien dan memeriksa setiap elemen bahkan jika itu adalah yang pertama.
giordano
7
Jangan gunakan if ("value" ~~ @array). ~~ adalah fitur eksperimental yang disebut Smartmatch. Percobaan tampaknya dianggap gagal dan akan dihapus atau dimodifikasi dalam versi Perl yang akan datang.
yahermann
120

Anda dapat menggunakan fitur smartmatch di Perl 5.10 sebagai berikut:

Untuk melakukan pencarian nilai literal, lakukan di bawah ini.

if ( "value" ~~ @array ) 

Untuk pencarian skalar, melakukan di bawah ini akan berfungsi seperti di atas.

if ($val ~~ @array)

Untuk melakukan inline array di bawah ini, akan berfungsi seperti di atas.

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

Dalam Perl 5.18 smartmatch ditandai sebagai percobaan karena itu Anda perlu mematikan peringatan dengan menyalakan pragma eksperimental dengan menambahkan di bawah ini ke skrip / modul Anda:

use experimental 'smartmatch';

Atau jika Anda ingin menghindari penggunaan smartmatch - maka seperti kata Aaron gunakan:

if ( grep( /^$value$/, @array ) ) {
  #TODO:
}
Bitmap
sumber
4
Ini bagus tetapi tampaknya baru untuk Perl 5.10. Butuh beberapa waktu sebelum saya mengetahui mengapa saya mendapatkan kesalahan sintaksis.
Igor Skochinsky
17
Peringatan: Anda mungkin ingin menghindari yang ini, karena operator tampaknya memiliki perilaku yang berbeda dalam versi yang berbeda, dan sementara itu telah ditandai sebagai percobaan . Jadi, kecuali Anda memiliki kontrol penuh atas versi perl Anda (dan siapa yang memilikinya), Anda mungkin harus menghindarinya.
Labirin
1
Saya suka penjelasan ini tentang mengapa pengaturan use experimental 'smartmatch'direkomendasikan. Karena saya memiliki kendali atas versi perl saya (sistem internal), saya menggunakan no warnings 'experimental::smartmatch';pernyataan itu.
lepe
43

Posting blog ini membahas jawaban terbaik untuk pertanyaan ini.

Sebagai ringkasan singkat, jika Anda dapat menginstal modul CPAN maka solusi yang paling mudah dibaca adalah:

any(@ingredients) eq 'flour';

atau

@ingredients->contains('flour');

Namun, ungkapan yang lebih umum adalah:

any { $_ eq 'flour' } @ingredients

Tapi tolong jangan gunakan first()fungsinya! Itu sama sekali tidak mengungkapkan maksud kode Anda. Jangan gunakan ~~operator "Pencocokan pintar": itu rusak. Dan jangan gunakan grep()atau solusi dengan hash: mereka beralih melalui seluruh daftar.

any() akan berhenti segera setelah menemukan nilai Anda.

Lihat posting blog untuk lebih jelasnya.

Mascip
sumber
8
kebutuhan apapunuse List::Util qw(any); . List::Utilada dalam modul Core .
Onlyjob
13

Metode 1: grep (mungkin hati-hati sementara nilai diharapkan menjadi regex).

Cobalah untuk menghindari penggunaan grep, jika melihat sumber daya.

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

Metode 2: Pencarian Linier

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

Metode 3: Gunakan hash

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

Metode 4: smartmatch

(ditambahkan dalam Perl 5.10, ditandai adalah eksperimental dalam Perl 5.18).

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

Metode 5: Gunakan modul List::MoreUtils

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

Patokan @ eakssjo rusak - langkah-langkah membuat hash di loop vs membuat regexes di loop. Versi tetap (ditambah saya telah menambahkan List::Util::firstdan 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} );
        },
    });

Dan hasilnya (ini untuk iterasi 100_000, sepuluh kali lebih banyak dari pada jawaban @ 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)
Xaerxess
sumber
6
Jika Anda ingin menguji beberapa elemen, membuat hash di muka menghemat waktu Anda. Tetapi jika Anda hanya ingin tahu apakah itu mengandung elemen tunggal, maka Anda belum memiliki hash. Oleh karena itu, membuat hash harus menjadi bagian dari waktu komputasi. Terlebih lagi untuk ekspresi reguler: Anda memerlukan regexp baru untuk setiap elemen yang Anda cari.
memancing
1
@fishinear Benar, tetapi jika Anda hanya tertarik pada satu cek, bukan beberapa pemeriksaan, maka jelas itu optimasi mikro untuk bahkan bertanya-tanya tentang metode mana yang lebih cepat karena mikrodetik itu tidak masalah. Jika Anda ingin mengulang cek ini, hash adalah cara untuk pergi, karena biaya membuat hash sekali cukup kecil untuk diabaikan. Di atas tolok ukur hanya mengukur berbagai cara pengujian, tidak termasuk pengaturan apa pun. Ya, ini mungkin tidak valid dalam kasus penggunaan Anda, tetapi sekali lagi - jika Anda hanya melakukan pemeriksaan tunggal, Anda harus menggunakan apa pun yang paling mudah dibaca oleh Anda dan pasangan Anda.
Xaerxess
10

Meskipun nyaman digunakan, sepertinya solusi konversi-ke-hash memerlukan cukup banyak kinerja, yang merupakan masalah bagi saya.

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

Output dari tes benchmark:

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)
aksel
sumber
5
Menggunakan List::Util::firstlebih cepat karena berhenti iterasi ketika menemukan kecocokan.
RobEarl
1
-1 patokan Anda memiliki cacat, grepadalah signifikan lebih lambat dari menciptakan hash dan melakukan pencarian, sejak mantan adalah O (n) dan yang terakhir O (1). Lakukan saja pembuatan hash hanya sekali (di luar loop) dan precompute regex untuk mengukur metode saja ( lihat jawaban saya ).
Xaerxess
4
@Xaerxess: Dalam kasus saya, saya ingin melakukan satu pencarian, jadi saya pikir adil untuk menghitung penciptaan hash / regex dan pencarian / grep. Itu tugas akan melakukan banyak pencarian, saya kira solusi Anda lebih baik.
aksel
3
Jika Anda ingin melakukan hanya satu iterasi, perbedaannya tidak dapat dibedakan antara metode apa pun yang Anda pilih, jadi tolok ukur apa pun salah karena ini merupakan mikro optimasi jahat dalam kasus ini.
Xaerxess
2
Regex dikompilasi hanya sekali, karena memiliki bendera 'o'.
Apoc
3

@files adalah array yang ada

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

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

print "\n";

/^2[\d[.[\d[[A-za-z[?/ = vaues mulai dari 2 di sini Anda dapat menaruh ekspresi reguler apa pun

Rohan
sumber
2

Anda tentu menginginkan hash di sini. Tempatkan parameter buruk sebagai kunci dalam hash, lalu putuskan apakah parameter tertentu ada di hash.

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

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

Jika Anda benar-benar tertarik melakukannya dengan array, lihat List::UtilatauList::MoreUtils

David M.
sumber
0

Ada dua cara Anda bisa melakukan ini. Anda bisa menggunakan lemparan nilai ke dalam hash untuk tabel pencarian, seperti yang disarankan oleh posting lain. (Saya hanya akan menambahkan idiom lain.)

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

Tetapi jika data sebagian besar karakter kata dan tidak terlalu banyak meta, Anda dapat membuangnya ke dalam pergantian 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/;

Solusi ini harus disesuaikan dengan jenis "nilai buruk" yang Anda cari. Dan lagi, itu mungkin sama sekali tidak pantas untuk jenis string tertentu, jadi emptor peringatan .

Axeman
sumber
1
Anda juga dapat menulis @bad_param_lookup{@bad_params} = (), tetapi Anda harus menggunakannya existsuntuk menguji keanggotaan.
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";
    }
}

Anda mungkin ingin memeriksa konsistensi spasial numerik terkemuka

Serge
sumber