【问题标题】:Cast a string into a hash or array in perl在 perl 中将字符串转换为哈希或数组
【发布时间】:2015-07-10 03:15:33
【问题描述】:

我目前正在将逗号分隔的 2 元组字符串解析为标量哈希。例如,给定输入:

"ip=192.168.100.1,port=80,file=howdy.php",

我最终得到一个看起来像这样的哈希:

%hash =
{
    ip => 192.168.100.1,
    port => 80,
    file => howdy.php
 }

代码运行良好,看起来像这样:

my $paramList = $1;
my @paramTuples = split(/,/, $paramList);
my %hash;
foreach my $paramTuple (@paramTuples) {
    my($key, $val) = split(/=/, $paramTuple, 2);
    $hash{$key} = $val;
}

我想将功能从仅采用标量扩展到还采用数组和散列。因此,另一个示例输入可能是:

"ips=(192.168.100.1,192.168.100.2),port=80,file=howdy.php,hashthing={key1 => val1, key2 => val2}",

我最终得到一个看起来像这样的哈希:

%hash =
{
    ips => (192.168.100.1, 192.168.100.2), # <--- this is an array
    port => 80,
    file => howdy.php,
    hashthing => { key1 => val1, key2 => val2 } # <--- this is a hash
 }

我知道我可以逐个字符地解析输入字符串。对于每个元组,我将执行以下操作:如果第一个字符是 (,则解析一个数组。否则,如果第一个字符是 {,则解析散列。否则解析一个标量。

我的一位同事表示,他认为您可以将看起来像 "(red,yellow,blue)" 的字符串转换为数组或将 "{c1 =&gt; red, c2 =&gt; yellow, c3 =&gt; blue}" 转换为带有某种类型转换函数的哈希。如果我走这条路,我可以使用不同的分隔符而不是逗号来分隔我的 2 元组,例如 |

这在 perl 中可行吗?

【问题讨论】:

    标签: arrays perl hash


    【解决方案1】:

    我认为您所指的“演员”功能可能是eval

    使用eval

    use strict;
    use warnings;
    use Data::Dumper;
    
    my $string = "{ a => 1, b => 2, c => 3}";
    my $thing =  eval $string;
    print "thing is a ", ref($thing),"\n";
    print Dumper $thing;
    

    将打印:

    thing is a HASH
    $VAR1 = {
                'a' => 1,
                'b' => 2,
                'c' => 3
              };
    

    或者对于数组:

    my $another_string = "[1, 2, 3 ]";
    my  $another_thing = eval $another_string;
    print "another_thing is ", ref ( $another_thing ), "\n";
    print Dumper $another_thing;
    
    another_thing is ARRAY
    $VAR1 = [
                1,
                2,
                3
              ];
    

    尽管请注意eval 要求您使用适合适当数据类型的括号 - {} 用于匿名哈希,[] 用于匿名数组。所以以你上面的例子为例:

    my %hash4;
    my $ip_string = "ips=[192.168.100.1,192.168.100.2]";
    my ( $key, $value ) = split ( /=/, $ip_string );
    $hash4{$key} = eval $value; 
    
    my $hashthing_string = "{ key1 => 'val1', key2 => 'val2' }"; 
    $hash4{'hashthing'} = eval $hashthing_string;
    print Dumper \%hash4;
    

    给予:

    $VAR1 = {
          'hashthing' => {
                           'key2' => 'val2',
                           'key1' => 'val1'
                         },
          'ips' => [
                     192.168.100.1,
                     192.168.100.2
                   ]
        };
    

    使用map将数组变成哈希

    如果你想把一个数组变成一个散列,map 函数就是为此而生的。

    my @array = ( "red", "yellow", "blue" );
    my %hash = map { $_ => 1 } @array; 
    print Dumper \%hash;
    

    使用slices 的哈希

    如果您有已知值和已知键,也可以使用slice

    my @keys = ( "c1", "c2", "c3" );
    my %hash2;
    @hash2{@keys} = @array;
    print Dumper \%hash2;
    

    JSON/XML

    或者,如果您可以控制导出机制,您可能会发现导出为JSONXML 格式是一个不错的选择,因为它们是“数据即文本”的明确标准。 (如果您只是在 Perl 进程之间移动数据,您也许也可以使用 Perl 的 Storable

    再次,取上面的%hash4(稍作修改,因为我必须引用IP):

    use JSON; 
    print encode_json(\%hash4);
    

    给我们:

    {"hashthing":{"key2":"val2","key1":"val1"},"ips":["192.168.100.1","192.168.100.2"]}
    

    你也可以漂亮地打印:

    use JSON; 
    print to_json(\%hash4, { pretty => 1} );
    

    获取:

    {
       "hashthing" : {
          "key2" : "val2",
          "key1" : "val1"
       },
       "ips" : [
          "192.168.100.1",
          "192.168.100.2"
       ]
    }
    

    这可以通过简单的方式读回:

    my $data_structure = decode_json ( $input_text ); 
    

    样式点

    作为一种风格 - 我可以建议您格式化数据结构的方式并不理想。如果您使用Dumper“打印”它们,那么这是大多数人都会认可的常见格式。所以你的“第一个哈希”看起来像:

    声明为(不是我的前缀,() 用于声明,以及strict 下所需的引号):

    my %hash3 = (
        "ip" => "192.168.100.1",
        "port" => 80,
        "file" => "howdy.php"
    );
    

    转储为({} 的括号,因为它是匿名哈希,但仍引用字符串):

    $VAR1 = {
              'file' => 'howdy.php',
              'ip' => '192.168.100.1',
              'port' => 80
            };
    

    这样一来,当人们能够重构和解释您的代码时,您就会更加高兴。

    还要注意 - dumper 样式格式也适合(在特定的有限情况下)通过eval 重新阅读。

    【讨论】:

      【解决方案2】:

      试试这个,但复合值必须单独解析。

      my $qr_key_1 = qr{
        (         # begin capture
          [^=]+   # equal sign is separator. NB: spaces captured too.
        )         # end capture
      }msx;
      
      my $qr_value_simple_1 = qr{
        (         # begin capture
          [^,]+   # comma is separator. NB: spaces captured too.
        )         # end capture
      }msx;
      
      my $qr_value_parenthesis_1 = qr{
        \(        # starts with parenthesis
        (         # begin capture
          [^)]+   # end with parenthesis NB: spaces captured too.
        )         # end capture
        \)        # end with parenthesis
      }msx;
      
      my $qr_value_brace_1 = qr{
        \{        # starts with brace
        (         # begin capture
          [^\}]+  # end with brace NB: spaces captured too.
        )         # end capture
        \}        # end with brace
      }msx;
      
      my $qr_value_3 = qr{
        (?:       # group alternative
          $qr_value_parenthesis_1
        |         # or other value
          $qr_value_brace_1
        |         # or other value
          $qr_value_simple_1
        )         # end group
      }msx;
      
      my $qr_end = qr{
        (?:       # begin group
          \,      # ends in comma
        |         # or
          \z      # end of string
        )         # end group
      }msx;
      
      my $qr_all_4 = qr{
        $qr_key_1     # capture a key
        \=            # separates key from value(s)
        $qr_value_3   # capture a value
        $qr_end       # end of key-value pair
      }msx;
      
      
      
      while( my $line = <DATA> ){
        print "\n\n$line";  # for demonstration; remove in real script
        chomp $line;
      
        while( $line =~ m{ \G $qr_all_4 }cgmsx ){
          my $key = $1;
          my $value = $2 || $3 || $4;
      
          print "$key = $value\n";  # for demonstration; remove in real script
        }
      }
      
      __DATA__
      ip=192.168.100.1,port=80,file=howdy.php
      ips=(192.168.100.1,192.168.100.2),port=80,file=howdy.php,hashthing={key1 => val1, key2 => val2}
      

      附录:

      解析扩展如此困难的原因,一句话,就是上下文。第一行数据ip=192.168.100.1,port=80,file=howdy.php 是上下文无关的。也就是说,其中的所有符号都不会改变它们的含义。可以单独使用正则表达式解析上下文无关的数据格式。

      规则#1: 如果表示数据结构的符号永远不会改变,则它是一种上下文无关的格式,正则表达式可以解析它。

      第二行,ips=(192.168.100.1,192.168.100.2),port=80,file=howdy.php,hashthing={key1 =&gt; val1, key2 =&gt; val2} 是一个不同的问题。逗号和等号的含义发生了变化。

      现在,您认为逗号不会改变;它仍然将事物分开,不是吗?但它改变了它分离的东西。这就是为什么第二行更难解析的原因。第二行有三个上下文,在树中:

      main context
      +--- list context
      +--- hash context
      

      tokienizer 必须在数据切换上下文时切换解析集。这需要一个状态机。

      规则#2: 如果数据格式的上下文形成一棵树,那么每个上下文需要一个状态机和不同的解析器。状态机确定正在使用的解析器。由于除根之外的每个上下文都只有一个父级,因此状态机可以在其当前上下文结束时切换回父级。

      为了完整起见,这是最后一条规则。在这个问题中没有用到。

      规则#3: 如果上下文形成DAG (directed acyclic graph) 或递归(又名循环)图,则状态机需要一个堆栈,以便知道要切换回哪个上下文当它到达当前上下文的末尾时。

      现在,您可能已经注意到上述代码中没有状态机。它在那里,但它隐藏在正则表达式中。但是隐藏它是有代价的:列表和哈希上下文不被解析。仅找到它们的字符串。它们必须单独解析。

      说明:

      以上代码使用qr// operator 来创建解析正则表达式。 qr// 运算符编译正则表达式并返回对它的引用。此引用可用于匹配、替换或其他 qr// 表达式。将每个qr// 表达式视为一个子例程。就像普通子程序一样,qr// 表达式可以用于其他 qr// 表达式,从简单的正则表达式构建复杂的正则表达式。

      第一个表达式$qr_key_1 捕获主上下文中的键名。由于等号将键与值分开,因此它会捕获所有非等号字符。我用变量名末尾的“_1”来提醒自己存在一个捕获组。

      Perl Best Practices 中建议使用表达式末尾的选项/m/s/x,但只有/x 选项有效。它允许在正则表达式中使用空格和 cmets。

      下一个表达式 $qr_value_simple_1 捕获键的简单值。

      下一个 $qr_value_parenthesis_1 处理列表上下文。这是可能的,因为右括号只有一个含义:列表上下文的结尾。但也有代价:列表不被解析;只找到它的字符串。

      对于$qr_value_brace_1,右括号只有一个含义。并且哈希也没有被解析。

      $qr_value_3 表达式将值 RE 合并为一个。 $qr_value_simple_1 必须在最后,但其他的可以按任意顺序排列。

      $qr_end 解析主上下文中字段的结尾。它的末尾没有数字,因为它不捕获任何东西。

      最后,$qr_all_4 将它们放在一起,为数据创建 RE。

      内部循环中使用的 RE,m{ \G $qr_all_4 }cgmsx,解析出主上下文中的每个字段。 \G 断言意味着:如果自上次调用以来已更改(或从未调用过),则从字符串开头开始匹配;否则,从最后一场比赛结束的地方开始。这与 /c/g``options to parse each field out from the$line` 结合使用,一次一个用于在循环内处理。

      这就是代码内部发生的简要情况。 ☺

      【讨论】:

      • 我认为这可能会受益于对正在发生的事情的更多解释。
      • 我可以写一本书来解释发生了什么。 OP不小心遇到了一个复杂的问题,这就是他遇到这些困难的原因。但我将添加镍巡回赛版本。 :)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-01-06
      • 2014-08-22
      • 1970-01-01
      • 2020-01-02
      • 2019-03-07
      • 2011-02-23
      • 2013-07-06
      相关资源
      最近更新 更多