2つの配列があります。ある要素が他の要素に現れるかどうかを確認する必要があります。
ネストされたループよりも効率的な方法はありますか?それぞれに数千の要素があり、プログラムを頻繁に実行する必要があります。
別の方法は Array :: Utils を使用することです
use Array::Utils qw(:all);
my @a = qw( a b c d );
my @b = qw( c d e f );
# symmetric difference
my @diff = array_diff(@a, @b);
# intersection
my @isect = intersect(@a, @b);
# unique union
my @unique = unique(@a, @b);
# check if arrays contain same members
if ( !array_diff(@a, @b) ) {
# do something
}
# get items from array @a that are not in array @b
my @minus = array_minus( @a, @b );
perlfaq4
救助に:
2つの配列の差を計算するにはどうすればよいですか? 2つの配列の交差を計算するにはどうすればよいですか?
ハッシュを使用します。以下は、両方を行うためのコードです。各要素は特定の配列内で一意であると想定しています。
@union = @intersection = @difference = (); %count = (); foreach $element (@array1, @array2) { $count{$element}++ } foreach $element (keys %count) { Push @union, $element; Push @{ $count{$element} > 1 ? \@intersection : \@difference }, $element; }
変数を適切に宣言すると、コードは次のようになります。
my %count;
for my $element (@array1, @array2) { $count{$element}++ }
my ( @union, @intersection, @difference );
for my $element (keys %count) {
Push @union, $element;
Push @{ $count{$element} > 1 ? \@intersection : \@difference }, $element;
}
より多くのコンテキストを提供する必要があります。それを行うより効率的な方法があります:
Perlの外に出て、シェルを使用します(sort
+ comm
)
map
1つの配列をPerlハッシュに入れてから、もう1つをループしてハッシュメンバーシップをチェックします。これは、「M * N」の複雑さを持つネストされたループとは対照的に、線形の複雑さ(「M + N」-基本的に各配列を1回ループします)
例:
my %second = map {$_=>1} @second;
my @only_in_first = grep { !$second{$_} } @first;
# use a foreach loop with `last` instead of "grep"
# if you only want yes/no answer instead of full list
最後の箇条書きを行うPerlモジュールを使用します(List :: Compareはコメントで言及されています)
ボリュームが非常に大きく、頻繁に再比較する必要がある場合は、要素が追加されたときのタイムスタンプに基づいて実行します。数千個の要素では十分な大きさではありませんが、最近、100kサイズのリストを比較する必要がありました。
Arrays::Utils
を試してみると、見栄えがよくシンプルに見えますが、バックエンドで強力な魔法を実行していません。 array_diffs
コードは次のとおりです。
sub array_diff(\@\@) {
my %e = map { $_ => undef } @{$_[1]};
return @{[ ( grep { (exists $e{$_}) ? ( delete $e{$_} ) : ( 1 ) } @{ $_[0] } ), keys %e ] };
}
Arrays::Utils
は標準モジュールではないので、このモジュールをインストールして保守するのに努力する価値があるかどうかを自問する必要があります。そうでなければ、それは [〜#〜] dvk [〜#〜] の答えにかなり近い。
気を付けなければならない特定の事柄があり、その特定のケースで何をしたいのかを定義する必要があります。まあ言ってみれば:
@array1 = qw(1 1 2 2 3 3 4 4 5 5);
@array2 = qw(1 2 3 4 5);
これらの配列は同じですか?または、彼らは異なっていますか?それらは同じ値を持ちますが、@array1
ではなく@array2
に重複があります。
これはどうですか?
@array1 = qw( 1 1 2 3 4 5 );
@array2 = qw( 1 1 2 3 4 5 );
これらの配列は同じであると言いますが、Array::Utils::arrays_diff
は異なります。これは、Array::Utils
が重複エントリがないことを前提としているためです。
また、Perl FAQ mob によって指摘されている場合でも、は、各要素が特定の配列で一意であると仮定しますこれはあなたが作ることができる仮定ですか?
何があっても、ハッシュが答えです。ハッシュを検索するのは簡単かつ迅速です。問題は、一意の値をどうするかです。
重複が問題にならないことを前提とする堅牢なソリューションを次に示します。
sub array_diff {
my @array1 = @{ shift() };
my @array2 = @{ shift() };
my %array1_hash;
my %array2_hash;
# Create a hash entry for each element in @array1
for my $element ( @array1 ) {
$array1_hash{$element} = @array1;
}
# Same for @array2: This time, use map instead of a loop
map { $array_2{$_} = 1 } @array2;
for my $entry ( @array2 ) {
if ( not $array1_hash{$entry} ) {
return 1; #Entry in @array2 but not @array1: Differ
}
}
if ( keys %array_hash1 != keys %array_hash2 ) {
return 1; #Arrays differ
}
else {
return 0; #Arrays contain the same elements
}
}
重複が問題になる場合は、それらをカウントする方法が必要です。配列内の各要素によってキー設定されたハッシュを作成するだけでなく、配列内の重複もカウントするために、mapを使用しています。
my %array1_hash;
my %array2_hash;
map { $array1_hash{$_} += 1 } @array1;
map { $array2_hash{$_} += 2 } @array2;
これで、各ハッシュを調べて、キーが存在するだけでなく、それらのエントリが一致することを確認できます
for my $key ( keys %array1_hash ) {
if ( not exists $array2_hash{$key}
or $array1_hash{$key} != $array2_hash{$key} ) {
return 1; #Arrays differ
}
}
%array1_hash
のすべてのエントリが%array2_hash
の対応するエントリと一致する場合にのみforループを終了します。ここで、%array2_hash
のすべてのエントリが%array1_hash
のエントリとも一致し、%array2_hash
にこれ以上エントリがないことを示す必要があります。幸いなことに、以前やったことができます。
if ( keys %array2_hash != keys %array1_hash ) {
return 1; #Arrays have a different number of keys: Don't match
}
else {
return; #Arrays have the same keys: They do match
}
これを使用して、2つの配列の違いを取得できます。
#!/usr/bin/Perl -w
use strict;
my @list1 = (1, 2, 3, 4, 5);
my @list2 = (2, 3, 4);
my %diff;
@diff{ @list1 } = undef;
delete @diff{ @list2 };
my @a = (1,2,3);
my @b=(2,3,1);
print "Equal" if grep { $_ ~~ @b } @a == @b;
n + n log nアルゴリズム、要素が各配列で一意であることが確実な場合(ハッシュキーとして)
my %count = ();
foreach my $element (@array1, @array2) {
$count{$element}++;
}
my @difference = grep { $count{$_} == 1 } keys %count;
my @intersect = grep { $count{$_} == 2 } keys %count;
my @union = keys %count;
そのため、私は団結がわからず、array2内のarray1の要素の存在を確認したい場合
my %count = ();
foreach (@array1) {
$count{$_} = 1 ;
};
foreach (@array2) {
$count{$_} = 2 if $count{$_};
};
# N log N
if (grep { $_ == 1 } values %count) {
return 'Some element of array1 does not appears in array2'
} else {
return 'All elements of array1 are in array2'.
}
# N + N log N
エレガントではないが、理解しやすい:
#!/usr/local/bin/Perl
use strict;
my $file1 = shift or die("need file1");
my $file2 = shift or die("need file2");;
my @file1lines = split/\n/,`cat $file1`;
my @file2lines = split/\n/,`cat $file2`;
my %lines;
foreach my $file1line(@file1lines){
$lines{$file1line}+=1;
}
foreach my $file2line(@file2lines){
$lines{$file2line}+=2;
}
while(my($key,$value)=each%lines){
if($value == 1){
print "$key is in only $file1\n";
}elsif($value == 2){
print "$key is in only $file2\n";
}elsif($value == 3){
print "$key is in both $file1 and $file2\n";
}
}
exit;
__END__
@xの各要素を@yの同じインデックスの要素と比較したいですか?これでできます。
print "Index: $_ => \@x: $x[$_], \@y: $y[$_]\n"
for grep { $x[$_] != $y[$_] } 0 .. $#x;
...または...
foreach( 0 .. $#x ) {
print "Index: $_ => \@x: $x[$_], \@y: $y[$_]\n" if $x[$_] != $y[$_];
}
どちらを選択するかは、異なる要素のインデックスのリストを保持することに関心があるか、単に不一致を1つずつ処理することに関心があるかによって異なります。 grepバージョンは、不一致のリストを取得するのに便利です。 ( 元の投稿 )
List:Compareを使用してみてください。 ITには、アレイで実行できるすべての操作に対するソリューションがあります。 https://metacpan.org/pod/List::Compare