Perl脚本|使用HASH

时间:2015-08-03 03:48:48

标签: perl hash

我有一个输入文件,如下所示

 =IP1
abc[0]
abc[1]
abc[2]
=IP2
def[4]
def[8]
def[9]

我需要以下面的格式获得输出 -

=IP1
abc[0-2]
=IP2
def[4,8-9]

我一直在尝试使用哈希来实现上述操作,其中我读取文件的每一行然后拆分(使用' [')每一行,我将第一部分保留为键并再次读取文件将值保存在散列键的数组中。但我陷入了困境。任何人都可以提供如何实现上述的帮助?

2 个答案:

答案 0 :(得分:1)

有几个有趣的子问题。首先,您要跟踪最新的标题(即=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可能如下所示:

sub num2range{ return Set::IntSpan->new(@_) }

Set::IntSpan对象重载了字符串化,因此打印它会给出范围的文本表示。如果你选择这条路线,你可以删除对数字列表进行排序的代码 - 这是由Set :: IntSpan内部处理的。

答案 1 :(得分:1)

好的,这是我对解决方案的看法。没有关于传入数据的任何更好的信息,它可能比必要的更复杂

它保留数据 - =IP标题和xyz[9]值的顺序与首次遇到的顺序相同。我已经将数字范围收缩的生成分离到子例程ranges

只需将文件中的数据(它希望作为命令行上的参数)读入数据结构%data@order并打印它们即可。再来一次。哈希的@order数组和_order子键用于保存遇到值的序列,并在将新键插入相应的哈希值时添加到该序列中

use strict;
use warnings;

my ($key, %data, @order);

while ( <> ) {

  chomp;

  if ( /^=/ ) {
    $key = $_;
    push @order, $key unless $data{$key};
    $data{$key} = { _order => [] };
  }
  elsif ( my ($key2, $n) = /([^\[\]\s]+)\[(\d+)\]/ ) {
    my $data = $data{$key};
    push @{ $data->{_order} }, $key2 unless $data->{$key2};
    push @{ $data->{$key2} }, $n; 
  }
}

for my $key ( @order ) {

    print $key, "\n";

    my $data = $data{$key};

    for my $key2 ( @{ $data->{_order} } ) {
        printf "%s[%s]\n", $key2, ranges( sort { $a <=> $b } @{ $data->{$key2} } );
    }

}

sub ranges {

    my @ranges;
    my ($start, $end);

    for my $n ( @_ ) {
        if ( not defined $start ) {
            $start = $end = $n;
        }
        elsif ( $n == $end + 1 ) {
            $end = $n;
        }
        else {
            push @ranges, $start == $end ? $start : "$start-$end";
            $start = $end = $n;
        }
    }

    push @ranges, $start == $end ? $start : "$start-$end" if defined $start;
    join ',', @ranges;
}

输出

=IP1
abc[0-2]
=IP2
def[4,8-9]