Extract the strings from nested arrays in Perl.
It prints: a, b, c, d, E
use strict;
use warnings;
use feature qw(say signatures current_sub);
no warnings qw(experimental::signatures);
my $nested = [1, 'a', [2, 3, 'b'], [4, [5, 6, 7, ['c']], [7, [8, 9, [10, 'd']]], 11, 'E']];
print join ', ', @{ extract_strings($nested) };
sub extract_strings($input) {
my @output = ();
my $loop = sub ($val) {
if (ref $val eq 'ARRAY') {
__SUB__->($_) for @{ $val };
} else {
push(@output, $val) if $val =~ /^[a-zA-Z]/;
}
};
$loop->($input);
return \@output;
}
Any idea for improvement without not core dependencies?
1 Answer 1
Your concept of "string" seems incomplete. I would look to firm up that definition and precisely match the need. Is ""
a string? Do you want "valid identifiers" (/^[^\d\W]\w+$/
) or "plausible ASCII words" (/^[A-Za-z]+$/
) or just "not numbers" (!/ ^ ( [+-]? \d* \.? \d+ (?:[Ee][+-]?\d+)? ) $/x
)?
I like use warnings FATAL => 'all';
so that I don't miss a warning in the midst of other output.
A fat arrow between arguments of different purpose can enhance readability.
Coding in functional style (with grep
and map
instead of @output
) is a natural fit to this kind of problem.
Plain old recursion is suited to the task and would obviate the need for experimental features.
Having extract_strings
take a ref (as it does now) and return a list simplifies the logic even further.
print join ', ' => extract_strings($nested);
sub extract_strings {
grep /^[a-zA-Z]/ => map { ref eq 'ARRAY' ? extract_strings($_) : $_ } @{ $_[0] }
}
If returning a ref is necessary, wrap and unwrap accordingly:
sub extract_strings {
[ grep /^[a-zA-Z]/ => map { ref eq 'ARRAY' ? @{ extract_strings($_) } : $_ } @{ $_[0] } ]
}
flatten
might be improved toextract_strings
? If wanted, speed could be improved by usingInline::C
or XS. \$\endgroup\$push(@output, $val) if /^[a-zA-Z]/;
vspush(@output, $val) if $val =~ /^[a-zA-Z]/;
I don't understand why this actually work. What is$_
in theelse
? I'll edit the function. \$\endgroup\$