in reply to howto map/grep a complex structure

#!/usr/bin/env perl use strict; use warnings; use Data::Dump; sub collectLeaves ($) { my ( $ds, $leaves ) = @_; $leaves = defined($leaves) ? $leaves : []; if ( ref $ds eq 'ARRAY' ) { &collectLeaves( $_, $leaves ) for ( @{$ds} ); } elsif ( ref $ds eq 'HASH' ) { &collectLeaves( $ds->{$_}, $leaves ) for ( keys %{$ds} ); } else { push @{$leaves}, $ds; } return @{$leaves}; } my $hash = { key1 => [ 3,4,5,6 ], key2 => [ 7,8,9,10 ], key3 => [ d1 => { 42 => [ 'blue' ], 58 => [ 7 ], } ] }; my @wanted = grep { $_ == 7 } collectLeaves $hash; dd @wanted; # (7, 7)
Here is a way. You can also write a more customized sub for your data structure. Not sure if this passes the test for elegant though :)

Replies are listed 'Best First'.
Re^2: howto map/grep a complex structure
by stevieb (Canon) on Aug 27, 2015 at 12:50 UTC

    I just want to point out that in this case, using prototypes is unnecessary (and actually not even used), and it's advised that you don't use them unless you know why you're using them. They're good for emulating and/or overriding built-in operators for instance.

    In the following:

    sub collectLeaves ($) { my ( $ds, $leaves ) = @_;

    You've said "I take a single scalar", but then extract from @_ after calling the sub with two distinct params, but it works. Why? Because when you call your subs the old fashioned way with &, the prototype is ignored. If you remove the & from the calls, your @_ will only contain the very last scalar element of all the params you've passed in. In fact, you'd get an error in your case, because you're sending in two params when the prototype says it only accepts one:

    sub this ($){ my @this = @_; print @this; } this(1, 1); __END__ Too many arguments for main::this at ./sub.pl line 10, near "1)" Execution of ./sub.pl aborted due to compilation errors.

    Back to the calls with &. It is legacy code, and other than a few rare cases, is really only needed when dereferencing a code reference. In your case, it's actually doing more harm than good as it makes the code very confusing for future maintainers. I'd have a look at the code and remove the &, but then prototypes may break. I'd then have to hunt down and figure out whether the prototypes are required, and remove them one at a time if they aren't. Or, a newer Perl programmer may add a sub call without & and not even understand why errors are thrown on their call, but not the others.

    Also, upon first glance at the sub definition, I may miss the $ prototype, but a my ($x, $y) = @_; will be abundantly clear. I would then be thinking why for the love of all things good is $y undef after calling the sub like func(@a);, where @a is a list with two elements. I'm passing in a list of two items, but $x is the last element of the list and $y is completely borked. Prototypes are a very uncommon thing, and unless you've dealt with them (most haven't), the errors and especially the silent modifications can be quite the treat to figure out.

      I can't say I agree with the overall approach of trippledubs here, but I think the use of prototypes therein can be said to have a constructive and well understood purpose.

      As written,  collectLeaves() is recursive, and within the function the calls such as
          &collectLeaves( $_, $leaves ) for ( @{$ds} );
      serve the purpose of suppressing prototype checking so the  $leaves reference can be passed in recursive calls.

      Outside the function, prototyping allows calls like
          my @wanted = grep { $_ == 7 } collectLeaves $hashref1, collectLeaves $hashref2;
      to be made, while
          ... collectLeaves($hashref1, $hashref2) ...
      will fail to compile.

      c:\@Work\Perl\monks>perl -wMstrict -le "sub S ($) { return $_[0] + print 0+@{$_[0]}, ' elements in ', 0+$_[0] +; } ;; print S [], S [42], 33; " 0 elements in 7450732 1 elements in 30219348 74507333021934933
      (But  S([],[42]) will not compile.)

      Again, I don't say I necessarily agree with the approach, but I cannot agree that prototyping has been sprinkled thoughtlessly onto the code as a kind of magical correctness dust, as we have often seen. OTOH, I agree with you that the response of future maintainers to this code (or recursive functions in general) is a valid concern.


      Give a man a fish:  <%-{-{-{-<

        Hi Anomalous! Thank you for discerning that it is not on accident! Why do you not necessarily agree with this approach? The nihilist in me says I shouldn't care yet the objectivist wants to improve..

      Sorry Guvna, did not know you are in charge of what is legacy / old fashioned. Please consider that I tried to make it look cool, clear, & concise when you call it without parenthesis outside the sub. It also adds some compile time checking against calling it in the wrong context.

      I'd have a look at the code and remove the &, but then prototypes may break. If I break it, it won't work.

      Prototypes are a very uncommon thing, and unless you've dealt with them How do you become proficient if you never use them a first time?

      I'm passing in a list of two items, but $x is the last element of the list and $y is completely borked. Can't pass in a list without intentionally circumventing the prototype.

      It could be re-written but then all the new perl programmers would be calling it in list context on accident and breaking everything.. those clumsy new guys. Here is another argument about maintainability Finding repeat sequences..