gpt4 book ai didi

perl - 函数式 Perl : Filter, 迭代器

转载 作者:行者123 更新时间:2023-12-01 05:05:38 25 4
gpt4 key购买 nike

虽然我更熟悉 Java、Python 和函数式语言,但我必须编写 Perl。我想知道是否有一些惯用的方法来解析像

这样的简单文件
# comment line - ignore

# ignore also empty lines
key1 = value
key2 = value1, value2, value3

我想要一个函数,我在文件的行上传递一个迭代器,并返回一个从键到值列表的映射。但为了功能性和结构化,我想:

  • 使用过滤器包装给定的迭代器并返回没有空行或注释行的迭代器
  • 提到的过滤器应该在函数之外定义,以供其他函数重用。
  • 使用另一个给定行并返回键和值字符串的元组的函数
  • 使用另一个函数将逗号分隔值分解为值列表。

执行此操作的最现代、惯用、最干净且仍然有效的方法是什么?代码的不同部分应该可以单独测试和重用。

作为引用,这里是(一个快速破解)我如何在 Python 中完成它:

re_is_comment_line = re.compile(r"^\s*#")
re_key_values = re.compile(r"^\s*(\w+)\s*=\s*(.*)$")
re_splitter = re.compile(r"\s*,\s*")
is_interesting_line = lambda line: not ("" == line or re_is_comment_line.match(line))
and re_key_values.match(line)

def parse(lines):
interesting_lines = ifilter(is_interesting_line, imap(strip, lines))
key_values = imap(lambda x: re_key_values.match(x).groups(), interesting_lines)
splitted_values = imap(lambda (k,v): (k, re_splitter.split(v)), key_values)
return dict(splitted_values)

最佳答案

您的 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,并且可以将表达式而不是 block 作为第一个参数。这些大致相当于一个 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;
...;
}

这将使用实际的迭代器。

您现在可以去添加函数参数,但只有当您正在优化灵 active 并且没有其他时才这样做。我已经在第一个示例中使用了代码引用。您可以使用 $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;

关于perl - 函数式 Perl : Filter, 迭代器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16220455/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com