【问题标题】:How to use pseudo hash in modern perl?如何在现代 perl 中使用伪散列?
【发布时间】:2020-03-20 02:43:22
【问题描述】:

我以下一种格式加载了数据(可能从.csv 文件加载):

my $data =  [
   [ 'id', 'name', 'value' ],
   [   23,  'foo',      77 ],
   [   44,  'bar',   'dfd' ],
]

我想访问如下数据:

$data->[$n]{ name }

我知道在旧的 perl 中我可以使用 phash(伪哈希),但它已被弃用并被 fields pragma 取代。

据我所知,它用于对象。就我而言,我不创建对象也不使用类。

我应该如何在我的用例中使用fields?请举个例子

【问题讨论】:

  • 有趣,这里有更多information关于psuedo-hashes perl v5.8的原始用法。
  • 哇,这倒是很早...
  • @zdim:通过$data->[$n]{name} 我想获得name 字段的值。稍微更新了问题。

标签: perl hash


【解决方案1】:

使用Text::CSV_XS模块读取你的CSV数据,并根据第一行告诉它列名是什么:

#!/usr/bin/env perl
use strict;
use warnings;
use feature 'say';
use Text::CSV_XS;

my $csv = Text::CSV_XS->new({binary => 1, auto_diag => 1});
$csv->column_names($csv->getline(\*DATA));
my $data = $csv->getline_hr_all(\*DATA);
say $data->[0]->{'name'}; # prints foo

__DATA__
id,name,value
23,foo,77
44,bar,dfd

不过,请考虑循环遍历记录,而不是一次读取整个文件。请参阅getline_hr 的文档了解几种方法。

【讨论】:

  • 嗨。我不能使用 Text::CSV_XS。我已经以这种格式加载了数据。另外我澄清一下问题:我不确定数据是从.csv 加载的
  • @Eugen Konkov,然后使用 Text::CSV
  • @ikegami:我更仔细地阅读了Text::CSV,现在我很困惑。 details
【解决方案2】:

如果你可以use Text::CSV_XS as Shawn shows,那就去做吧。

我们在Intermediate Perl 中有一个练习可以做到这一点。使用第一行将标头名称映射到位置,然后使用该散列转换另一种方式。这里大量使用postfix dereferencing

use v5.24;

my $data =  [
   [ 'id', 'name', 'value' ],
   [   23,  'foo',      77 ],
   [   44,  'bar',   'dfd' ],
];

# ikegami's suggestion
my %name_to_index = map { $data->[0][$i] => $_ } 0..$data->[0]->$#*;

foreach my $i ( 1 .. $data->$#* ) {
    say $data->[$i][ $name_to_index{name} ]
    }

这是适用于 v5.24 之前的版本的circumfix 版本,但我认为它更丑(而且你必须使用一个六岁的、不受支持的 Perl):

use v5.10;

my $data =  [
   [ 'id', 'name', 'value' ],
   [   23,  'foo',      77 ],
   [   44,  'bar',   'dfd' ],
];

# ikegami's suggestion
my %name_to_index = map { $data->[0][$i] => $_ } 0.. $#{ $data->[0] };

foreach my $i ( 1 .. $#{ $data } ) {
    say $data->[$i][ $name_to_index{name} ]
    }

由于真正的代码可能要复杂得多,我认为当您不深入研究所有数据结构时,通常会更容易理解。如果您不介意额外的工作(就像您在热循环中所做的那样),您可以将您的行变成一个哈希,其中键是标题(类似于 Text::CSV_XS 所做的)然后使用该哈希而不考虑整个取消引用链。此示例使用哈希切片一次填充所有内容。之后你玩%hash而不是$data->[$i][...]

use v5.24;

my $data =  [
   [ 'id', 'name', 'value' ],
   [   23,  'foo',      77 ],
   [   44,  'bar',   'dfd' ],
];

my @headers = $data->[0]->@*;
foreach my $i ( 1 .. $data->$#* ) {
    my %hash;
    @hash{ @headers } = $data->[$i]->@*;

    say $hash{name};
    }

奇怪的是,在Pseudohash section in perlref 之后,文档显示了一个带有函数模板的示例。您可以定义子例程,而不是进行映射的哈希。有些人喜欢标头索引的名称看起来更干净一些,但我认为这不值得额外解释软refs违规和类型globs的解释:

use v5.24;

my $data =  [
   [ 'id', 'name', 'value' ],
   [   23,  'foo',      77 ],
   [   44,  'bar',   'dfd' ],
];

foreach my $name ( $data->[0]->@* ) {
    state $n = 0;
    my $m = $n++;      # don't reference $n
    no strict 'refs';  # Hey there!
    *{uc $name} = sub () { $m }; # runtime sub definition
    }

foreach my $i ( 1 .. $data->$#* ) {
    say $data->[$i][ NAME() ]
    }

【讨论】:

  • @Polar Bear,OP 特别想要替代旧版本 Perl 中的内容,因为他们没有旧版本的 Perl。 /// 让我担心的是代码不能在子中使用(因为使用了state。)修正:my $names = $data->[0]; my %name_to_index = map { $names->[0] => $_ } 0..$#$names;
  • @北极熊,跑题了。 OP 并没有询问他们是否应该迁移到更新版本的 Perl,而且这个答案也没有触及这个话题。请保持 cmets 关注主题。
  • @ikegami -- OP 询问如何实现所需的访问方法,并提到不推荐使用旧的“phash”。正如您所说,他并没有具体说明 perl 版本。我只是补充说,此解决方案不适用于由于有效原因而无法升级 perl 的旧系统。我的答案适用于所有系统,包括非常旧的系统。
  • @ikegami - 是的,我对state 中的map 感到非常兴奋。我添加了你的版本。谢谢。对于其他所有人:当我使用状态时,该变量的值将永远存在。如果我想对多个数据结构执行此操作,则该映射无法再次从 $n = 0 开始。举一个小例子,我不小心逃脱了它,我不应该为此优化。
【解决方案3】:

$data 需要转换成哈希数组。

$data 的第一行是一个字段名,它将用作哈希键。

接下来我们需要:

遍历数组的其他行

  • 从键和数据创建哈希

  • 将哈希推入新数组

当所有数据处理完毕

返回$newfinal数组的引用

此时$new数组引用的元素可以作为$new->[0]{id}访问。

请参阅以下代码,演示如何实现所需的数据存储。

注意:此代码不依赖于新的 perl 功能,并且即使在 20 年前的系统上也应该产生所需的结果(由于正当理由无法更新 perl)。

最后一个for 循环演示打印$new 数组引用的所有元素。

use strict;
use warnings;
use feature 'say';

use Data::Dumper;

my $debug = 0;                             # debug = 1 - Debug mode ON

my $data =  [
   [ 'id', 'name', 'value' ],
   [   23,  'foo',      77 ],
   [   44,  'bar',   'dfd' ],
];

my $new = convert($data);

say Dumper($new) if $debug;

for ( 0..$#{$new} ) {                      # walk through result array
    say '-' x 35;
    say 'Id:    ' . $new->[$_]{id};
    say 'Name:  ' . $new->[$_]{name};
    say 'Value: ' . $new->[$_]{value};
    say '-' x 35;
}

sub convert {
    my $data = shift;
    
    my @fields = @{$data->[0]};            # used as hash key
    
    my @data;

    for ( 1..$#{$data} ) {                # walk through $data starting from index=1
        my %hash;
        @hash{@fields} = @{$data->[$_]};  # store data in hash
        push @data, \%hash;               # store hash into array
    }
    
    return \@data;                        # return reference to array of hashes
}

输出

-----------------------------------
Id:    23
Name:  foo
Value: 77
-----------------------------------
-----------------------------------
Id:    44
Name:  bar
Value: dfd
-----------------------------------

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-12-07
    • 2010-12-31
    • 2015-07-17
    • 2015-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多