必要な動作を得るための主な変更点は、別の順序でループすることです。つまり、主にセット 2 をループし、次にセット 1 をループします。これは、セット 2 のプロパティに関心があるため、プログラムするより自然な方法です。変化を詳しく見てください。
strict
とwarnings
プラグマタを有効にすると良いです!必ず守ってください。
#! /usr/bin/env perl
use strict;
use warnings;
完全にばらばらな範囲について質問されたので、ここでセット 2 に追加します。
#Set 1: 1..6, 2..7, 3..8, 4..9, 5..10
my @set1_low = (1..5);
my @set1_up = (6..10);
my @set1 = ([@set1_low],[@set1_up]);
#Set 2: 2..7, 2..6, 22..32
my @set2_low = (2,2,22);
my @set2_up = (7,6,32);
my @set2 = ([@set2_low],[@set2_up]);
サイズを計算するには、スカラーに割り当てているため、スカラー コンテキストが既に暗示されています。これらをより簡潔に書く方法はmy $size1 = @set1_low;
、たとえば です。
my $size1 = scalar(@set1_low);
my $size2 = scalar(@set2_low);
完全なミスをカウントしたいので、ここで を追加し$no_match
ます。
my $low_count=0;
my $up_count=0;
my $match=0;
my $no_match=0;
ループが反転していることに注意して$a
ください$b
。セット 2 の範囲ごとに、コードはセット 1 の範囲を順番に調べて、最初に満たされたプロパティを検索します。ヒット時には、セット 1 の残りの範囲を考慮する必要はありません。なぜなら、あなたの質問ではダブルカウントをしたくないと述べているため、内側のループを で終了しますlast
。プログラムで最初に発生するテストが優先されるように、プロパティを重要度の高い順に並べます。
どのプロパティも一致しない場合 (つまり、$found_match
false のままである場合) は、空振りを記録します。
文体上の注意として、単にコードを英語で言い換えるだけのコメントは価値がありません。コメントは理由と非自明な情報を説明するためのものなので、以下で削除しました。
for(my $b=0; $b < $size2; $b++){
my $found_match=0;
for(my $a=0; $a < $size1; $a++){
my ($lower,$upper) = ($set1[0][$a],$set1[1][$a]);
if ($lower==$set2[0][$b] && $upper==$set2[1][$b]){
$match++;
$found_match++;
last;
}
elsif ($lower==$set2[0][$b] && $upper!=$set2[1][$b]){
$low_count++;
$found_match++;
last;
}
elsif ($lower!=$set2[0][$b] && $upper==$set2[1][$b]){
$up_count++;
$found_match++;
last;
}
}
unless ($found_match) {
$no_match++;
}
}
最後に、結果を印刷します。
print "Perfect match: $match\n";
print "lower match, upper unmatch: $low_count\n";
print "upper match, lower unmatch: $up_count\n";
print "No match: $no_match\n";
出力:
完全一致: 1
下位一致、上位不一致: 0
上位一致、下位不一致: 1
一致しない: 1
上記のデータ構造とスタイルは、Perl にとって少し不自然です。解決しようとしている問題の状況について詳しく教えていただければ、より役立つ提案を行うことができます。
より多くのテスト (部分的なオーバーラップなど) を追加する負担により、より良いデータ構造を選択することが急速に迫られます。C プログラムで行う必要があるかもしれないように、上限と下限に並列配列を使用する代わりに、各範囲を単位として扱います。
my @set1 = ([1, 6], [2, 7], [3, 8], [4, 9], [5, 10], [90, 150]);
my @set2 = ([2, 7], [2, 6], [7, 8], [22, 32], [80, 140]);
下限と上限が同じスカラー (この場合は配列への参照) に関連付けられているため、2 つの範囲が下限を共有しているかどうか、または上限を共有しているかどうかを尋ねることができます。
sub lowers { $_[0][0] == $_[1][0] }
sub uppers { $_[0][1] == $_[1][1] }
これらが定義されていれば、完全一致のテストは簡単です。
sub match { lowers(@_) && uppers(@_) }
オーバーラップをテストするとき、and を書く$range1[0]
の$range2[1]
はすぐに面倒になるので、以下では範囲を ( a 0 , a 1 ) と ( b 0 , b 1 ) に分解します。次に、一方の範囲のいずれかのエンドポイントが他方の範囲内にあるかどうかをテストします。
sub overlap {
my($a0,$a1,$b0,$b1) = map @$_, @_;
$a0 >= $b0 && $a0 <= $b1 || $a1 >= $b0 && $a1 <= $b1;
}
これらの各条件をテストするためのコードは、呼び出す関数とインクリメントするカウントが異なるだけで、ほとんど同じになるので、テストを因数分解してそれぞれのカウントに関連付けましょう。@tests
テストは互いに重複していますが、多くても 1 つのテストが「信用」されるため、期待する結果が得られるように順序を変更する準備をしてください。
my $low_count=0;
my $up_count=0;
my $match=0;
my $overlap=0;
my $no_match=0;
my @tests = (
[\&match, \$match],
[\&lowers, \$low_count],
[\&uppers, \$up_count],
[\&overlap, \$overlap],
);
アルゴリズムのコアは驚くほど簡潔になりました。コードは、他の人に説明する方法に似ています。つまり、セット 2 の各範囲について、次に各テストについて、セット 1 のすべての範囲をスキャンします。一致したら、成功を記録し、セット 2 の次の範囲に進みます。すべてのテストを試して成功しなかった後、失敗に注意して続行します。
SET2:
foreach my $two (@set2) {
for (@tests) {
my($test,$count) = @$_;
if (grep $test->($_, $two), @set1) {
++$$count;
next SET2;
}
}
++$no_match;
}
はい、アルゴリズムは簡潔ですが、二次時間の複雑さがあります。これは、たとえば、セットのサイズを 3 倍にすると、約 9 倍の速度低下が生じることを意味します。
出力コードは期待どおりです。
print "Perfect match: $match\n";
print "lower match, upper unmatch: $low_count\n";
print "upper match, lower unmatch: $up_count\n";
print "Overlap: $overlap\n";
print "No match: $no_match\n";
出力:
完全一致: 1
下位一致、上位不一致: 1
上位一致、下位不一致: 1
重複: 1
一致しない: 1