我有两组范围,由[start,stop]值表示。一些范围重叠,这意味着一个范围的开始位于另一个范围的[开始,停止]之间。我想制作一组没有这种重叠的新范围,并且不包括范围内的任何新值。
范围看起来像这样:
@starts @ends
5 108
5 187
44 187
44 229
44 236
64 236
104 236
580 644
632 770
我期望的输出是:
@starts @ends
5 236
580 770
这是因为前七个范围与5 =>的区间重叠。 236和最后两个重叠,间隔从632 => 770.
这是我尝试过的代码:
$fix = 0;
foreach (@ends) {
if ($starts[$fix + 1] < $ends[$fix]) {
splice(@ends, $fix, $fix);
splice(@starts, $fix + 1, $fix + 1);
} else {
$fix += 1;
}
}
我可以自己打印出这些值,我只需要帮助合并算法。
答案 0 :(得分:3)
这会就地编辑你的数组,只是在重叠时折叠边界。
# Since they're sorted by @starts, accept the 0th interval, start at 1
for (1..$#starts) {
# extra check on array bounds, since we edit in-place
last unless $_ < @starts;
# don't need to collapse if no overlap with previous end
next unless $starts[$_] <= $ends[$_-1];
# delete this start and the previous end
splice(@starts,$_,1);
splice(@ends,$_-1,1);
# rerun this loop for the same value of $_ since it was deleted
redo;
}
答案 1 :(得分:1)
我认为这就是你想要的。您有一系列[start,stop]形式的范围,并且您想要合并重叠范围。以下方法相当简单。
希望这是有道理的。从你的问题来看,这不是你想要的,这让我知道这是不对的。
#!/usr/bin/perl
use strict;
use warnings;
my @starts = qw/ 5 5 44 44 44 64 104 580 632 /;
my @ends = qw/ 108 187 187 229 236 236 236 644 770 /;
my @ranges;
while ( @starts && @ends ) {
my $s = shift @starts;
my $e = shift @ends;
push @ranges, [ $s, $e ];
}
my @merged_ranges;
push @merged_ranges, shift @ranges;
foreach my $range (@ranges) {
my $overlap = 0;
foreach my $m_range (@merged_ranges) {
if ( ranges_overlap($range,$m_range) ) {
$overlap = 1;
$m_range = merge_range($range,$m_range);
}
}
if ( !$overlap ) {
push @merged_ranges, $range;
}
}
print join ' ', qw/ start end /;
print "\n";
foreach my $range (@merged_ranges) {
print join ' ', ( $range->[0], $range->[1] );
print "\n";
}
sub ranges_overlap {
my $r1 = shift;
my $r2 = shift;
return ( $r1->[0] <= $r2->[1] && $r2->[0] <= $r1->[1] );
}
sub merge_range {
my $r1 = shift;
my $r2 = shift;
use List::Util qw/ min max/;
my $merged = [ min($r1->[0],$r2->[0]), max($r1->[1],$r2->[1]) ];
return $merged;
}
答案 2 :(得分:1)
由于数组是按开始排序的,因此最简单的方法是从最后开始工作:
# this assumes at least one element in @starts, @ends
my $n = $#starts;
for (my $i = $#starts - 1; $i >= 0; $i--) {
if ($ends[$i] < $starts[$n]) {
# new interval
$n--;
($starts[$n], $ends[$n]) = ($starts[$i], $ends[$i]);
} else {
# merge intervals - first scan for how far back to go
while ($n < $#starts && $ends[$i] < $starts[$n+1]) {
$n++;
}
$starts[$n] = $starts[$i];
}
}
@starts = @starts[$n..$#starts];
@ends = @ends[$n..$#ends];
答案 3 :(得分:0)
这是怎么回事?
#!perl
use strict;
use warnings;
my @starts = qw(5 5 44 44 44 64 104 580 632);
my @ends = qw(108 187 187 229 236 236 236 644 770);
my @starts_new;
my @ends_new;
if ((scalar @starts) ne (scalar @ends)) {
die "Arrays are not of equal length!\n";
}
my %ranges;
my $next_i = 0;
for (my $i=0; $i <= $#starts; $i=$next_i) {
# If nothing changes below, the next array item we'll visit is the next sequential one.
$next_i = $i + 1;
# Init some temp stuff.
my $start = $starts[$i]; # this one shouldn't change during this "for $i" loop
my $end = $ends[$i];
for (my $j=$i+1; $j <= $#ends; $j++) {
if ($starts[$j] <= $end) {
# This item further down the @starts array is actually less than
# (or equal to) the current $end.
# So, we need to "skip" this item in @starts and update
# $end to reflect the corresponding entry in @ends.
$next_i = $j +1;
$end = $ends[$j] if ($ends[$j] > $end);
}
}
# We have a valid start/end pair.
push (@starts_new, $start);
push (@ends_new, $end);
}
for (my $i=0; $i <= $#starts_new; $i++) {
print "$starts_new[$i], $ends_new[$i]\n";
}
答案 4 :(得分:0)
我不熟悉PERL,但以下伪代码解决方案可能很容易适应:
for(i=0; i<N;){
//we know that the next merged interval starts here:
start = starts[i]
end = ends[i]
for(i=i+1; i < N && starts[i] < end; i++){ //perhaps you want <= ?
end = maximum(end, ends[i]);
}
add (start, end) to merged array
}