你的 Python 的直接翻译是
my $re_is_comment_line = qr/^\s*#/;
my $re_key_values = qr/^\s*(\w+)\s*=\s*(.*)$/;
my $re_splitter = qr/\s*,\s*/;
my $is_interesting_line= sub {
my $_ = shift;
length($_) and not /$re_is_comment_line/ and /$re_key_values/;
};
sub parse {
my @lines = @_;
my @interesting_lines = grep $is_interesting_line->($_), @lines;
my @key_values = map [/$re_key_values/], @interesting_lines;
my %splitted_values = map { $_->[0], [split $re_splitter, $_->[1]] } @key_values;
return %splitted_values;
}
区别是:
-
ifilter 被称为grep,并且可以将表达式而不是块作为第一个参数。这些大致相当于一个 lambda。当前项目在$_ 变量中给出。这同样适用于map。
- Perl 不强调惰性,并且很少使用迭代器。在某些情况下需要这样做,但通常会立即评估整个列表。
在下一个示例中,将添加以下内容:
- 正则表达式不必预编译,Perl 非常擅长正则表达式优化。
- 我们不使用正则表达式提取键/值,而是使用
split。它需要一个可选的第三个参数来限制结果片段的数量。
- 整个
map/filter 的东西可以写成一个表达式。这并没有提高效率,但强调了数据的流动。从下往上阅读 map-map-grep(实际上是从右到左,想想 APL)。
.
sub parse {
my %splitted_values =
map { $_->[0], [split /\s*,\s*/, $_->[1]] }
map {[split /\s*=\s*/, $_, 2]}
grep{ length and !/^\s*#/ and /^\s*\w+\s*=\s*\S/ }
@_;
return \%splitted_values; # returning a reference improves efficiency
}
但我认为这里更优雅的解决方案是使用传统循环:
sub parse {
my %splitted_values;
LINE: for (@_) {
next LINE if !length or /^\s*#/;
s/\A\s*|\s*\z//g; # Trimming the string—omitted in previous examples
my ($key, $vals) = split /\s*=\s*/, $_, 2;
defined $vals or next LINE; # check if $vals was assigned
@{ $splitted_values{$key} } = split /\s*,\s*/, $vals; # Automatically create array in $splitted_values{$key}
}
return \%splitted_values
}
如果我们决定传递一个文件句柄,循环将被替换为
my $fh = shift;
LOOP: while (<$fh>) {
chomp;
...;
}
这将使用一个实际的迭代器。
您现在可以添加函数参数,但仅当您正在优化灵活性并且什么都没有时才这样做。我已经在第一个示例中使用了代码参考。您可以使用$code->(@args) 语法调用它们。
use Carp; # Error handling for writing APIs
sub parse {
my $args = shift;
my $interesting = $args->{interesting} or croak qq("interesting" callback required);
my $kv_splitter = $args->{kv_splitter} or croak qq("kv_splitter" callback required);
my $val_transform= $args->{val_transform} || sub { $_[0] }; # identity by default
my %splitted_values;
LINE: for (@_) {
next LINE unless $interesting->($_);
s/\A\s*|\s*\z//g;
my ($key, $vals) = $kv_splitter->($_);
defined $vals or next LINE;
$splitted_values{$key} = $val_transform->($vals);
}
return \%splitted_values;
}
然后可以这样调用
my $data = parse {
interesting => sub { length($_[0]) and not $_[0] =~ /^\s*#/ },
kv_splitter => sub { split /\s*=\s*/, $_[0], 2 },
val_transform => sub { [ split /\s*,\s*/, $_[0] ] }, # returns anonymous arrayref
}, @lines;