【问题标题】:How can I parse this syntax to an array in Perl?如何将此语法解析为 Perl 中的数组?
【发布时间】:2010-09-21 19:43:36
【问题描述】:

我有一个包含使用此语法的参数的文件

RANGE {<value> | <value>-<value>} [ , ...]

value 是数字。

例如,所有这些都是有效的语法

RANGE 34
RANGE 45, 234
RANGE 2-99
RANGE 3-7, 15, 16, 2, 54

如何在 Perl 中将值解析为数组?

例如,对于最后一个示例,我希望我的数组具有3, 4, 5, 6, 7, 15, 16, 2, 54。元素的顺序无关紧要。


最基本的方法是检查-符号来判断是否有范围,使用循环解析范围,然后解析其余元素

my @arr;
my $fh, "<", "file.txt" or die (...);
while (<$fh>) {
    if ($_ =~ /RANGE/) {
        if ($_ =~ /-/) { # parse the range
            < how do I parse the lower and upper limits? >
            for($lower..$upper) {
                $arr[++$#arr] = $_;
            }
        } else { # parse the first value
            < how do I parse the first value? >
        }

        # parse the rest of the values after the comma
        < how do I parse the values after the comma? >
    }
}
  • 我需要帮助来解析数字。对于解析,我能想到的一种方法是使用连续拆分(-,)。有没有更好的(干净优雅,可能使用正则表达式?)方式?

  • 此外,欢迎对整体程序结构提出 cmets/建议。

【问题讨论】:

  • 打开输入文件进行写入可能是一个糟糕的第一步;)
  • 由于&lt;fh&gt;,您的代码也将无法运行,需要将其更改为&lt;$fh&gt;

标签: arrays perl


【解决方案1】:

看看来自 CPAN 的 Text::NumericList 模块。它可以以您需要的类似方式将字符串转换为数组:

use Text::NumericList;
my $list = Text::NumericList->new;

$list->set_string('1-3,5-7');
my @array = $list->get_array;     # Returns (1,2,3,5,6,7)

您至少可以查看它的源代码以获得想法。

【讨论】:

    【解决方案2】:

    我建议将该行解析为一个单独的变量,因为$_ 往往会被其他函数调用破坏。您可以使用chomp 同时删除尾随换行符。

    while (<$fh)>
    {
        chomp (my $line = $_);
        # ...
    }
    

    接下来,您需要检测“RANGE”指标,并提取后面的数字。如果没有这样的指标,你可以直接跳到下一行:

    next if $line !~ /^RANGE (.*)$/;
    

    现在,您可以开始提取数字,用逗号分隔:

    my @ranges = split /, /, $1;
    

    现在您可以提取破折号并将其转换为范围。这是棘手的部分——如果值中有破折号,则获取第一个和第二个数字,并使用.. 运算符将它们转换为一个范围;否则,不要管这个数字:

    @ranges = map { /(\d+)-(\d+)/ ? ($1 .. $2) : $_ } @ranges;
    

    将所有这些放在一起,并结合表达式,我们得出:

    my @numbers;
    while (<$fh)>
    {
        chomp (my $line = $_);
        next if $line !~ /^RANGE (.*)$/;
    
        push @numbers, map { /(\d+)-(\d+)/ ? ($1 .. $2) : $_ } (split /, /, $1);
    }
    

    【讨论】:

    • 用这个映射:{ /(\d+)-(\d+)/ ? ($1 .. $2) : $_ }
    【解决方案3】:

    这个怎么样?

    先将行分割成以值分隔的元素,然后检查是否有'-'符号来创建范围:

    if ($line =~ /RANGE ([\d\,\- ]+)/) {
        my $paramtxt = $1;
        my @elements = split(/\,/, $paramtxt);
        for my $element (@elements) {
            if ($element =~ /(\d+)\-(\d+)/) {
                $lower = $1;
                $upper = $2;
                push @arr, $lower .. $upper;
            } elsif ($element =~ /(\d+)/) {
                $solo = $1;
                push @arr, $solo;
            }
        } 
    }
    

    【讨论】:

      【解决方案4】:

      我喜欢使用 Perl 的范围和 || 运算符来解决这样的问题:

      map {  my($x,$y)=split/-/; $x..$y||$x } split /\s*,\s*/;
      

      如果令牌包含-split/-/ 语句将同时设置$x$y,并将$x$y 的范围添加到map 输出。否则,它只会设置$x 并将$x 添加到输出中。

      【讨论】:

        【解决方案5】:

        使用哈希过滤重复项:

        #! /usr/bin/perl
        
        use warnings;
        use strict;
        
        use 5.10.0;
        
        my @tests = (
          "RANGE 34",
          "RANGE 45, 234",
          "RANGE 2-99",
          "RANGE 3-7, 15, 16, 2, 54",
        );
        
        for (@tests) {
          my %hits;
          @hits{$1 .. $2 // $1} = ()
            while /(\d+)(?:-(\d+))?/g;
        
          my @array = sort { $a <=> $b } keys %hits;
          print "@array\n";
        }
        

        输出:

        34
        45 234
        2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 45 44 44 45 56 4 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 991 92 92 93
        2 3 4 5 6 7 15 16 54

        【讨论】:

          【解决方案6】:

          与其他答案相同:

          #!/usr/bin/perl
          
          use strict; use warnings;
          
          my $number = '[0-9]+';
          my $range  = "$number(:?-$number)?";
          my $ranges = "$range(:?, $range)*";
          my $pattern = qr/^RANGE ($ranges)$/;
          
          
          while ( my $range = <DATA> ) {
              next unless $range =~ $pattern;
              my $expanded = expand_ranges($1);
              print "@$expanded\n\n";
          }
          
          sub expand_ranges {
              my ($ranges) = @_;
              my @terms = split /, /, $ranges;
              my @expanded;
          
              for my $term ( @terms ) {
                  my ($lo, $hi) = split /-/, $term;
                  push @expanded, defined( $hi ) ? $lo .. $hi : $lo .. $lo;
              }
          
              return \@expanded;
          }
          
          
          __DATA__
          RANGE 34
          RANGE 45, 234
          RANGE 2-99
          RANGE 3-7, 15, 16, 2, 54
          

          输出:

          34
          
          45 234
          
          2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 3
          1 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
          58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
           85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
          
          3 4 5 6 7 15 16 2 54

          【讨论】:

            【解决方案7】:

            这是我的努力:

            sub parse_range {
                my $str = shift;
                return unless $str =~ /^RANGE /g;
            
                my @array;
                while ($str =~ / \G \s* ( \d+ ) ( - ( \d+ ) ) ? \s* (?: , | $ ) /gxc) {
                    push @array, $2 ? $1 .. $3 : $1;
                }
            
                return $str =~ /\G$/ ? @array : ();
            
            }
            

            如果字符串参数不符合您布置的基本格式,则返回一个空列表。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2023-03-08
              • 1970-01-01
              • 2017-03-25
              • 1970-01-01
              • 2018-07-15
              • 1970-01-01
              相关资源
              最近更新 更多