2

私は Perl を書かなければなりませんが、Java、Python、および関数型言語の方がはるかに快適です。次のような単純なファイルを解析する慣用的な方法があるかどうか知りたい

# 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)
4

4 に答える 4

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. 結果のフラグメントの数を制限するオプションの 3 番目の引数を取ります。
  • map/の全体filterを 1 つの式で記述できます。これにより効率が向上するわけではありませんが、データの流れが強調されます。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;
于 2013-04-25T18:01:42.377 に答える
4

最も近代的なアプローチは、CPAN モジュールを利用することにあると思います。あなたの例では、Config::Propertiesが役立つ場合があります。

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

my $config = Config::Properties->new(file => 'example.properties') or die $!;
my $value = $config->getProperty('key');
于 2013-04-25T17:27:37.060 に答える
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'
                    ]
        };
于 2013-04-26T07:00:39.650 に答える