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?

\$\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\$ Sep 9, 2019 at 12:22
  • \$\begingroup\$ It is exactly extract_strings in my edit :) \$\endgroup\$ 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\$ 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\$
    – t3chb0t
    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] } ]
}
\$\endgroup\$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.