richard90522 has asked for the wisdom of the Perl Monks concerning the following question:

I would like to change a perldata-structure recursively. In this example multiply everything by 100.

I found hints to traverse/read a data-structure, but nothing to change it in place. A good solution would also be to build up a parallel structure with changed data. Thanks for your help!

#!/usr/bin/perl use strict; use warnings; use Data::Dumper; sub change_all_elements { my ( $data, $subref ) = @_; if ( ref($data) eq 'HASH' ) { for my $k ( keys %$data ) { change_all_elements( $data->{$k}, $subref ); } } elsif ( ref($data) eq 'ARRAY' ) { for my $line (@$data) { change_all_elements( $line, $subref ); } } elsif ( ref($data) eq '' ) { $subref->($data); } else { die "I dont know " . ref($data) . "\n"; } } my $deep = { a => 12, b => 13, c => { d => 14, e => 15, }, f => [ 16, 17, ] }; # I want to have all scalar elements to be 100x my $changed = change_all_elements( $deep, sub { 100 * $_[0]; } ); print "All changed:\n"; print Dumper($deep);

Replies are listed 'Best First'.
Re: Recursively change perl-data-structure
by 1nickt (Canon) on Nov 01, 2022 at 18:10 UTC

    Hi,

    See Data::Rmap. Built for the task.

    Hope this helps!


    The way forward always starts with a minimal test.

      Helpful module, no trouble to compile with "cpan install", also available in Debian as package libdata-rmap-perl..

      No recursive stuff to think about, recursive feels very strange to me. Easy to use. Thanks!

Re: Recursively change perl-data-structure
by Corion (Patriarch) on Nov 01, 2022 at 17:05 UTC

    The easy way would be to use the aliasing of @_:

    ... elsif ( ref($data) eq '' ) { $_[0] = $subref->($data); } ...

    ... but that will of course break as soon as you change the order or arguments of change_all_elements.

    The other approach would be to rewrite your code so that it does the assignment and check one level higher:

    ... if ( ref($data) eq 'HASH' ) { for my $k ( keys %$data ) { if( ref $data->{$k} ) { change_all_elements( $data->{$k}, $subref ); } else { $data->{$k} = subref->($data->{$k}); } } } ... # and the same for arrays
      Helpful and working, the old $_[0] changed because it's a pointer to the real data:
      $_[0] = $subref->($data);
      Thanks!
Re: Recursively change perl-data-structure
by NERDVANA (Priest) on Nov 02, 2022 at 00:24 UTC
    This isn't necessarily the best advice, but I personally find these traversal problems really fun to solve in perl using the $_ variable. It also gives the fastest execution time.
    sub recursive_mult_by_100() { if (!ref) { $_ *= 100 } elsif (ref eq 'ARRAY') { recursive_mult_by_100 for @$_ } elsif (ref eq 'HASH') { recursive_mult_by_100 for values %$_ } else { die "Unhandled data type ".ref; } } recursive_mult_by_100 for $deep;

    This takes advantage of the feature where Perl aliases things to $_ during a for loop, so modifying $_ modifies the underlying thing, and where ref operates on $_ by default if you don't give it a regular parameter.

    I say it's not the best advice because it isn't the most readable code, nor a standard pattern that people expect. Note that the () on the function stops it from accepting parameters, which helps you avoid the mistake of calling recursive_mult_by_100($data) and getting buggy results.