有几个有趣的子问题。首先,您要跟踪最新的标头(即=IP1)。其次,您想要跟踪与某些键关联的数字列表,第三,您想要生成范围字符串。
我会这样做:
#!/usr/bin/env perl
use strict;
use warnings;
my $tl;
my %h;
# First process the lines of the input file.
while(<DATA>) {
chomp;
next unless length;
if(/^(=\w{2}\d+)$/) { # Recognize and track a top level heading.
$tl = $1;
next;
}
if(/^(\w+)\[(\d+)\]$/) { # Or grab a key/value pair.
my($k,$v) = ($1,$2);
push @{$h{$tl}{$k}}, $v; # push the value into the right bucket.
next;
}
warn "Unrecognized format cannot be processed at $.: (($_))\n";
}
# Sort the top level headers alphabetically and numerically.
# Uses a Schwartzian Transform so that we don't need to recompute
# sort keys repeatedly.
my @topkeys = map {$_->[0]}
sort {$a->[1] cmp $b->[1] || $a->[2] <=> $b->[2]}
map {
my($alpha, $num) = $_ =~ m/^=(\w+)(\d+)$/;
[$_, $alpha, $num]
} keys %h;
# Now iterate through the structure in sorted order, generate range
# strings on the fly, and print our output.
foreach my $top (@topkeys) {
print "$top\n";
foreach my $k (sort keys %{$h{$top}}) {
my @vl = sort {$a <=> $b} @{$h{$top}{$k}};
my $range = num2range(@vl);
print "$k\[$range]\n";
}
}
sub num2range {
local $_ = join ',' => @_;
s/(?<!\d)(\d+)(?:,((??{$++1}))(?!\d))+/$1-$+/g;
return $_;
}
__DATA__
=IP1
abc[0]
abc[1]
abc[2]
=IP2
def[4]
def[8]
def[9]
产生以下输出:
=IP1
abc[0-2]
=IP2
def[4,8-9]
如果回答了Borodin 作为对您原始帖子的评论提出的一些问题的答案,则可以进一步优化此解决方案。例如,如果我们知道数字已经按顺序排列,那么在生成范围之前就没有必要对我们的数字列表进行排序。如果我们更多地了解“abc”和“def”是什么,则可能会消除一些复杂性(和计算工作)。如果排序顺序无关紧要,我们可以进一步简化,同时减少完成的工作量。
此外,Set::IntSpan 模块可能会提供一种更强大的方法来生成范围字符串,如果该脚本旨在超越“一次性”的生命周期,则可能值得考虑。如果您选择使用 Set::IntSpan,您的 num2range 子可能如下所示:
sub num2range{ return Set::IntSpan->new(@_) }
Set::IntSpan 对象已重载字符串化,因此打印它会给出范围的文本表示。如果你走这条路,你可以消除对数字列表进行排序的代码——由 Set::IntSpan 内部处理。