Bir Perl dizisinin belirli bir değer içerip içermediğini nasıl kontrol edebilirim?


239

Dizi üzerinden yineleme olmadan bir dizi değeri olup olmadığını denetlemek için bir yol bulmaya çalışıyorum.

Bir parametre için bir dosya okuyorum. Başa çıkmak istemediğim parametrelerin uzun bir listesi var. Bu istenmeyen parametreleri bir diziye yerleştirdim @badparams.

Yeni bir parametre okumak istiyorum ve eğer mevcut değilse @badparams, onu işleyin. Eğer mevcutsa @badparams, sonraki okumaya gidin.


3
Kayıt için cevap sizin durumunuza bağlıdır. Tekrarlanan arama yapmak istediğiniz gibi geliyor, bu yüzden jkramer'ın önerdiği gibi bir karma kullanmak iyidir. Yalnızca bir arama yapmak istiyorsanız, yineleme yapabilirsiniz. (Ve bazı durumlarda, karma kullanmak yerine ikili arama yapmak isteyebilirsiniz!)
Cascabel


6
Kayıt için (ve bu durumunuza tamamen uygulanabilir olmayabilir), bilinen 'kötü değerleri' ayıklamaya çalışmak yerine 'iyi değerleri' tanımlamak ve geri kalanları görmezden gelmek genellikle daha iyi bir fikirdir. Sormanız gereken soru, henüz bilmediğiniz bazı kötü değerlerin mümkün olup olmadığıdır.
Grant McLean

Yanıtlar:


187

Diziyi bir karma haline getirin:

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

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

Ayrıca listeye daha fazla (benzersiz) parametre ekleyebilirsiniz:

$params{$newparam} = 1;

Ve sonra (benzersiz) parametrelerin bir listesini geri alın:

@badparams = keys %params;

38
Kayıt için, bu kod hala dizi boyunca yineleme yapar. Harita {} çağrısı, yinelemenin yazılmasını çok kolay hale getirir.
Kenny Wyland

3
Bunu sadece @ badparams içindeki değerleriniz sahte statikse ve haritaya karşı çok fazla kontrol yapmayı planlıyorsanız yapardım. Ben bu tek bir kontrol için tavsiye etmem.
Aaron T Harris

Bu, aynı değere sahip birden fazla öğe içeren bir dizi için gitmeyecek mi?
Rob Wells

3
@RobWells hayır, iyi çalışır. Bir dahaki sefere aynı değer gördüğünde, karmadaki girişin üzerine yazacak ve bu durumda 1tekrar ayarlayacaktır .
13:00

222

En iyi genel amaç - Özellikle hangi optimizasyonların ihtiyaçlarına en uygun olduğundan emin olmayan kısa diziler (1000 veya daha az öğe) ve kodlayıcılar.

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

Dizideki ilk değer eşleşse bile grep'in tüm değerlerden geçtiği belirtilmiştir. Bu doğrudur, ancak grep çoğu durumda hala çok hızlıdır . Kısa dizilerden (1000 öğeden az) bahsediyorsanız, algoritmaların çoğu zaten oldukça hızlı olacaktır. Çok uzun dizilerden (1.000.000 öğe) bahsediyorsanız, öğenin dizideki ilk veya orta veya son olmasına bakılmaksızın grep kabul edilebilir derecede hızlıdır.

Daha uzun diziler için Optimizasyon Durumları:

Diziniz sıralanırsa , "ikili arama" kullanın.

Eğer aynı dizi tekrar tekrar aranır defalarca, ilk olarak bir karma kopyalayın ve daha sonra karma kontrol edin. Bellek bir sorunsa, diziden her öğeyi karmaya taşıyın. Daha verimli bellek ama orijinal diziyi yok eder.

Eğer aynı değerler defalarca arandığını dizinin içinde, tembel bir önbellek oluşturmak. (her öğe aranırken, önce arama sonucunun kalıcı bir karma içinde saklanıp saklanmadığını kontrol edin. arama sonucu karma içinde bulunmazsa, diziyi arayın ve sonucu kalıcı hash içine koyun, böylece bir dahaki sefere hash içinde bulun ve aramayı atlayın).

Not: Bu optimizasyonlar yalnızca uzun dizilerle uğraşırken daha hızlı olacaktır. Aşırı optimizasyon yapmayın.


12
Çift tilde Perl 5.10'da tanıtıldı
sonraki duyuruya kadar duraklatıldı.

15
@DennisWilliamson ... ve 5.18'de deneysel olarak kabul edildi .
Xaerxess

5
Üretim kodunda akıllı eşleşmeden kaçının. Kararsız / deneysel bir sonraki uyarı bekliyor.
Vektör Gorgoth

1
Daha okunabilir buluyorum ama kullanmayın , verimli olmadığını söylüyor ve ilk bile olsa her elemanı kontrol ediyor.
giordano

7
Eğer kullanmayın ("değer" ~~ @array). ~~ Smartmatch adı verilen deneysel bir özelliktir. Denemenin başarısız olduğu görülüyor ve Perl'in gelecekteki bir sürümünde kaldırılacak veya değiştirilecek.
yahermann

120

Perl 5.10'daki smartmatch özelliğini aşağıdaki gibi kullanabilirsiniz :

Gerçek değer arama için aşağıdaki işi yapmak hile yapacaktır.

if ( "value" ~~ @array ) 

Skaler arama için, aşağıda yapmak yukarıdaki gibi çalışacaktır.

if ($val ~~ @array)

Satır içi dizi için aşağıdaki gibi çalışır.

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

In Perl 5.18 nedenle açarak uyarıları kapatmak için gerekmez deneysel olarak işaretlenir SmartMatch deneysel betiğinize / modülüne aşağıdaki ekleyerek pragma:

use experimental 'smartmatch';

Alternatif olarak, smartmatch kullanımından kaçınmak istiyorsanız - o zaman Aaron'un dediği gibi:

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

4
Bu güzel ama Perl 5.10 için yeni gibi görünüyor. Neden sözdizimi hataları aldığımı anlayamadan biraz zaman aldı.
Igor Skochinsky

17
Uyarı: bundan kaçınmak isteyebilirsiniz, çünkü operatör farklı versiyonlarda görünüşte farklı davranışlara sahiptir ve bu arada deneysel olarak işaretlenmiştir . Yani perl sürümünüz üzerinde tam kontrole sahip değilseniz (ve buna sahip olan) muhtemelen bundan kaçınmalısınız.
Labirent

1
Ayarın neden önerildiğine ilişkin bu açıklamayı seviyorum use experimental 'smartmatch'. Perl versiyonumu (dahili sistem) kontrol ettiğim için no warnings 'experimental::smartmatch';ifadeyi kullanıyorum.
lepe

43

Bu blog gönderisi , bu sorunun en iyi yanıtlarını tartışıyor.

Kısa bir özet olarak, CPAN modüllerini kurabilirseniz, en okunabilir çözümler şunlardır:

any(@ingredients) eq 'flour';

veya

@ingredients->contains('flour');

Bununla birlikte, daha yaygın bir deyim:

any { $_ eq 'flour' } @ingredients

Ama lütfen first()fonksiyonu kullanmayın ! Kodunuzun amacını hiç ifade etmez. Kullanmayın ~~"Akıllı maçı" operatörünü: o kırılır. Ve grep()bir karma ile çözüm kullanmayın : tüm liste boyunca tekrar ederler.

any() değerinizi bulur bulmaz duracaktır.

Daha fazla bilgi için blog yayınına göz atın.


8
herhangi bir ihtiyaçuse List::Util qw(any);. List::Utilolduğu Çekirdek modüllerinin .
Onlyjob

13

Yöntem 1: grep (değerin normal ifade olması beklenirken dikkatli olabilir).

grepKaynaklara bakıyorsanız, kullanmaktan kaçının .

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

Yöntem 2: Doğrusal Arama

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

Yöntem 3: Karma kullanma

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

Yöntem 4: SmartMatch

(Perl 5.10'da eklenmiştir, işaretli Perl 5.18'de deneyseldir).

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

Yöntem 5: Modülü kullanma List::MoreUtils

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

12

@ eakssjo'nun ölçütü kırıldı - döngüde karma oluşturma ve döngüde normal ifadeler oluşturma ölçütleri. Sabit sürüm (artı ekledim List::Util::firstve 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} );
        },
    });

Ve sonuç (100_000 yineleme için, @ eakssjo'nun cevabından on kat daha fazla):

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
Birden çok öğeyi test etmek istiyorsanız, önceden karma oluşturmak size zaman kazandırır. Ancak sadece tek bir öğe içerip içermediğini bilmek istiyorsanız, karmaya zaten sahip değilsiniz. Bu nedenle, karma oluşturmak hesaplama süresinin bir parçası olmalıdır. Normal ifade için daha da fazlası: aradığınız her öğe için yeni bir regexp'e ihtiyacınız var.
fishin24

1
@fishinear True, ancak birden fazla kontrolle değil, sadece bir çekle ilgileniyorsanız, hangi mikrosaniyelerin önemli olmadığı için hangi yöntemin daha hızlı olduğunu merak etmek bile mikrooptimizasyon olur. Eğer Bu çeki yinelemek ister ki, karma gitmek yoludur kez karma yaratma nedeni maliyeti daha sonra göz ardı edilecek küçük yeterlidir. Yukarıdaki karşılaştırmalar , herhangi bir kurulum dahil değil, yalnızca farklı test yöntemlerini ölçer . Evet, bu sizin kullanım durumunuzda geçersiz olabilir, ancak yine de - sadece tek bir kontrol yapıyorsanız, size ve arkadaşlarınıza en okunabilir olanı kullanmalısınız.
Xaerxess

10

Kullanmak uygun olsa da, hash'e dönüştür çözümünün oldukça fazla performansa ihtiyacı var gibi görünüyor, bu benim için bir sorundu.

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

Kıyaslama testinin çıktısı:

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
Kullanılması List::Util::firstBir eşleşme bulduğunda o iterating durur durmaz hızlıdır.
RobEarl

1
-1 Kişisel kriter, kusurları vardır grepolan önemli ölçüde eski O (n) ve ikinci O (1) olduğu, yavaş karma oluşturma ve arama yaparak daha. Karma oluşturma işlemini yalnızca bir kez yapın (döngü dışında) ve yalnızca yöntemleri ölçmek için normal ifadeyi önceden hesaplayın ( cevabımı görün ).
Xaerxess

4
@Xaerxess: Benim durumumda bir arama yapmak istedim , bu yüzden hem hash / regex oluşturma hem de lookup / grep'i saymanın adil olduğunu düşünüyorum. Görev çok fazla arama yapmak olurdu, sanırım çözümünüz daha iyi.
aksel

3
Sadece tek bir yineleme yapmak istiyorsanız, seçtiğiniz yöntemlerden herhangi biri arasındaki fark ayırt edilemez, bu nedenle bu durumda kötü bir mikrooptimizasyon olduğu için herhangi bir kıyaslama yanlıştır.
Xaerxess

2
Normal ifade 'o' bayrağına sahip olduğu için yalnızca bir kez derlenir.
Apoc

3

@files mevcut bir dizidir

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

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

print "\n";

/^2[\dülür.[\d Mixed[A-za-z Karışık?/ = 2'den başlayan değerler burada herhangi bir normal ifade koyabilirsiniz


2

Kesinlikle burada bir karma istiyorsun. Hatalı parametreleri karma olarak anahtarlar olarak yerleştirin, sonra karma içinde belirli bir parametrenin var olup olmadığına karar verin.

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

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

Bir dizi ile gerçekten ilgileniyorsanız, List::UtilveyaList::MoreUtils


0

Bunu yapmanın iki yolu vardır. Diğer gönderilerin önerdiği gibi, değerleri arama tablosu için bir karmaya atmak için kullanabilirsiniz. (Başka bir deyim ekleyeceğim.)

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

Ancak, çoğunlukla kelime karakterlerinin verileri ve çok fazla meta değil ise, normal ifade dönüşümüne dökebilirsiniz:

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/;

Bu çözüm, aradığınız "kötü değerler" türleri için ayarlanmalıdır. Ve yine, bazı dizeler için tamamen uygun olmayabilir, bu yüzden uyarı imptoru .


1
Ayrıca yazabilirsiniz @bad_param_lookup{@bad_params} = (), ancak existsüyeliği test etmek için kullanmanız gerekir .
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";
    }
}

Sayısal önde gelen boşlukların tutarlılığını kontrol etmek isteyebilirsiniz

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.