【问题标题】:How can I convert these strings to a hash in Perl?如何在 Perl 中将这些字符串转换为哈希?
【发布时间】:2010-09-21 11:05:02
【问题描述】:

我希望将具有多个分隔符的单个字符串转换为 key=>value 哈希结构。有没有一种简单的方法可以做到这一点?我目前的实现是:

sub readConfigFile() {
    my %CONFIG;
    my $index = 0;
    open(CON_FILE, "config");
    my @lines = <CON_FILE>;
    close(CON_FILE);

    my @array = split(/>/, $lines[0]);
    my $total = @array;

    while($index < $total) {
        my @arr = split(/=/, $array[$index]); 
        chomp($arr[1]);
        $CONFIG{$arr[0]} = $arr[1];       
        $index = $index + 1; 
    }

    while ( ($k,$v) = each %CONFIG ) {
        print "$k => $v\n";
    }

    return;
}

“配置”包含:

pub=3>rec=0>size=3>adv=1234 123 4.5 6.00
pub=1>rec=1>size=2>adv=111 22 3456 .76

最后的数字也需要删除,并保存在一个单独的 key=>value 对中,其名称可以是 'ip'。 (如果代码过于冗长和复杂,我无法做到这一点)。

【问题讨论】:

  • 你能解释一下最后一位是什么吗?也许给我们最终的结果(你想看到的键值对)。配置中最后一个数字前可以有空格吗?

标签: perl string parsing hash


【解决方案1】:

您的配置数据结构应该是什么样的?到目前为止,解决方案只记录最后一行,因为它们每次添加记录时都会踩到相同的哈希键。

这可能会让你更接近,但你仍然需要弄清楚数据结构应该是什么。

  • 我将文件句柄作为参数传递,因此我的子例程不依赖于获取数据的特定方式。在这种情况下,它可以来自文件、字符串、套接字,甚至是 DATA 下面的内容。

  • 我没有在解析字符串后进行修复,而是在解析字符串之前将其修复为具有“ip”元素。一旦我这样做了,“ip”元素就不是特例了,它只是一个双重分割的问题。这是一项非常重要的技术,可以节省大量工作和代码。

  • 我在子例程中创建了一个哈希引用,并在完成后返回该哈希引用。我不需要全局变量。 :)

使用警告; 使用严格; 使用 Data::Dumper; 读取配置文件(\*DATA); 子读取配置文件 { 我的($fh)= 班次; 我的 $hash = {}; 而( ) { 咀嚼; s/\s+(\d*\.\d+)$/>ip=$1/; $哈希->{ $. } = { 地图 { 拆分 /=/ } 拆分 />/ }; } 返回$哈希; } 我的 $hash = readConfigFile(\*DATA); 打印自卸车($hash); __数据__ pub=3>rec=0>size=3>adv=1234 123 4.5 6.00 pub=1>rec=1>size=2>adv=111 22 3456 .76

这为您提供了一个数据结构,其中每一行都是单独的记录。我选择记录的行号 ($.) 作为顶级键,但你可以使用任何你喜欢的。

$VAR1 = { '1' => { 'ip' => '6.00', 'rec' => '0', 'adv' => '1234 123 4.5', '酒吧' => '3', '大小' => '3' }, '2' => { 'ip' => '.76', 'rec' => '1', 'adv' => '111 22 3456', '酒吧' => '1', '大小' => '2' } };

如果这不是您想要的结构,请告诉我们您希望最终得到什么,我们可以调整答案。

【讨论】:

  • 这是我正在寻找的结构,但我认为对 *DATA 的使用有点困惑,您能否对此多说一些,如果我希望使用文件句柄,我该怎么做。我暂时无法做到这一点。 :-(
  • *DATA 是一个 typeglob 引用。获取命名文件句柄的文件句柄引用有点神奇。您不必担心它,因为您可能不会在实际程序中使用 DATA。您可以将任何文件句柄传递给子例程。
【解决方案2】:

我假设您要阅读和解析超过 1 行。所以,我选择将值存储在 AoH 中。

#!/usr/bin/perl
使用严格;
使用警告;

我的@config;

而(){
    咀嚼;
    推@config,{拆分/[=>]/};
}

对于我的 $href (@config) {
    而(我的($k,$v)=每个%$href){
        打印 "$k => $v\n";
    }
}

__数据__
pub=3>rec=0>size=3>adv=1234 123 4.5 6.00
pub=1>rec=1>size=2>adv=111 22 3456 .76

这将导致下面的打印输出。 (上面的 while 循环从 DATA 中读取。)

rec => 0
进阶 => 1234 123 4.5 6.00
酒吧 => 3
大小 => 3
记录 => 1
进阶 => 111 22 3456 .76
酒吧 => 1
大小 => 2

克里斯

【讨论】:

  • 我修复了你的数据。在 pre 中,您必须使用小于符号的实体,否则 HTML 认为它是一个标记。太糟糕了,降价预览被破坏了(或相反)。
  • 哦,别忘了他要求行的最后一个数字是一个单独的键。
  • 这行得通,除了最后一位数字的要求,正如布赖恩所指出的那样。不过谢谢你:-)
【解决方案3】:

以下假设定界符保证为>,并且不可能出现在数据中。

我只是根据“>”分割每一行。最后一个值将包含一个 key=value 对,然后是一个空格,然后是 IP,因此将其拆分为 // 仅一次(限制 2),您将获得 k=v 和 IP。将 IP 保存到哈希中,并将 k=v 对保留在数组中,然后遍历数组并在 '=' 上拆分 k=v。

填写 hashref 并将其推送到更高范围的数组。完成后,这将包含您的 hashrefs。

(已将配置加载到数组中)

my @hashes;

for my $line (@config) {
    my $hash; # config line will end up here

    my @pairs = split />/, $line;

    # Do the ip first. Split the last element of @pairs and put the second half into the
    # hash, overwriting the element with the first half at the same time.
    # This means we don't have to do anything special with the for loop below.
    ($pairs[-1], $hash->{ip}) = (split / /, $pairs[-1], 2);

    for (@pairs) {
        my ($k, $v) = split /=/;
        $hash->{$k} = $v;
    }

    push @hashes, $hash;
}

【讨论】:

    【解决方案4】:

    配置文件格式不是最理想的,容我们说吧。也就是说,有更容易解析和理解的格式。 [已添加:但格式已由另一个程序定义。 Perl 足够灵活来处理这个问题。]

    当没有真正需要时,您的代码会删除文件。

    您的代码只关注文件中的最后一行数据(正如 Chris Charley 在我输入此内容时指出的那样)。

    您也不允许使用注释行或空白行 - 在任何配置文件中两者都是一个好主意,并且它们很容易支持。 [添加:同样,对于预定义的格式,这几乎无关紧要,但是当您设计自己的文件时,请记住它。]

    这是将您的函数改编成更惯用的 Perl。

    #!/bin/perl -w
    use strict;
    use constant debug => 0;
    
    sub readConfigFile()
    {
        my %CONFIG;
        open(CON_FILE, "config") or die "failed to open file ($!)\n";
    
        while (my $line = <CON_FILE>)
        {
            chomp $line;
            $line =~ s/#.*//;           # Remove comments
            next if $line =~ /^\s*$/;   # Ignore blank lines
    
            foreach my $field (split(/>/, $line))
            {
                my @arr = split(/=/, $field);
                $CONFIG{$arr[0]} = $arr[1];
                print ":: $arr[0] => $arr[1]\n" if debug;
            }
        }
        close(CON_FILE);
    
        while (my($k,$v) = each %CONFIG)
        {
            print "$k => $v\n";
        }
        return %CONFIG;
    }
    
    readConfigFile;    # Ignores returned hash
    

    现在,您需要更清楚地解释最后一个字段的结构是什么,以及为什么您有一个没有 key=value 表示法的“ip”字段。一致性使每个人的生活更轻松。您还需要考虑应该如何处理多行。我会探索使用更正统的符号,例如:

    pub=3;rec=0;size=3;adv=(1234,123,4.5);ip=6.00
    

    冒号或分号作为分隔符是相当常规的;列表中以逗号分隔的项目周围的括号并不是一个离谱的约定。一致性是最重要的。艾默生说:“愚蠢的一致性是小头脑的妖精,受到小政治家、哲学家和神学家的崇拜”,但计算机科学中的一致性对每个人都有很大的好处。

    【讨论】:

    • 艾默生说“愚蠢的一致性”。这与不愚蠢的一致性不同。 :)
    • 实际上我从一个已经存在的文件中获取输入以用于其他目的(它是一个要解析和检查的树结构),因此使用了奇怪的分隔符......大脑给出了多行的解决方案,对我来说看起来不错。你的 ADV 符号看起来不错。谢谢...
    • @Brian:是的,愚蠢的一致性不同于非愚蠢的一致性;最困难的部分是将两者区分开来。
    • @Gagneet:很公平;我想知道它是否是一种预先存在的格式,但暗示它显然不是一种好的格式并没有什么坏处。 Perl 足够灵活,可以适应世界其他地方对其所做的最坏的事情。
    【解决方案5】:

    这是一种方法。

    foreach (@lines) { 咀嚼; 我的%配置; # 先提取最后一个数字,并用结尾替换 # 对分隔符。 s/\s*([\d\.]+)\s*$/>/; $CONFIG{ip} = $1; 而 ( /([^=]*)=([^>]*)>/g ) { $CONFIG{$1} = $2; } 打印自卸车(\%CONFIG); }

    【讨论】:

    • 投反对票很蹩脚。打印语句与解决方案无关。否决所有不属于你的答案?
    • 可能需要对代码进行一些更改,因为最后一位数字再次作为单独的哈希变量出现 $VAR1 = { 'rec' => '1', 'ip' => '.76 ', 'adv' => '111 22 3456', 'pub' => '1', 'size' => '2' }; $VAR1 = { 'ip' => '.76' };
    • 不,否决票是因为代码没有机会解决问题。它忘记了除了最后一行之外的所有内容。我赞成实际可行的解决方案,但您的解决方案不是其中之一。
    • 原始请求不需要返回 %CONFIG。它可以在循环内处理,不需要记住每一行。对于将字符串转换为哈希,这将工作得很好。 :)
    • 回答人们提出的问题与回答他们的问题不同。
    猜你喜欢
    • 2017-01-06
    • 2019-03-07
    • 2013-02-12
    • 1970-01-01
    • 1970-01-01
    • 2015-07-10
    • 2020-01-02
    • 2012-11-11
    • 2014-09-24
    相关资源
    最近更新 更多