如何有效地计算 Perl 中覆盖给定范围的范围?

2024-05-07

我有一个大约 30k 范围的数据库,每个范围都作为一对起点和终点给出:

[12,80],[34,60],[34,9000],[76,743],...

我想编写一个 Perl 子例程来表示一个范围(不是来自数据库),并返回数据库中完全“包含”给定范围的范围数。

例如,如果数据库中只有这 4 个范围,并且查询范围是[38,70],子程序应该返回2,因为第一个和第三个范围都完全包含查询范围。

问题:我希望使查询尽可能“便宜”,如果有帮助的话,我不介意进行大量预处理。

一些注意事项:

  1. 我随意使用了“数据库”这个词,我并不是指实际的数据库(例如 SQL);这只是一长串范围。

  2. 我的世界是圆形的...有一个给定的max_length (e.g. 9999)和范围如[8541,6]是合法的(您可以将其视为单个范围,它是[8541,9999] and [1,6]).

谢谢, 戴夫

UPDATE这是我原来的代码:

use strict;
use warnings;

my $max_length = 200;
my @ranges     = (
    { START => 10,   END => 100 },
    { START => 30,   END => 90 },
    { START => 50, END => 80 },
    { START => 180,  END => 30 }
);

sub n_covering_ranges($) {
    my ($query_h) = shift;
    my $start     = $query_h->{START};
    my $end       = $query_h->{END};
    my $count     = 0;
    if ( $end >= $start ) {

        # query range is normal
        foreach my $range_h (@ranges) {
            if (( $start >= $range_h->{START} and $end <= $range_h->{END} )
                or (    $range_h->{END} <= $range_h->{START} and  $range_h->{START} <= $end )
                or ( $range_h->{END} <= $range_h->{START} and  $range_h->{END} >= $end)
                )
            {
                $count++;
            }
        }

    }

    else {

        # query range is hanging over edge
        # only other hanging over edges can contain it
        foreach my $range_h (@ranges) {
            if ( $start >= $range_h->{START} and $end <= $range_h->{END} ) {
                $count++;
            }
        }

    }

    return $count;
}

print n_covering_ranges( { START => 1, END => 10 } ), "\n";
print n_covering_ranges( { START => 30, END => 70 } ), "\n";

是的,我知道if它们很丑陋,但可以做得更好、更高效。

更新 2 - 基准测试建议的解决方案

到目前为止,我已经对两种专用解决方案进行了一些基准测试:天真的一个 https://stackoverflow.com/questions/3790166/how-can-i-efficiently-count-ranges-that-cover-a-given-range-in-perl/3790411#3790411,由cjm建议,这与我原来的解决方案类似,并且对记忆力要求较高的一 https://stackoverflow.com/questions/3790166/how-can-i-efficiently-count-ranges-that-cover-a-given-range-in-perl/3790446#3790446,由 Aristotle Pagaltzis 建议 再次感谢你们俩!

为了比较两者,我创建了以下使用相同接口的包:

use strict;
use warnings;

package RangeMap;

sub new {
    my $class      = shift;
    my $max_length = shift;
    my @lookup;
    for (@_) {
        my ( $start, $end ) = @$_;
        my @idx
            = $end >= $start
            ? $start .. $end
            : ( $start .. $max_length, 0 .. $end );
        for my $i (@idx) { $lookup[$i] .= pack 'L', $end }
    }
    bless \@lookup, $class;
}

sub num_ranges_containing {
    my $self = shift;
    my ( $start, $end ) = @_;
    return 0 unless defined $self->[$start];
    return 0 + grep { $end <= $_ } unpack 'L*', $self->[$start];
}

1;

and:

use strict;
use warnings;

package cjm;

sub new {
    my $class      = shift;
    my $max_length = shift;

    my $self = {};
    bless $self, $class;

    $self->{MAX_LENGTH} = $max_length;

    my @normal  = ();
    my @wrapped = ();

    foreach my $r (@_) {
        if ( $r->[0] <= $r->[1] ) {
            push @normal, $r;
        }
        else {
            push @wrapped, $r;
        }
    }

    $self->{NORMAL}  = \@normal;
    $self->{WRAPPED} = \@wrapped;
    return $self;
}

sub num_ranges_containing {
    my $self = shift;
    my ( $start, $end ) = @_;

    if ( $start <= $end ) {

        # This is a normal range
        return ( grep { $_->[0] <= $start and $_->[1] >= $end }
                @{ $self->{NORMAL} } )
            + ( grep { $end <= $_->[1] or $_->[0] <= $start }
                @{ $self->{WRAPPED} } );
    }
    else {

        # This is a wrapped range
        return ( grep { $_->[0] <= $start and $_->[1] >= $end }
                @{ $self->{WRAPPED} } )

            # This part should probably be calculated only once:
            + ( grep { $_->[0] == 1 and $_->[1] == $self->{MAX_LENGTH} }
                @{ $self->{NORMAL} } );
    }
}

1;

然后我使用了一些真实数据:$max_length=3150000,大约 17000 个范围,平均大小为几千个,最后用大约 10000 个查询来查询对象。我对对象的创建(添加所有范围)和查询进行了计时。结果:

cjm creation done in 0.0082 seconds
cjm querying done in 21.209857 seconds
RangeMap creation done in 45.840982 seconds
RangeMap querying done in 0.04941 seconds

恭喜亚里士多德·帕加尔齐斯!您的实施速度非常快! 但是,要使用此解决方案,我显然希望对对象进行一次预处理(创建)。我可以存储(nstore)这个对象创建后?我以前从来没有这样做过。而我该怎么办retrieve它?有什么特别的吗?希望检索速度会很快,这样就不会影响这个伟大数据结构的整体性能。

UPDATE 3

我尝试了一个简单的nstore并检索RangeMap目的。这似乎工作正常。唯一的问题是生成的文件大约 1GB,我会有大约 1000 个这样的文件。我可以接受 1 TB 的存储空间,但我想知道是否有办法更有效地存储它,而不会对检索性能产生太大影响。另请参阅此处:http://www.perlmonks.org/?node_id=861961 http://www.perlmonks.org/?node_id=861961.

更新 4 -RangeMap bug

很遗憾,RangeMap有一个错误。感谢 PerlMonks 的 BrowserUK 指出了这一点。例如,创建一个对象$max_lenght=10和作为单一范围[6,2]。然后查询[7,8]。答案应该是1, not 0.

我认为这个更新的包应该可以完成工作:

use strict;
use warnings;

package FastRanges;

sub new($$$) {
    my $class      = shift;
    my $max_length = shift;
    my $ranges_a   = shift;
    my @lookup;
    for ( @{$ranges_a} ) {
        my ( $start, $end ) = @$_;
        my @idx
            = $end >= $start
            ? $start .. $end
            : ( $start .. $max_length, 1 .. $end );
        for my $i (@idx) { $lookup[$i] .= pack 'L', $end }
    }
    bless \@lookup, $class;
}

sub num_ranges_containing($$$) {
    my $self = shift;
    my ( $start, $end ) = @_;    # query range coordinates

    return 0
        unless ( defined $self->[$start] )
        ;    # no ranges overlap the start position of the query

    if ( $end >= $start ) {

        # query range is simple
        # any inverted range in {LOOKUP}[$start] must contain it,
        # and so does any simple range which ends at or after $end
        return 0 + grep { $_ < $start or $end <= $_ } unpack 'L*',
            $self->[$start];
    }
    else {

        # query range is inverted
        # only inverted ranges in {LOOKUP}[$start] which also end
        # at of after $end contain it. simple ranges can't contain
        # the query range
        return 0 + grep { $_ < $start and $end <= $_ } unpack 'L*',
            $self->[$start];
    }
}

1;

欢迎您提出意见。


你有很多可用内存吗?

my $max_length = 9999;
my @range = ( [12,80],[34,60],[34,9000] );

my @lookup;

for ( @range ) {
    my ( $start, $end ) = @$_;
    my @idx = $end >= $start ? $start .. $end : ( $start .. $max_length, 0 .. $end );
    for my $i ( @idx ) { $lookup[$i] .= pack "L", $end }
}

现在你有一个压缩数字列表的数组@lookup其中每个索引处的打包列表包含包含该点的所有范围的末尾。因此,要检查有多少范围包含另一个范围,您可以在数组中查找其起始索引,然后计算该索引处的打包列表中的条目数,这些条目小于或等于结束索引。该算法对于覆盖任意一点的最大范围数(限制为范围总数)而言为O(n),每次迭代的开销非常小。

sub num_ranges_containing {
    my ( $start, $end ) = @_;

    return 0 unless defined $lookup[$start];

    # simple ranges can be contained in inverted ranges,
    # but inverted ranges can only be contained in inverted ranges
    my $counter = ( $start <= $end )
        ? sub { 0 + grep { $_ < $start or  $end <= $_ } }
        : sub { 0 + grep { $_ < $start and $end <= $_ } };

    return $counter->( unpack 'L*', $lookup[$start] );
}

未经测试。

为了额外的整洁,

package RangeMap;

sub new {
    my $class = shift;
    my $max_length = shift;
    my @lookup;
    for ( @_ ) {
        my ( $start, $end ) = @$_;
        my @idx = $end >= $start ? $start .. $end : ( $start .. $max_length, 0 .. $end );
        for my $i ( @idx ) { $lookup[$i] .= pack 'L', $end }
    }
    bless \@lookup, $class;
}

sub num_ranges_containing {
    my $self = shift;
    my ( $start, $end ) = @_;

    return 0 unless defined $self->[$start];

    # simple ranges can be contained in inverted ranges,
    # but inverted ranges can only be contained in inverted ranges
    my $counter = ( $start <= $end )
        ? sub { 0 + grep { $_ < $start or  $end <= $_ } }
        : sub { 0 + grep { $_ < $start and $end <= $_ } };

    return $counter->( unpack 'L*', $self->[$start] );
}

package main;
my $rm = RangeMap->new( 9999, [12,80],[34,60],[34,9000] );

这样你就可以拥有任意数量的范围。

也未经测试。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何有效地计算 Perl 中覆盖给定范围的范围? 的相关文章

随机推荐

  • 电子应用程序的实时重新加载

    我想使用 VScode Gulp Electron 的组合来构建一个应用程序 开发工作流程的一个不错的功能是向我的 Gulp 监视任务添加实时重新加载任务 以便在每次更改时重新加载 Electron 应用程序 任何想法如何实现这一目标 非常
  • 改造如何打印响应 JSON

    我正在使用 Retrofit 并且想要访问从服务器返回的 JSON 响应 有人可以告诉我吗 谢谢 如果您只想查看出于调试目的的响应 只需在改造中打开调试并查看日志即可 它是这样的 restAdapter setDebuggingEnable
  • 样式媒体接收器源 (Chromecast)

    目前 我正在开发我的应用程序的 chromecast 集成 目前 您的投射接收器应用程序有 3 个选项 风格媒体接收器 默认媒体接收器 定制媒体接收器 我真的很喜欢媒体接收器的样式 因为以这种方式设计接收器的样式非常容易 然而 有时我真的很
  • 使用 shell 脚本在 docker 容器内运行脚本

    我正在尝试创建一个 shell 脚本来设置 docker 容器 我的脚本文件如下所示 bin bash docker run t i p 5902 5902 name mycontainer privileged myImage new b
  • 回车前清除行

    我想在一行中打印一个进度 所以我使用回车符 问题是我的进步不是增加 gt 这意味着第一个打印可能是Processing Foo Bar Baz下一个打印可能是Processing Foo 简单回车的问题是第二次打印将是重叠的通过第一张打印
  • 编译器优化对 malloc 的调用以返回更多弱对齐内存是否合法?

    假设我们有以下代码 include
  • 将 HTML 表格转换为 R 数据框

    table cellspacing 1 cellpadding 7 border 1 thead tr td align left valign middle nbsp td td align left 1a My peers make a
  • Codeigniter-如何在删除index.php后加载新页面

    我是 codeIgniter 的新手 我的索引文件中有一些链接 我已经从 url 中删除了 index php 所以现在 url 看起来像 http localhost app Loader demo page http localhost
  • 在 MySQL 中选择不同的对

    我想选择 A 列和 B 列中具有相同值的行 例如 如果我的表是 A B 1 2 3 4 1 2 4 5 输出应该是 A B 1 2 A SELECT DISTINCT A B FROM table 选择表中的所有值 B SELECT DIS
  • 使用 pandas to_datetime 时如何定义格式?

    我想根据以下内容绘制结果与时间的关系图testresult csv文件具有以下格式 并且我无法正确定义 TIME 列的数据类型 TIME RESULT 03 24 2016 12 27 11 AM 2 03 24 2016 12 28 41
  • Android Google Cast 通知禁用

    我将 Google Cast 集成到了我的 Android 应用程序中 我的所有内容都是通过带有通知的服务播放的 当我使用 Google Cast 播放媒体时 它会添加自己的通知 是否可以禁用默认的谷歌强制转换通知并仅使用自己的通知 谢谢
  • Firefox 和 IE 在 元素上添加了内边距/边距。和clearfix的奇怪之处

    在很长一段时间里 我在 Firefox 和 IE 中遇到了一些垂直间距问题 我正在使用一个 我的 css 中的选择器将边距应用于某个容器元素内的所有内容 在 Chrome 中工作正常 但是在 FF 和 IE 中 我似乎不知从何而来得到了神秘
  • 在数据库设计中什么时候需要使用一对一关系?

    在数据库设计中什么时候需要使用一对一关系 在我看来 如果两个表是一对一的关系 那么它们可以合并成一个表 这是真的 对大型表进行垂直分区以减少 I O 和缓存需求 将经常查询的列与很少查询的列分开 向生产系统添加列时alter table就是
  • PHP DOM 获取选定的

    假设 HTML 看起来像这样
  • 在 jQuery 中删除或更改 CSS 伪类

    一个足够简单的问题 如此简单 是否可以使用 jQuery 删除或更改 CSS 伪类 或者任何其他与此相关的 Javascript 方法 具体来说 我想摆脱 专注于输入 我无法以任何方式直接更改 CSS 文件 谢谢你的帮助 Buster 我无
  • 干净地销毁System V共享内存段

    我在用shmget shmat and shmctl分别获取和创建共享内存段 将其附加到进程地址空间中并删除它 我想知道进程是否仍然可以使用共享内存段 即使它已被分离并要求使用删除 shmctl id IPC RMID 在一个过程中 我无法
  • 如何从具体类转换为接口类型?

    我正在构建一个多层应用程序 当我将对象从表示层传递到业 务层时 我想将其转换为接口类型 因为业务层不知道表示层的具体类 public ActionResult SubmitSurvey SurveyAnswer answers ISurve
  • 通过 POST 将 JSON 编码的变量从 PHP 传递到 Javascript

    我有一个多维数组 我想将其发送到带有 Javascript 的 PHP 脚本 该脚本解析 JSON 数据并将其绘制在 Google 地图上 我正在尝试使用表单来模拟它
  • 使用 Rails/ActiveRecord 覆盖旧数据库中列的名称或别名

    我正在针对旧数据库编写 Rails 应用程序 此旧数据库中的一个表有一个名为object id 很遗憾object id也是 Ruby 中每个对象的属性 因此当 ActiveRecord 尝试使用这些对象来制定查询时 它使用 Ruby 定义
  • 如何有效地计算 Perl 中覆盖给定范围的范围?

    我有一个大约 30k 范围的数据库 每个范围都作为一对起点和终点给出 12 80 34 60 34 9000 76 743 我想编写一个 Perl 子例程来表示一个范围 不是来自数据库 并返回数据库中完全 包含 给定范围的范围数 例如 如果