Re: howto map/grep a complex structure
by kcott (Archbishop) on Aug 27, 2015 at 05:55 UTC
|
"i look forward to learn how to make it more elegant with map and grep."
You appear to be assuming that using map and/or
grep will automatically make your code more elegant.
This is not the case.
The code you already have is easy to read (and therefore maintain) and its intent is clear.
I can't say the same for these map/grep alternatives:
print $_->[0] for grep {
@{$_->[1]}
}
map {
[ $_, [ grep { $_ eq $search } @{$hash->{$_}{member}} ] ]
} keys %$hash;
print $_->[0] for grep {
$_->[1]
}
map {
[ $_, scalar grep { $_ eq $search } @{$hash->{$_}{member}} ]
} keys %$hash;
Furthermore, for loops are often faster.
Benchmark to check this with whatever alternative solution(s)
you might be considering.
Here's my test code:
#!/usr/bin/env perl -l
use strict;
use warnings;
my $hash = {
A => {
member => [qw{q w e}],
},
B => {
member => [qw{a s d}],
},
C => {
member => [qw{q a z}],
},
};
my $search = 'q';
print 'OP Solution:';
for my $hk (keys %$hash) {
for my $m (@{$hash->{$hk}{member}}) {
if ($m eq $search) {
print $hk;
}
}
}
print 'map/grep Solution 1';
print $_->[0] for grep {
@{$_->[1]}
}
map {
[ $_, [ grep { $_ eq $search } @{$hash->{$_}{member}} ] ]
} keys %$hash;
print 'map/grep Solution 2';
print $_->[0] for grep {
$_->[1]
}
map {
[ $_, scalar grep { $_ eq $search } @{$hash->{$_}{member}} ]
} keys %$hash;
Output:
OP Solution:
A
C
map/grep Solution 1
A
C
map/grep Solution 2
A
C
| [reply] [d/l] [select] |
Re: howto map/grep a complex structure
by eyepopslikeamosquito (Archbishop) on Aug 27, 2015 at 07:26 UTC
|
is there a tutorial to learn how to translate
this 'foreach / if' combination into 'map / grep'?
Effective Perl Programming item 20,
"Use foreach, map and grep as appropriate",
provides a nice summary of when to use foreach, map and grep:
- Use foreach to iterate read-only over each element of a list
- Use map to create a list based on the contents of another list
- Use foreach to modify elements of a list
- Use grep to select elements in a list
| [reply] [d/l] [select] |
Re: howto map/grep a complex structure
by GrandFather (Saint) on Aug 27, 2015 at 05:03 UTC
|
I'm not sure a tutorial is needed. Some common sense guidelines and an example may help:
Umm, actually that's about all you need for guidelines. Don't make whatever code you come up with too complicated to grok. That generally means avoid multiple grep and map usages in one statement. So your example could become:
for $hk (keys %$hash) {
print "$hk\n" for grep {$_ eq $search} @{$hash->{$hk}->{member}};
}
for some gain. But trying to roll the whole lot into one statement would be counterproductive.
Premature optimization is the root of all job security
| [reply] [d/l] |
Re: howto map/grep a complex structure
by 1nickt (Canon) on Aug 27, 2015 at 04:56 UTC
|
foreach my $hk ( keys %{ $href } ) {
print "$hk\n" for grep { /^$search$/ } @{ $href->{$hk}->{'member'}
+ };
}
#! perl
use strict;
use warnings;
use feature qw/ say /;
my $href = {
foo => {
member => [ 'fred', 'barney', 'wilma', 'betty' ],
},
bar => {
member => [ 'mickey', 'minnie' ],
},
};
my $search = 'wilma';
foreach my $hk ( keys %{ $href } ) {
say $hk for grep { /^$search$/ } @{ $href->{$hk}->{'member'} };
}
__END__
Output:
[21:57][nick:~/monks]$ perl 1140148.pl
foo
Update: added output
The way forward always starts with a minimal test.
| [reply] [d/l] [select] |
Re: howto map/grep a complex structure
by LanX (Saint) on Aug 29, 2015 at 13:48 UTC
|
The deeper underlying problem is that map / grep are well suited to process lists but not paired structures like hashes.
You could build your own map like operators for hashes if you really wanted. But nonstandard ops are not wildly readable.
You might try shortening your code with a while ( each ) and a grep or smartmatch ~~ if you want, but both structures are disputed.
update
depending on if you really need each submatch :
use Data::Dump;
my $hash={};
for my $hk ( 4..10 ) {
push @{$hash->{$hk}->{member}}, 1..$hk,1..$hk;
}
dd $hash;
$\="\n";
my $pat="8";
print "Test1:";
while ( my ($hk,$hv) = each %$hash ) {
print "$hk " x grep {$pat eq $_} @{$hv->{member}};
}
print "Test2";
while ( my ($hk,$hv) = each %$hash ) {
print "$hk" if $pat ~~ $hv->{member};
}
{
4 => { member => [1 .. 4, 1 .. 4] },
5 => { member => [1 .. 5, 1 .. 5] },
6 => { member => [1 .. 6, 1 .. 6] },
7 => { member => [1 .. 7, 1 .. 7] },
8 => { member => [1 .. 8, 1 .. 8] },
9 => { member => [1 .. 9, 1 .. 9] },
10 => { member => [1 .. 10, 1 .. 10] },
}
Test1:
8 8
10 10
9 9
Test2
8
10
9
| [reply] [d/l] [select] |
Re: howto map/grep a complex structure
by trippledubs (Deacon) on Aug 27, 2015 at 12:15 UTC
|
#!/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 :) | [reply] [d/l] |
|
|
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.
| [reply] [d/l] [select] |
|
|
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: <%-{-{-{-<
| [reply] [d/l] [select] |
|
|
|
|
|
|
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..
| [reply] |
Re: howto map/grep a complex structure
by Anonymous Monk on Aug 27, 2015 at 05:22 UTC
|
thank you,
because i've got two equivalent answers to this questions, which didn't come up with map, i still think it would be nice to see a solution with map / grep combination.
but maybe this example is not the best for such a solution.
your way is anyway more elegant than my neested foreach loops with an if statement.
greetings
| [reply] |
|
|
print for
grep {
grep {$search eq $_ } @{$href->{$_}->{member}}
} keys %$href;
Clarity: it's like that one thing that is not the other thing, except for when it is.
| [reply] [d/l] |
| A reply falls below the community's threshold of quality. You may see it by logging in. |