1

配列のリストからツリー状のデータ構造に変換する必要があります。データの処理を開始する前にツリーの深さを知っていますが、コードを再利用できるように柔軟性を維持したいと考えています。

そこで、(Moose ベースのモジュール内から) その場で subref を生成して、配列からツリーに移動するというアイデアにたどり着きました。このように(単純化された方法で):

use Data::Dump qw/dump/;

sub create_tree_builder {
     my $depth = shift;
     return eval join '', 'sub { $_[0]->{$_[', 
                           join(']}->{$_[', (1..$depth)),
                          ']} = $_[',  $depth + 1 , '] }'; 
}


my $s = create_tree_builder(5);
my $tree = {};

$s->($tree, qw/one two three four five/, 'a value');

print dump $tree;

# prints
#  {
#     one => { two => { three => { four => { five => "a value" } } } },
#  }

これは私に世界を切り開き、パラメトリックに生成された文字列をあらゆる場所で関数に評価するこのプロセスのクールな用途を見つけています(明らかに、問題を探すための解決策です)。

しかし、それは少し気持ちが良すぎて、ほとんど真実ではありません。

この慣行に対するアドバイスはありますか?または改善のための提案?

任意の入力を評価することが最も安全ではない可能性があることははっきりとわかりますが、他に何がありますか?

ファローアップ

すべての答えをありがとう。私は amon のコードを使用して、次のように少しベンチマークしました。

use Benchmark qw(:all) ;

$\ = "\n";

sub create_tree_builder {
 my $depth = shift;
 return eval join '', 'sub { $_[0]->{$_[', 
               join(']}->{$_[', (1..$depth)),
              ']} = $_[',  $depth + 1 , '] }'; 
}


my $s = create_tree_builder(5);

$t = sub {
$_[0] //= {};

    my ($tree, @keys) = @_;
    my $value = pop @keys;

    $tree = $tree->{shift @keys} //= {} while @keys > 1;
    $tree->{$keys[0]} = $value;
};


cmpthese(900000, {
        'eval'  => sub { $s->($tree, qw/one two three four five/, 'a value') },
    'build' => sub { $t->($tree, qw/one two three four five/, 'a value') },

});

結果は、評価されたファクトリではなく、ツリーの構築に明らかに有利です。

            Rate build  eval
build  326087/s    --  -79%
eval  1525424/s  368%    -- 

私は以前にそれを行うことができたことを認めます。(同じ要素を何度も割り当てるのではなく) より多くのランダム ツリーを試してみますが、結果が異なる理由はわかりません。

助けてくれてありがとう。

4

4 に答える 4

5

このようなネストされたハッシュを構築する一般化されたサブルーチンを作成するのは非常に簡単です。この方法は、特定の数のハッシュ レベルに対してそのようなサブルーチンを生成するファクトリを作成するよりもはるかに簡単です。

use strict;
use warnings;

sub tree_assign {

  # Create an empty tree if one was not given, using an alias to the original argument
  $_[0] //= {};

  my ($tree, @keys) = @_;
  my $value = pop @keys;

  $tree = $tree->{shift @keys} //= {} while @keys > 1;
  $tree->{$keys[0]} = $value;
}

tree_assign(my $tree, qw/one two three four five/, 'a value');

use Data::Dump;
dd $tree;

出力

{
  one => { two => { three => { four => { five => "a value" } } } },
}
于 2013-01-09T23:39:45.510 に答える
3

なぜこれが悪い考えかもしれないか

  1. 保守性。

    評価されるコードは、最初にプログラマーの頭の中で評価される必要があります。必ずしも簡単な作業ではありません。基本的に、回避は難読化です。

  2. スピード。

    eval通常の実行が再開される前に、perlパーサーとコンパイラーを再実行します。ただし、同じ手法を使用して、サブルーチンのコンパイルを必要になるまで延期することにより、起動時間を増やすことができます。これはそのような場合ではありません

  3. それを行うには複数の方法があります。

    私は匿名サブルーチンが好きですが、evalそれらを構築するためにを使用する必要はありません。とにかく閉鎖です。何かのようなもの

    ...;
    return sub {
      my ($tree, $keys, $value) = @_;
      $#$keys >= $depth or die "need moar keys";
      $tree = $tree->{$keys->[$_]} for 0 .. $depth - 1;
      $tree->{$keys->[$depth]} = $value;
    };
    

    $s->($tree, [qw(one two three four five)], "a value");
    

    驚くほど似たようなことをするでしょう。(実際、$depth現在の使用は設計エラーのように見えます。完全なパスはすでにキーによって指定されています。したがって、通常の名前付きサブルーチンを作成するのがおそらく最善です。)

于 2013-01-09T23:32:25.843 に答える
2

彼らのコメントに基づいてOPが何をしているのかを理解し、Borodinのコードをリフして、インターフェースの変更を提案します。ツリーの奥深くに値を適用するサブルーチンを作成するのではなく、空のサブツリーを作成してからそのサブツリーで作業するサブルーチンを作成します。これにより、すべての操作でツリーをたどる必要がなく、サブツリーで効率的に作業できます。

package Root;

use Mouse;

has root =>
  is    => 'ro',
  isa   => 'HashRef',
  default => sub { {} };

sub init_subtree {
    my $self = shift;
    my $tree = $self->root;

    for my $key (@_) {
        $tree = $tree->{$key} //= {};
    }

    return $tree;
}

my $root = Root->new;
my $subtree = $root->init_subtree(qw/one two three four/);

# Now you can quickly work with the subtree without having
# to walk down every time.  This loop's performance is only
# dependent on the number of keys you're adding, rather than
# the number of keys TIMES the depth of the subtree.
my $val = 0;
for my $key ("a".."c") {
    $subtree->{$key} = $val++;
}

use Data::Dump;
dd $root;
于 2013-01-10T00:12:37.470 に答える
2

Data::Diverはあなたの友達です:

use Data::Diver 'DiveVal', 'DiveRef';
my $tree = {};

DiveVal( $tree, qw/one two three four five/ ) = 'a value';

# or if you hate lvalue subroutines:
${ DiveRef( $tree, qw/one two three four five/ ) } = 'a value';

use Data::Dump 'dump';
print dump $tree;
于 2013-01-10T00:54:23.170 に答える