Perl配列に特定の値が含まれているかどうかを確認するにはどうすればよいですか?


239

配列を反復処理せずに、配列内の値の存在を確認する方法を理解しようとしています。

パラメータのファイルを読み取っています。処理したくないパラメーターの長いリストがあります。これらの不要なパラメータを配列に配置しました@badparams

新しいパラメーターを読み取り、それがに存在しない場合は@badparams処理します。に存在する場合は@badparams、次の読み取りに進みます。


3
記録のために、答えはあなたの状況によって異なります。繰り返しルックアップを行いたいように思えるので、jkramerが示唆するようにハッシュを使用することは良いことです。ルックアップを1つだけ作成したい場合は、単に繰り返すだけでもよいでしょう。(そして、場合によっては、ハッシュを使用する代わりにバイナリ検索をしたいかもしれません!)
Cascabel '18


6
記録について(これは状況にまったく当てはまらない場合があります)、一般に、既知の「悪い値」を取り除こうとするよりも、「良い値」を識別して残りを無視する方が良い考えです。あなたが尋ねる必要がある質問は、まだ知らないいくつかの悪い値がある可能性があるかどうかです。
Grant McLean、

回答:


187

配列をハッシュに変換するだけです。

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

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

リストに(一意の)パラメータをさらに追加することもできます。

$params{$newparam} = 1;

そして、後で(一意の)パラメータのリストを取得します。

@badparams = keys %params;

38
記録のために、このコードは配列を繰り返し処理します。map {}を呼び出すと、その反復の入力が非常に簡単になります。
ケニーワイランド

3
@badparamsの値が疑似静的で、マップに対して多くのチェックを行う予定がある場合にのみ、これを行います。これは単一のチェックにはお勧めしません。
アーロンTハリス

これは、同じ値を持つ複数の項目を含む配列に影響を与えませんか?
ロブ・ウェルズ

3
@RobWellsいいえ、問題なく動作します。次に同じ値であることがわかると、ハッシュのエントリが上書きされるだけで、この場合はエントリが1再び設定されます。
andrewrjones 2013年

222

最高の汎用性-特に短い配列(1000アイテム以下)と、どの最適化が自分のニーズに最適であるかわからないコーダー。

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

配列の最初の値が一致する場合でも、grepはすべての値を通過することが言及されています。これは事実ですが、ほとんどの場合grepは非常に高速です。短い配列(1000項目未満)について話している場合、ほとんどのアルゴリズムはとにかくかなり高速になります。非常に長い配列(1,000,000項目)について話している場合、grepは、項目が配列の最初か中間か最後かに関係なく、許容範囲内で高速です。

より長いアレイの最適化ケース:

配列がソートされている場合は、「バイナリ検索」を使用してください

同じ配列が何度も繰り返し検索される場合は、まず配列をハッシュにコピーしてから、ハッシュを確認します。メモリが問題になる場合は、各項目を配列からハッシュに移動します。メモリ効率は向上しますが、元の配列が破壊されます。

場合は、同じ値が繰り返し検索され、アレイ内、なまけキャッシュを構築します。(各アイテムが検索されるとき、最初に検索結果が永続的なハッシュに格納されているかどうかを確認します。検索結果がハッシュに見つからない場合は、配列を検索し、結果を永続的なハッシュに入れて、次回はハッシュでそれを見つけ、検索をスキップします)。

注:これらの最適化は、長い配列を処理する場合にのみ高速になります。過度に最適化しないでください。


12
二重チルドはPerl 5.10で導入されました
追って通知があるまで一時停止しました。

15
@DennisWilliamson ...そして5.18では、実験的と見なされます
Xaerxess 2013年

5
量産コードではsmartmatchを避けてください。これは不安定であり、実験的なものであり、今後の通知を待っています。
Vector Gorgoth 2013年

1
私はそれもより読みやすいと思いますが、使用しないと言うのは効率的ではなく、それが最初のものであってもすべての要素をチェックします。
giordano 2013

7
if( "value" ~~ @array)は使用しないでください。~~はSmartmatchと呼ばれる実験的な機能です。実験は失敗と見なされ、Perlの将来のバージョンで削除または変更される予定です。
yahermann 2017年

120

次のように、Perl 5.10で smartmatch機能を使用できます。

リテラル値の検索については、以下を実行するとうまくいきます。

if ( "value" ~~ @array ) 

スカラー検索の場合、以下を実行すると上記と同様に機能します。

if ($val ~~ @array)

以下を行うインライン配列の場合、上記のように動作します。

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

Perl 5.18実験としてフラグが立てられているスマートマッチしたがって、あなたはをオンにして警告をオフにする必要があり、実験スクリプト/モジュールに下記追加することにより、プラグマ:

use experimental 'smartmatch';

または、smartmatchの使用を避けたい場合は、Aaronが次のように使用します。

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

4
これはすばらしいですが、Perl 5.10の新機能のようです。構文エラーが発生する理由を理解するまでに少し時間がかかりました。
Igor Skochinsky 2014年

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であるコアモジュール
Onlyjob

13

方法1:grep(値が正規表現であると予想される間は注意が必要です)。

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

そして結果(@eakssjoの回答よりも10倍多い100_000回の反復に対するものです):

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
複数の要素をテストする場合は、事前にハッシュを作成しておくと時間を節約できます。しかし、それが単一の要素を含むかどうかを知りたいだけなら、ハッシュはまだありません。したがって、ハッシュの作成は計算時間の一部である必要があります。正規表現の場合はなおさらです。検索する要素ごとに新しい正規表現が必要です。
2013年

1
@fishinear True、ただし、複数のチェックではなく1つのチェックのみに関心がある場合は、マイクロ最適化により、マイクロ秒が重要ではないため、どちらのメソッドがより高速であるかを疑問に思うことは明らかです。このチェックをやり直したい場合は、ハッシュを使用することをお勧めします。一度ハッシュを作成すると、無視できるほどのコストになるためです。上記のベンチマークは、セットアップを含まない、さまざまなテスト方法のみを測定します。はい、これはあなたのユースケースでは無効かもしれませんが、繰り返しになります-単一のチェックのみを行う場合は、あなたとあなたの仲間にとって最も読みやすいものを使用すべきです。
Xaerxess、2015

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)であるので、遅いハッシュを作成し、ルックアップ行うより。ハッシュの作成を1回だけ(ループの外で)行い、正規表現を事前計算してメソッドのみを測定します(私の答えを参照)。
Xaerxess、

4
@Xaerxess:私の場合、1つのルックアップを実行したかったので、ハッシュ/正規表現の作成とルックアップ/ grepの両方をカウントするのは公平だと思います。それは多くのルックアップを行うことがタスクであろう、私はあなたの解決策がより良いと思います。
aksel 2013年

3
反復を1回だけ実行したい場合、選択した方法の違いは区別できません。この場合、ベンチマークは悪いマイクロ最適化であるため、間違っています。
Xaerxess、2013年

2
正規表現はフラグ 'o'を持っているため、一度だけコンパイルされます。
Apoc 2014

3

@filesは既存の配列です

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

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

print "\n";

/^2[\d].[\d][A-za-z]?/ = 2から始まる値ここに任意の正規表現を置くことができます


2

確かにここでハッシュが必要です。不良パラメーターをキーとしてハッシュに配置し、特定のパラメーターがハッシュに存在するかどうかを判断します。

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

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

配列でそれを行うことに本当に興味がある場合はList::UtilList::MoreUtils


0

これを行うには、2つの方法があります。他の投稿で提案されているように、ルックアップテーブルのハッシュに値をスローすることができます。(もう1つのイディオムを追加します。)

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

しかし、ほとんどが単語文字でメタが多すぎないデータの場合は、正規表現の代替にダンプできます。

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してメンバーシップをテストする必要があります。
グレッグベーコン

-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.