4

範囲は2セットあります。各範囲は、単一のより大きな範囲のサブ範囲を表す整数のペア(開始と終了)です。2セットの範囲は、これと同様の構造になっています(もちろん、...は実際の数値に置き換えられます)。

$a_ranges =
{
  a_1 =>
  {
    start => ...,
    end   => ...,
  },
  a_2 =>
  {
    start => ...,
    end   => ...,
  },
  a_3 =>
  {
    start => ...,
    end   => ...,
  },
  # and so on
};

$b_ranges =
{
  b_1 =>
  {
    start => ...,
    end   => ...,
  },
  b_2 =>
  {
    start => ...,
    end   => ...,
  },
  b_3 =>
  {
    start => ...,
    end   => ...,
  },
  # and so on
};

セットAのどの範​​囲がセットBのどの範囲とオーバーラップするかを判断する必要があります。2つの範囲が与えられると、それらがオーバーラップするかどうかを簡単に判断できます。私はこれを行うために単にダブルループを使用しています-外側のループのセットAのすべての要素をループし、内側のループのセットBのすべての要素をループし、どの要素がオーバーラップしているかを追跡します。

このアプローチには2つの問題があります。まず、オーバーラップスペースが非常にまばらであるということです。各セットに数千の範囲がある場合でも、セットAの各範囲がセットBの1つまたは2つの範囲とオーバーラップすると予想されます。私のアプローチでは、すべての可能性を列挙します。やり過ぎ。これは私の2番目の問題につながります-それが非常に不十分にスケーリングするという事実。各セットに数百の範囲がある場合、コードは非常に速く(1分未満)終了しますが、各セットに数千の範囲がある場合、非常に長い時間(+/- 30分)かかります。

これらの範囲にインデックスを付けて、重複について不必要なチェックをあまり行わないようにするためのより良い方法はありますか?

更新:私が探している出力は2つのハッシュ(範囲の各セットに1つ)で、キーは範囲IDであり、値はこのセットの特定の範囲と重複する他のセットの範囲のIDです。

4

5 に答える 5

10

これは、この操作をサポートするために特別に設計されたデータ構造であるInterval Treeの完璧な使用例のように思えます。サイズが m と n の間隔のセットが 2 つある場合、そのうちの 1 つを時間 O(m lg m) で間隔ツリーに構築し、時間 O(n lg m + k) で n 個の交差クエリを実行できます。ここで、 k は、見つけた交点の総数です。これにより、O((m + n) lg m + k) の正味ランタイムが得られます。最悪の場合 k = O(nm) であるため、これはあなたが持っているものよりも優れているわけではありませんが、交差点の数がまばらな場合、これは正しい O(mn) ランタイムよりも大幅に優れている可能性があります。今。

私はインターバル ツリーを扱った経験はあまりありません (そして Perl の経験はゼロです。申し訳ありません!) が、説明から、構築するのはそれほど難しくないように思われます。まだ存在していなかったら、かなり驚かれることでしょう。

お役に立てれば!

于 2011-01-13T08:34:10.120 に答える
4

perl だけに縛られているわけではない場合に備えて。R の IRanges パッケージは区間演算を扱います。これには非常に強力なプリミティブがあり、それらを使用してソリューションをコーディングするのはおそらく簡単でしょう。

2 つ目の注意点は、間隔に追加の構造があれば、問題が非常に簡単になる可能性があるということです。たとえば、範囲の各セット内で重複がない場合 (その場合、2 つの順序付けられたセットを同時にふるいにかける線形アプローチが可能です)。そのような構造がない場合でも、できることは、範囲の 1 つのセットを開始点でソートし、もう 1 つのセットを終了点でソートし、一致が不可能になったら内側のループから抜け出すことだけです。もちろん、前述のインターバル ツリーなどの既存の一般的なアルゴリズムとデータ構造が最も強力です。

于 2011-01-13T11:47:24.207 に答える
3

この問題を解決するいくつかの既存の CPAN モジュールがあります。私はそのうちの 2 つを開発しました: Data::Range::Compare と Data::Range::Compare::Stream

Data::Range::Compare はメモリ内の配列でのみ機能しますが、一般的な範囲型をサポートします。

Data::Range::Compare::Stream イテレータを介してデータのストリームを処理しますが、一般的なデータ型に拡張するには OO Perl を理解する必要があります。非常に大きなデータ セットを処理する場合は、Data::Range::Compare::Stream をお勧めします。

以下は、Data::Range::Compare::Stream の Examples フォルダーからの抜粋です。

次の 3 つのデータ セットがあるとします。

Numeric Range set: A contained in file: source_a.src
+----------+
| 1 - 11   |
| 13 - 44  |
| 17 - 23  |
| 55 - 66  |
+----------+

Numeric Range set: B contained in file: source_b.src
+----------+
| 0  - 1   |
| 2  - 29  |
| 88 - 133 |
+----------+

Numeric Range set: C contained in file: source_c.src
+-----------+
| 17  - 29  |
| 220 - 240 |
| 241 - 250 |
+-----------+

予想される出力は次のようになります。

+--------------------------------------------------------------------+
| Common Range | Numeric Range A | Numeric Range B | Numeric Range C |
+--------------------------------------------------------------------+
| 0 - 0        |   No Data       |   0 - 1         |   No Data       |
| 1 - 1        |   1 - 11        |   0 - 1         |   No Data       |
| 2 - 11       |   1 - 11        |   2 - 29        |   No Data       |
| 12 - 12      |   No Data       |   2 - 29        |   No Data       |
| 13 - 16      |   13 - 44       |   2 - 29        |   No Data       |
| 17 - 29      |   13 - 44       |   2 - 29        |   17 - 29       |
| 30 - 44      |   13 - 44       |   No Data       |   No Data       |
| 55 - 66      |   55 - 66       |   No Data       |   No Data       |
| 88 - 133     |   No Data       |   88 - 133      |   No Data       |
| 220 - 240    |   No Data       |   No Data       |   220 - 240     |
| 241 - 250    |   No Data       |   No Data       |   241 - 250     |
+--------------------------------------------------------------------+

ソースコードはここにあります。

#!/usr/bin/perl

use strict;
use warnings;
use Data::Dumper;
use lib qw(./ ../lib);

# custom package from FILE_EXAMPLE.pl
use Data::Range::Compare::Stream::Iterator::File;


use Data::Range::Compare::Stream;
use Data::Range::Compare::Stream::Iterator::Consolidate;
use Data::Range::Compare::Stream::Iterator::Compare::Asc;

my $source_a=Data::Range::Compare::Stream::Iterator::File->new(filename=>'source_a.src');
my $source_b=Data::Range::Compare::Stream::Iterator::File->new(filename=>'source_b.src');
my $source_c=Data::Range::Compare::Stream::Iterator::File->new(filename=>'source_c.src');

my $consolidator_a=new Data::Range::Compare::Stream::Iterator::Consolidate($source_a);
my $consolidator_b=new Data::Range::Compare::Stream::Iterator::Consolidate($source_b);
my $consolidator_c=new Data::Range::Compare::Stream::Iterator::Consolidate($source_c);


my $compare=new  Data::Range::Compare::Stream::Iterator::Compare::Asc();

my $src_id_a=$compare->add_consolidator($consolidator_a);
my $src_id_b=$compare->add_consolidator($consolidator_b);
my $src_id_c=$compare->add_consolidator($consolidator_c);



print "  +--------------------------------------------------------------------+
  | Common Range | Numeric Range A | Numeric Range B | Numeric Range C |
  +--------------------------------------------------------------------+\n";

my $format='  | %-12s |   %-13s |   %-13s |   %-13s |'."\n";
while($compare->has_next) {
  my $result=$compare->get_next;
  my $string=$result->to_string;
  my @data=($result->get_common);
  next if $result->is_empty;
  for(0 .. 2) {
     my $column=$result->get_column_by_id($_);
     unless(defined($column)) {
      $column="No Data";
    } else {
      $column=$column->get_common->to_string;
    }
     push @data,$column;


   }
  printf $format,@data;

  } 
 print "  +--------------------------------------------------------------------+\n";
于 2012-03-30T16:37:27.623 に答える
1

最速の方法かどうかを知るために時間を確認する必要がありますが、データの構造に応じて、これを試す必要があります。

use strict;

my $fromA = 12;
my $toA = 15;
my $fromB = 7;
my $toB = 35;

my @common_range = get_common_range($fromA, $toA, $fromB, $toB);
my $common_range = $common_range[0]."-".$common_range[-1];

sub get_common_range {
  my @A = $_[0]..$_[1];
  my %B = map {$_ => 1} $_[2]..$_[3];
  my @common = ();

  foreach my $i (@A) {
    if (defined $B{$i}) {
      push (@common, $i);
    } 
  }
  return sort {$a <=> $b} @common;
}
于 2013-09-17T08:13:35.127 に答える
1

Tree::RB を試してみてください。ただし、相互に排他的な範囲を見つけるには、オーバーラップはありません

約 10000 個のセグメントがあり、個々の数値ごとにセグメントを見つけなければならない場合、パフォーマンスは悪くありません。私の入力には 3 億件のレコードがありました。私はそれらを別々のバケツに入れる必要がありました。データの分割のように。Tree::RB はうまくいきました。

$var = [
[0,90],
[91,2930],
[2950,8293]
.
.
.
]

私のルックアップ値は10、99、991でした...

基本的に、指定された数値の範囲の位置が必要でした

この以下の比較関数では、次のようなものを使用します。

my $cmp = sub
{
    my ($a1, $b1) = @_;

    if(ref($b1) && ref($a1))
    {
        return ($$a1[1]) <=> ($$b1[0]);
    }

    my $ret = 0;

    if(ref($a1) eq 'ARRAY')
    {
        #
        if($$a1[0] <= $b1 && $b1 >= $$a1[1])
        {
            $ret =  0;
        }
        if($$a1[0] < $b1)
        {
            $ret =  -1;
        }
        if($$a1[1] > $b1)
        {
            $ret =  1;
        }
    }
    else
    {
        if($$b1[0] <= $a1 && $a1 >= $$b1[1])
        {
            $ret =  0;
        }
        if($$b1[0] > $a1)
        {
            $ret =  -1;
        }
        if($$b1[1] < $a1)
        {
            $ret =  1;
        }
    }

    return $ret;
}
于 2011-04-01T19:03:38.453 に答える