【问题标题】:Read CSV to parse data and store it in Hash读取 CSV 以解析数据并将其存储在 Hash 中
【发布时间】:2020-10-23 21:53:18
【问题描述】:

我有一个 CSV 文件,其中包含如下数据:

我想从上面的 csv 文件中解析数据并将其存储在最初的哈希中。所以我的哈希转储器%hash 看起来像这样:

$VAR1 = {
            '1' =>  {
                        'Name' => 'Name1',
                        'Time' => '7/2/2020 11:00'
                        'Cell' => 'NCell1',
                        'PMR'  => '1001',
                        'ISD'  => 'ISDVAL1',
                        'PCO'  => 'PCOVAL1' 
                    },
            '2' =>  {
                        'Name' => 'Name2',
                        'Time' => '7/3/2020 13:10',
                        'Cell' => 'NCell2',
                        'PMR'  => '1002',
                        'PCO'  => 'PCOVAL2',
                        'MKR'  => 'MKRVAL2',
                        'STD'  => 'STDVAL2'
                    },      
            '3' =>  {
                        'Name' => 'Name3',
                        'Time' => '7/4/2020 20:15',
                        'Cell' => 'NCell3',
                        'PMR'  => '1003',
                        'ISD'  => 'ISDVAL3',
                        'MKR'  => 'MKRVAL3'
                    },        
        };

脚本如下:

#!/usr/bin/perl

use strict;
use warnings;

use Text::CSV;
use Data::Dumper;

my %hash;

my $csv = Text::CSV->new ({ binary => 1, auto_diag => 1 });
open my $fh, "<:encoding(utf8)", "input_file.csv" or die "input_file.csv: $!";
while (my $row = $csv->getline ($fh)) {
    my @fields = @$row;
    $hash{$fields[0]}{"Time"} = $fields[1];
    $hash{$fields[0]}{"Name"} = $fields[2];
    $hash{$fields[0]}{"Cell"} = $fields[3];
}
close $fh;

print Dumper(\%hash);

这里id是每一行中的一个关键元素,根据数据值,每个数据应该存储在一个id的相应名称中。

这里的问题是,直到列 D (Cell) 我能够在上面的脚本中解析数据,并且在 D 列之后不会有标题行,就像 E 列将充当标题和列F 是特定标头的特定 id 的值。类似的情况会出现在其余的数据值上,直到结束。在中间我们可以看到一些值也会丢失。例如,id 1 没有 MKR 值。

如何解析这些数据并将其存储在哈希中,以便我的哈希看起来像上面那样。 TIA。

【问题讨论】:

    标签: csv perl hash


    【解决方案1】:

    对发布的脚本所做的更改是删除标题行,使其不构成结果的一部分,并添加了一个 for 循环来设置数据的重置。

    使用的测试数据:

    id,Time,Name,Cell,,,,,
    1,7/2/2020 11:00,Name1,NCell1,PMR,1001,ISD,ISDVAL1
    2,7/3/2020 13:10,Name2,NCell3,PMR,1002,PCO,PCOVAL2,MKR,MKRVAL2
    

    更新脚本:(这是第一个版本,建议在编辑中使用改进版本)

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    
    use Text::CSV;
    use Data::Dumper;
    
    my %hash;
    
    my $csv = Text::CSV->new ({ binary => 1, auto_diag => 1 });
    open my $fh, "<:encoding(utf8)", "input_file.csv" or die "input_file.csv: $!";
    my $headers = $csv->getline ($fh);
    while (my $row = $csv->getline ($fh)) {
        $hash{$row->[0]}{Time} = $row->[1];
        $hash{$row->[0]}{Name} = $row->[2];
        $hash{$row->[0]}{Cell} = $row->[3];
        for (my $i = 4; $i < scalar (@{$row}); $i += 2) {
            $hash{$row->[0]}{$row->[$i]} = $row->[$i + 1];
        }
    }
    close $fh;
    
    print Dumper(\%hash);
    

    输出:

    $VAR1 = {
              '2' => {
                       'MKR' => 'MKRVAL2',
                       'Name' => 'Name2',
                       'PCO' => 'PCOVAL2',
                       'Cell' => 'NCell3',
                       'Time' => '7/3/2020 13:10',
                       'PMR' => '1002'
                     },
              '1' => {
                       'Name' => 'Name1',
                       'ISD' => 'ISDVAL1',
                       'Cell' => 'NCell1',
                       'Time' => '7/2/2020 11:00',
                       'PMR' => '1001'
                     }
            };
    

    编辑:

    感谢@choroba 的评论,这里是脚本的改进版本,首先使用所有其他行值设置哈希,然后使用从文件中读取的标题行添加第一个值Time Name Cell

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    
    use Text::CSV;
    use Data::Dumper;
    
    my %hash;
    
    my $csv = Text::CSV->new ({ binary => 1, auto_diag => 1 });
    open my $fh, "<:encoding(utf8)", "input_file.csv" or die "input_file.csv: $!";
    my $headers = $csv->getline ($fh);
    while (my $row = $csv->getline ($fh)) {
        $hash{$row->[0]} = { @$row[4 .. $#$row] };
        @{$hash{$row->[0]}}{@$headers[1, 2, 3]} = @$row[1, 2, 3];
    }
    close $fh;
    
    print Dumper(\%hash);
    

    【讨论】:

    • 您可以通过将内部代码更改为$hash{ $row-&gt;[0] } = { @$row[4 .. $#$row] }; @{ $hash{ $row-&gt;[0] } }{qw{ Time Name Cell }} = @$row[1, 2, 3];来缩短和加速循环
    • 感谢@choroba 对帖子进行了编辑。还包括使用从文件中读取的标题行以获取 Time Name Cell
    • 你能帮我理解编辑代码的逻辑吗?
    • $hash{$row-&gt;[0]} = { @$row[4 .. $#$row] }; 导致哈希 ref 初始设置为该行中的所有值,该行中的所有值都在该行的索引 4 处声明,直到该行中的最后一个值。像这样的东西:$hash{$row-&gt;[0]} = {"PMR", "1001", "ISD", "ISDVAL1"};$hash{$row-&gt;[0]} = {"PMR" =&gt; "1001", "ISD" =&gt; "ISDVAL1"};。此行 @{$hash{$row-&gt;[0]}}{@$headers[1, 2, 3]} = @$row[1, 2, 3]; 添加到该哈希引用。类似于$hash{$row-&gt;[0]}{$header-&gt;[1]} = $row-&gt;[1];。希望对您有所帮助。
    • 这种解释在答案中会做得很好,而不是在评论中 :)
    【解决方案2】:

    您可以使用一些Text::CSV 功能来简化此操作。通过去除循环中的密度可以获得很多可读性。

    首先,您可以为缺少的标题值设置列名。我不知道这些列代表什么,所以我称它们为K1V1 等等。您可以为它们替换更好的名称。我如何做到这一点并不重要,重要的是我做到了。我使用的是 v5.26,因为我使用的是postfix dereferencing

    use v5.26;
    my $headers = $csv->getline($fh);
    
    my @kv_range = 1 .. 4;
    $headers->@[4..11] = map { ("K$_", "V$_") } @kv_range;
    $csv->column_names( $headers );
    

    如果我知道这些名字,我可以用它们代替数字。我只是更改@kv_range中的内容:

    my @kv_range = qw(machine test regression ice_cream);
    

    而且,当数据文件发生变化时,我会在这里处理所有这些。当它在循环之外时,错过的就更少了。

    现在我已经命名了所有列,我使用getline_hr 来取回该行的哈希引用。键是我刚刚设置的列名。这已经为你做了很多工作。你必须在最后处理这些对,但这也很容易:

    my %Grand;
    while( my $row = $csv->getline_hr($fh) ) {
        foreach ( @kv_range ) {
            no warnings 'uninitialized';
            $row->{ delete $row->{"K$_"} } = delete $row->{"V$_"};
            }
        $Grand{ $row->{id} } = $row;
        delete $row->@{ 'id', '' };
        }
    

    现在处理最后的对:我想取K1 列中的值并将其设为键,然后取V1 中的值并将其设为值。同时,我需要删除那些K1V1 列。 delete 有一个很好的行为,它返回您删除的键的值。这种方式不需要任何类型的指针数学或有关位置的知识。这些事情可能会改变,而在我走到这一步之前,我已经处理了所有这些:

            $row->{ delete $row->{"K$_"} } = delete $row->{"V$_"};
    

    如果这句话对你来说太过分了,你也可以分几步完成:

            my( $key, $value ) = delete $row->@{ "K$_", "V$_" };
            $row->{$key} = $value;
    

    我会将id 列保留在其中,但如果您不想要它,请将其删除。此外,deletes 的那一步可能为没有值的单元格创建了一些空字符串键。我没有防范这种情况并使foreach 更加复杂,而是让它发生并在最后摆脱它:

        delete $row->@{ 'id', '' };
    

    总而言之,它看起来像这样。它与Piet Bosch's answer 做同样的事情,但我已经将很多复杂性推回到模块中,并做了一些预循环工作:

    use v5.26;
    use strict;
    use warnings;
    
    use Data::Dumper;
    use Text::CSV;
    
    my $csv = Text::CSV->new({
        binary    => 1,
        auto_diag => 1
        });
    
    open my $fh, "<:encoding(utf8)", "input_file.csv"
        or die "input_file.csv: $!";
    
    my $headers = $csv->getline($fh);
    
    my @kv_range = 1 .. 4;
    $headers->@[4..11] = map { ("K$_", "V$_") } @kv_range;
    $csv->column_names( $headers );
    
    my %Grand;
    while( my $row = $csv->getline_hr($fh) ) {
        foreach ( @kv_range ) {
            no warnings 'uninitialized';
            $row->{ delete $row->{"K$_"} } = delete $row->{"V$_"};
            }
        $Grand{ $row->{id} } = $row;
        delete $row->@{ 'id', '' };
        }
    
    say Dumper( \%Grand );
    

    输出如下所示:

    $VAR1 = {
              '2' => {
                       'PMR' => '1002',
                       'PCO' => 'PCOVAL2',
                       'MKR' => 'MKRVAL2',
                       'Name' => 'Name2',
                       'Time' => '7/3/2020 13:10',
                       'Cell' => 'NCell3'
                     },
              '1' => {
                       'Cell' => 'NCell1',
                       'Time' => '7/2/2020 11:00',
                       'ISD' => 'ISDVAL1',
                       'PMR' => '1001',
                       'Name' => 'Name1'
                     }
            };
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-10
      • 1970-01-01
      • 1970-01-01
      • 2015-07-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多