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\$
  • 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\$ – Håkon Hægland Sep 9 at 12:22
  • \$\begingroup\$ It is exactly extract_strings in my edit :) \$\endgroup\$ – Miroslav Popov Sep 9 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\$ – Miroslav Popov Sep 9 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 at 7:11
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, privacy policy and cookie policy

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