循环遍历两个数组,删除perl中的重叠

时间:2011-06-30 16:13:07

标签: arrays algorithm perl merge intervals

我有两组范围,由[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;
    }
}

我可以自己打印出这些值,我只需要帮助合并算法。

5 个答案:

答案 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]形式的范围,并且您想要合并重叠范围。以下方法相当简单。

  1. 有两组范围, 原始集和合并集。
  2. 您将第一个范围添加到集合中 合并(非重叠)范围。 对于每个候选范围 原始套装,你做出选择:
    • 如果该候选项与已合并集中的候选项重叠,则可以适当地扩展合并集中范围的边界。
    • 如果候选范围与合并集中的任何范围之间没有重叠,则将候选范围添加到合并集中。
  3. 希望这是有道理的。从你的问题来看,这不是你想要的,这让我知道这是不对的。

    #!/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
}