Algorithm 在Perl中确定范围重叠的最快方法
我有两套射程。每个范围都是一对整数(开始和结束),表示单个较大范围的某个子范围。这两组范围的结构与此类似(当然…s将被实际数字替换) 我需要确定集合A中的哪些范围与集合B中的哪些范围重叠。给定两个范围,很容易确定它们是否重叠。我只是简单地使用了一个双循环来实现这一点——在外循环中遍历集合a中的所有元素,在内循环中遍历集合B中的所有元素,并跟踪哪些元素重叠 这种方法有两个问题。首先,重叠空间非常稀疏——即使每个集合中有数千个范围,我希望集合A中的每个范围都与集合B中的1或2个范围重叠。我的方法列举了每一种可能性,这是过分的。这导致了我的第二个问题——它的伸缩性非常差。当每个集合中有数百个范围时,代码完成得非常快(亚分钟),但当每个集合中有数千个范围时,代码完成得非常长(+/-30分钟) 有没有更好的方法可以索引这些范围,这样我就不会对重叠进行太多不必要的检查Algorithm 在Perl中确定范围重叠的最快方法,algorithm,perl,range,overlap,Algorithm,Perl,Range,Overlap,我有两套射程。每个范围都是一对整数(开始和结束),表示单个较大范围的某个子范围。这两组范围的结构与此类似(当然…s将被实际数字替换) 我需要确定集合A中的哪些范围与集合B中的哪些范围重叠。给定两个范围,很容易确定它们是否重叠。我只是简单地使用了一个双循环来实现这一点——在外循环中遍历集合a中的所有元素,在内循环中遍历集合B中的所有元素,并跟踪哪些元素重叠 这种方法有两个问题。首先,重叠空间非常稀疏——即使每个集合中有数千个范围,我希望集合A中的每个范围都与集合B中的1或2个范围重叠。我的方法列举
更新:我要寻找的输出是两个散列(每组范围一个),其中键是范围ID,值是另一个集合中与该集合中给定范围重叠的范围ID。这听起来像是,这是专门为支持此操作而设计的数据结构。如果有两组大小分别为m和n的区间,则可以在时间O(m lg m)中将其中一组构建到区间树中,然后在时间O(n lg m+k)中进行n次交叉查询,其中k是找到的交叉点总数。这给出了O((m+n)lgm+k)的净运行时间。请记住,在最坏的情况下,k=O(nm),因此这并不比您拥有的更好,但是对于交叉点数量稀少的情况,这可能比您现在拥有的O(mn)运行时要好得多 我没有太多的使用区间树的经验(抱歉,Perl中没有经验),但是从描述来看,构建它们应该不那么困难。如果还没有,我会很惊讶
希望这有帮助 以防您不完全依赖perl;R中的IRanges包处理区间算术。它有非常强大的原语,用它们编写解决方案可能很容易
第二点意见是,如果间隔具有附加结构,问题可能变得非常容易;例如,如果在每一组范围内没有重叠(在这种情况下,线性方法可以同时筛选两个有序集)。即使在没有这种结构的情况下,您至少可以按起点对一组范围进行排序,按终点对另一组范围进行排序,然后在不再可能进行匹配时中断内部循环。当然,现有的和通用的算法和数据结构(如前面提到的区间树)是最强大的。尝试tree::RB,但要找到相互排斥的范围,没有重叠 如果我有大约10000个片段,并且必须为每个离散的数字找到片段,那么性能还不错。我的输入有3亿条记录。我打算把它们分别放进不同的桶里。比如对数据进行分区。树: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;
}
my$cmp=sub
{
我的($a1,$b1)=@;
if(参考($b1)和参考($a1))
{
返回($$a1[1])($$b1[0]);
}
我的$ret=0;
if(参考($a1)等式“数组”)
{
#
如果($$a1[0]=$$a1[1])
{
$ret=0;
}
如果($$a1[0]<$b1)
{
$ret=-1;
}
如果($$a1[1]>$b1)
{
$ret=1;
}
}
其他的
{
如果($$b1[0]=$$b1[1])
{
$ret=0;
}
如果($$b1[0]>$a1)
{
$ret=-1;
}
如果($$b1[1]<$a1)
{
$ret=1;
}
}
返回$ret;
}
有几个现有的CPAN模块可以解决这个问题,我开发了其中的两个:Data::Range::Compare和Data::Range::Compare::Stream
Data::Range::Compare仅适用于内存中的数组,但支持通用范围类型
Data::Range::Compare::Stream通过迭代器处理数据流,但它需要理解OO Perl才能扩展到通用数据类型。如果您正在处理非常大的数据集,建议使用Data::Range::Compare::Stream
下面是Data::Range::Compare::Stream示例文件夹的摘录
考虑到这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";
我应该检查时间,以了解这是否是最快的方法,但根据您的数据结构,您应该尝试以下方法:
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;
}
使用严格;
我的$fromA=12;
我的$toA=15;
我的$fromB=7;
我的$toB=35;
my@common\u range=获取常用范围($fromA、$toA、$fromB、$toB);
我的$common_range=$common_range[0]。“-”$common_range[-1];
子get_公共_范围{
my@A=$\[0]..$\[1];
我的%B=map{$\=>1}$\[2]..$\[3];
我的@common=();
每个我的$i(@A){
if(定义为$B{$i}){
推送(@common$i);
}
}
返回排序{$a$b}@common;
}
您能确认您需要什么输出吗?“哪个范围与哪个范围重叠”有点含糊不清,我读它的时候好像你想要一个(a_I,b_j)对的列表,但你可能指的是一个间隔的列表--(标准,结束)对。@JB谢谢你提出这个问题,我已经更新了这个问题。谢谢你的id
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;
}