6
\$\begingroup\$

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?

t3chb0t
44.6k9 gold badges84 silver badges190 bronze badges
asked Sep 9, 2019 at 10:00
\$\endgroup\$
4
  • 2
    \$\begingroup\$ Looks fine to me :) The name of the sub, flatten might be improved to extract_strings? If wanted, speed could be improved by using Inline::C or XS. \$\endgroup\$ Commented Sep 9, 2019 at 12:22
  • \$\begingroup\$ It is exactly extract_strings in my edit :) \$\endgroup\$ Commented Sep 9, 2019 at 20:14
  • 1
    \$\begingroup\$ Actually, there is something that might be a bug: push(@output, $val) if /^[a-zA-Z]/; vs push(@output, $val) if $val =~ /^[a-zA-Z]/; I don't understand why this actually work. What is $_ in the else? I'll edit the function. \$\endgroup\$ Commented Sep 9, 2019 at 20:24
  • 3
    \$\begingroup\$ It is not allowed to modify the code when answers have been posted. It can invalidate them. I have rolled back your last edit. \$\endgroup\$ Commented Sep 10, 2019 at 7:11

1 Answer 1

5
\$\begingroup\$

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] } ]
}
answered Sep 9, 2019 at 22:28
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.