2013-04-25 61 views
2

我必须编写Perl,尽管我对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) 
+0

这是具有挑战性的做用Perl真正的功能性编程。我会坚持一个更迭代的方法来读取文件。 – squiguy 2013-04-25 17:44:34

回答

5

你的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,并且可以代替块作为第一个参数的表达式。这些大致相当于一个拉姆达。当前项目在$_变量中给出。这同样适用于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; 
+0

不错。在值分割中,我认为你可以在逗号前留下'\ s *',因为这个空格应该被前面的kv分割和右边的'\ s *'所吃掉。 – delicateLatticeworkFever 2013-04-25 18:08:01

+0

@goldilocks不,我不能。例如。考虑'key [=] foo(,)bar(,)baz'(parens mark是什么值拆分,括号标记kv-split) – amon 2013-04-25 18:12:41

+0

Doh! - 无论如何,很大的故障。 – delicateLatticeworkFever 2013-04-25 18:17:13

4

我觉得最现代化的方法在于取CPAN模块的优势。在你的榜样,Config::Properties 5月帮助:

use strict; 
use warnings; 
use Config::Properties; 

my $config = Config::Properties->new(file => 'example.properties') or die $!; 
my $value = $config->getProperty('key'); 
0

您可能会感兴趣的this SO question以及this one

以下代码是一个自包含的perl脚本,用于给您一个如何在perl中实现的想法(仅部分用于实用样式;如果您不感到看到特定的编码风格和/或语言构造,我可以稍微改进这个解决方案)。

Miguel Prz是正确的,在大多数情况下,您会搜索CPAN解决方案以符合您的要求。

my (
     $is_interesting_line 
    , $re_is_comment_line 
    , $re_key_values 
    , $re_splitter 
); 

$re_is_comment_line = qr(^\s*#); 
$re_key_values  = qr(^\s*(\w+)\s*=\s*(.*)$); 
$re_splitter  = qr(\s*,\s*); 
$is_interesting_line = sub { 
     my $line = shift; 
     return (
       (!(
         !defined($line) 
        || ($line eq '') 
       )) 
      && ($line =~ /$re_key_values/) 
     ); 
    }; 

sub strip { 
    my $line = shift; 
    # your implementation goes here 
    return $line; 
} 
sub parse { 
    my @lines = @_; 
    # 
    my (
      $dict 
     , $interesting_lines 
     , $k 
     , $v 
    ); 
    # 
    @$interesting_lines = 
     grep { 
       &{$is_interesting_line} ($_); 
      } (map { strip($_); } @lines) 
    ; 

    $dict = {}; 
    map { 
     if ($_ =~ /$re_key_values/) { 
      ($k, $v) = ($1, [split(/$re_splitter/, $2)]); 
      $$dict{$k} = $v; 
     } 
    } @$interesting_lines; 

    return $dict; 
} # parse 

# 
# sample execution goes here 
#  
my $parse =<<EOL; 
# comment 
what = is, this, you, wonder 
it = is, perl 
EOL 

parse (split (/[\r\n]+/, $parse)); 
2

为在@collapsar挂帖指出,Higher-Order Perl是在Perl探索功能的技术有很大的阅读。

这里是击中了你的子弹点的例子:

use strict; 
use warnings; 
use Data::Dumper; 

my @filt_rx = (qr{^\s*\#}, 
       qr{^[\r\n]+$}); 
my $kv_rx = qr{^\s*(\w+)\s*=\s*([^\r\n]*)}; 
my $spl_rx = qr{\s*,\s*}; 

my $iterator = sub { 
    my ($fh) = @_; 
    return sub { 
     my $line = readline($fh); 
     return $line; 
    }; 
}; 
my $filter = sub { 
    my ($it,@r) = @_; 
    return sub { 
     my $line; 
     do { 
      $line = $it->(); 
     } while ( defined $line 
       && grep { $line =~ m/$_/} @r); 
     return $line; 
    }; 
}; 
my $kv = sub { 
    my ($line,$rx) = @_; 
    return ($line =~ m/$rx/); 
}; 
my $spl = sub { 
    my ($values,$rx) = @_; 
    return split $rx, $values; 
}; 

my $it = $iterator->(\*DATA); 
my $f = $filter->($it,@filt_rx); 

my %map; 
while (my $line = $f->()) { 
    my ($k,$v) = $kv->($line,$kv_rx); 
    $map{$k} = [ $spl->($v,$spl_rx) ]; 
} 
print Dumper \%map; 

__DATA__ 
# comment line - ignore 

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

它生产所提供的输入下面的哈希:

$VAR1 = { 
      'key2' => [ 
         'value1', 
         'value2', 
         'value3' 
        ], 
      'key1' => [ 
         'value' 
        ] 
     }; 
+0

非常好的练习来展示如何实现迭代器。 (但是,'readline'应该被强制置于标量上下文中,或者不能保证是一个迭代器。注意,'$ kv_rx'的选择会隐式地从每行中删除换行符。这明确) – amon 2013-04-26 09:09:41

+0

感谢您的意见 - 更新。 – 2013-04-27 03:23:32