Arrays Perl-从数组中提取带偏移量的数字序列

Arrays Perl-从数组中提取带偏移量的数字序列,arrays,perl,extract,sequence,Arrays,Perl,Extract,Sequence,我试图在整数数组中搜索一系列数字。例如,如果数组由数字1,2,3,10,12,14组成,则可以将其汇总为 1到3,偏移量为1 10到14,偏移量为2 在我的代码下面,我从第二个元素循环数组,跟踪连续数组元素之间的偏移量,如果偏移量发生变化,则创建一个新的“系列”: use strict; use warnings; my @numbers = (1,2,3,10,12,14); #array to extract series from my $last_diff; my $start =

我试图在整数数组中搜索一系列数字。例如,如果数组由数字
1,2,3,10,12,14
组成,则可以将其汇总为

1到3,偏移量为1

10到14,偏移量为2

在我的代码下面,我从第二个元素循环数组,跟踪连续数组元素之间的偏移量,如果偏移量发生变化,则创建一个新的“系列”:

use strict;
use warnings;

my @numbers = (1,2,3,10,12,14); #array to extract series from 
my $last_diff;
my $start = $numbers[0];
my $end;
my @all_series; #array will hold all information on series
for my $i (1..($#numbers+1)){
        my $diff;
        if ($i <($#numbers+1)){
                $diff = $numbers[$i] - $numbers[$i-1];
        }
        if (!$diff || ( $last_diff && ($last_diff != $diff)) ) {
                $end = $numbers[$i-1];
                my $series = { 'start'=> $start,
                            'end'  => $end,
                            'offset'=> $start == $end ? 1 : $last_diff,
                };
                push @all_series, $series;
                $start = $numbers[$i];
        }
        $last_diff = $diff;
}

use Data::Dumper;
print Dumper(@all_series);
这不是期望的结果,因为最后两个系列可以总结为一个(10到14,偏移量2而不是两个系列)

该算法中的缺陷与perl无关,但是,也许有人可以给我一个关于如何最好地实现这一点的提示,也许存在一些perl特有的技巧

在我的应用程序中,数组中的所有整数都是升序的,不存在重复的数字

编辑 如果出现无法分配给严重错误的单个数字,则它们应该是长度为1的序列


可以总结成系列的数字越多越好(我想尽量减少系列的数量!)

问题在于三元运算符。如果你用普通的

offset => $last_diff,
你会注意到有

$VAR2 = {
          'offset' => 7,
          'end' => 10,
          'start' => 10
这在某种程度上是正确的。要避免这种情况,您可以在按下@series后
undef$diff
。它将为您的案例生成预期的输出,但仍然将
12377101214
视为三个序列,从1、7和12开始。现在,你需要的是说一个更长的句子

我尝试了以下内容,但您应该进行更多测试:

#!/usr/bin/perl
use warnings;
use strict;

use Data::Dumper;

my @numbers = (1, 2, 3, 10, 12, 14);
my $last_diff;
my $start = $numbers[0];
my @all_series;
for my $i (1 .. $#numbers + 1) {
    my $diff;
    if ($i < $#numbers + 1) {
        $diff = $numbers[$i] - $numbers[ $i - 1 ];
    }

    # Merge with the last number from the previous series if needed:
    if (!$last_diff # Just starting a new series.
        and $i > 2  # Far enough to have preceding numbers.
        and $diff and $diff == $numbers[ $i - 1 ] - $numbers[ $i - 2 ]
       ) {
        $all_series[-1]{end} = $numbers[ $i - 3 ];
        $all_series[-1]{offset} = 0 if $all_series[-1]{start} == $all_series[-1]{end};
        $start = $numbers[ $i - 2 ];
    }

    if (! $diff or ( $last_diff && ($last_diff != $diff)) ) {
        push @all_series, { start  => $start,
                            end    => $numbers[ $i - 1 ],
                            offset => $last_diff,
                          };
        $start = $numbers[$i];
        undef $diff;
    }
    $last_diff = $diff;
}

print Dumper(@all_series);
#/usr/bin/perl
使用警告;
严格使用;
使用数据::转储程序;
我的@numbers=(1,2,3,10,12,14);
我的$last_diff;
我的$start=$numbers[0];
我的@all_系列;
对于我的$i(1..$#数字+1){
我的$diff;
如果($i<$#数字+1){
$diff=$numbers[$i]-$numbers[$i-1];
}
#如果需要,与上一系列中的最后一个数字合并:
如果(!$last_diff)#刚刚开始一个新系列。
而且$i>2,足够有前面的数字了。
和$diff和$diff==$numbers[$i-1]-$numbers[$i-2]
) {
$all_系列[-1]{end}=$numbers[$i-3];
如果$all_系列[-1]{start}==$all_系列[-1]{end},则$all_系列[-1]{offset}=0;
$start=$number[$i-2];
}
如果(!$diff或($last_diff&($last_diff!=$diff))){
推送@all_系列,{start=>$start,
结束=>$numbers[$i-1],
偏移量=>$last_diff,
};
$start=$number[$i];
未定义$diff;
}
$last_diff=$diff;
}
打印转储程序(@all_系列);

如果通过三个单独的步骤完成,则最容易解决此问题

  • 确定每个数字之间的差异
  • 确定差异的顺序
  • 最后确定范围
这些步骤中的每一个都是为了更容易地调试每个步骤是否正确。此外,对于某些值,如
1,7,8,9
,有必要查看前面的三个数字,以确定
7
是否应与
1
拥抱在一起。因此,提前计算所有信息有助于更容易地确定和指定最终循环中构建范围所需的规则

为了使输出更易于阅读,我将单个数字显示为一个
start
值。此外,我在范围散列中添加了一个
count
。这些变化以后很容易调整

对于额外的测试数据,我添加了一个序列,后面是一个单独的1,后面是一个由3个数字组成的序列,我还添加了一个斐波那契序列作为挑战

use strict;
use warnings;

use Data::Dump;

while (<DATA>) {
    chomp;
    my @nums = split ',';

    my @diffs = map {$nums[$_+1] - $nums[$_]} (0..$#nums-1);

    my @seq;
    for (@diffs) {
        if (@seq && $seq[-1]{diff} == $_) {
            $seq[-1]{count}++;
        } else {
            push @seq, {diff => $_, count => 1};
        }
    }

    my @ranges;
    for (my $i = 0; $i < @nums; $i++) {
        my $seq = shift @seq;

        # Solitary Number
        if (!$seq || ($seq->{count} == 1 && @seq && $seq[0]{count} > 1)) {
            push @ranges, {start => $nums[$i]};

        # Confirmed Range
        } else {
            push @ranges, {
                start  => $nums[$i],
                end    => $nums[$i + $seq->{count}],
                count  => $seq->{count} + 1,    # Can be commented out
                offset => $seq->{diff},
            };
            $i += $seq->{count};
            shift @seq if @seq && !--$seq[0]{count};
        }
    }

    dd @nums;
    dd @ranges;
    print "\n";
}

__DATA__
1,2,3,10,12,14
1,2,3,5,7
1,7,8,9
1,2,3,7,8,11,13,15,22,100,150,200
2,3,5,8,13,21,34,55,89

你的规格还不清楚。采取
12357
:3应该去哪里?另外,对于
1230101202122
,您是想要一个系列
1012
,还是让它们成为两个单件系列?我没有想到这一点。对于第一种情况:在我的应用程序中,三个是第一个序列还是第二个序列的一部分并不重要。对于后一种情况:
1012
应该是一个系列。有效的解决方案。它适用于所有的测试用例,我可以想出。您的解决方案与我的解决方案稍有不同,在本例中,它拥抱右侧(3与5),而不是左侧(3与2):
1,2,3,5,7
。另外,如果将undef调用更改为
$diff=0,则尾随值将不具有undef偏移量。例:
1,2,3,9
两个答案都与我的数据非常吻合,非常感谢!我会接受这个,因为它来得更早。。。
use strict;
use warnings;

use Data::Dump;

while (<DATA>) {
    chomp;
    my @nums = split ',';

    my @diffs = map {$nums[$_+1] - $nums[$_]} (0..$#nums-1);

    my @seq;
    for (@diffs) {
        if (@seq && $seq[-1]{diff} == $_) {
            $seq[-1]{count}++;
        } else {
            push @seq, {diff => $_, count => 1};
        }
    }

    my @ranges;
    for (my $i = 0; $i < @nums; $i++) {
        my $seq = shift @seq;

        # Solitary Number
        if (!$seq || ($seq->{count} == 1 && @seq && $seq[0]{count} > 1)) {
            push @ranges, {start => $nums[$i]};

        # Confirmed Range
        } else {
            push @ranges, {
                start  => $nums[$i],
                end    => $nums[$i + $seq->{count}],
                count  => $seq->{count} + 1,    # Can be commented out
                offset => $seq->{diff},
            };
            $i += $seq->{count};
            shift @seq if @seq && !--$seq[0]{count};
        }
    }

    dd @nums;
    dd @ranges;
    print "\n";
}

__DATA__
1,2,3,10,12,14
1,2,3,5,7
1,7,8,9
1,2,3,7,8,11,13,15,22,100,150,200
2,3,5,8,13,21,34,55,89
(1, 2, 3, 10, 12, 14)
(
  { count => 3, end => 3, offset => 1, start => 1 },
  { count => 3, end => 14, offset => 2, start => 10 },
)

(1, 2, 3, 5, 7)
(
  { count => 3, end => 3, offset => 1, start => 1 },
  { count => 2, end => 7, offset => 2, start => 5 },
)

(1, 7, 8, 9)
(
  { start => 1 },
  { count => 3, end => 9, offset => 1, start => 7 },
)

(1, 2, 3, 7, 8, 11, 13, 15, 22, 100, 150, 200)
(
  { count => 3, end => 3, offset => 1, start => 1 },
  { count => 2, end => 8, offset => 1, start => 7 },
  { count => 3, end => 15, offset => 2, start => 11 },
  { start => 22 },
  { count => 3, end => 200, offset => 50, start => 100 },
)

(2, 3, 5, 8, 13, 21, 34, 55, 89)
(
  { count => 2, end => 3, offset => 1, start => 2 },
  { count => 2, end => 8, offset => 3, start => 5 },
  { count => 2, end => 21, offset => 8, start => 13 },
  { count => 2, end => 55, offset => 21, start => 34 },
  { start => 89 },
)