Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

Returning two lists from a sub

by cormanaz (Deacon)
on Jun 07, 2007 at 13:14 UTC ( [id://619803]=perlquestion: print w/replies, xml ) Need Help??

cormanaz has asked for the wisdom of the Perl Monks concerning the following question:

Monastic Ones,

In this example

#!/usr/bin/perl -w use strict; my (@a,@b) = getlists(); print join(" ",@a),"\n",join(" ",@b); sub getlists { my @foo = (1,2,3); my @bar = (4,5,6); return @foo,@bar; }
the contents of @foo and @bar are contatenated and returned as the content of @a, while @b is null. How do I return them as separete lists? I realize I could send them by reference as arguments to the sub, but can I do it without that?

Thx...Steve

Replies are listed 'Best First'.
Re: Returning two lists from a sub
by philcrow (Priest) on Jun 07, 2007 at 13:23 UTC
    You need to use references to avoid the flattening you noticed:
    my ($a, $b) = getarrayrefs(); print join(" ", @{ $a }), "\n", join(" ", @{ $b }); sub getarrayrefs { my @foo = (1,2,3); my @bar = (4,5,6); return \@foo, \@bar; }
    Phil

    Update: Corrected sub name to match call. Thanks Albannach.

    The Gantry Web Framework Book is now available.
Re: Returning two lists from a sub
by johngg (Canon) on Jun 07, 2007 at 13:25 UTC
    You need to return two array references from the subroutine.

    use strict; use warnings; my ($ref_to_a, $ref_to_b) = getlists(); print qq{@$ref_to_a\n@$ref_to_b\n}; sub getlists { my @foo = (1,2,3); my @bar = (4,5,6); return \@foo, \@bar; }

    BTW, interpolating an array inside double quotes will, by default, space-separate the elements; you don't need the join.

    Cheers,

    JohnGG

Re: Returning two lists from a sub
by ikegami (Patriarch) on Jun 07, 2007 at 14:43 UTC
    An alternative is to pass references to the arrays to the function:
    getlists(\@a, \@b); print join(" ",@a),"\n",join(" ",@b); sub getlists { my @foo = (1,2,3); my @bar = (4,5,6); @{$_[0]} = @foo; @{$_[1]} = @bar; }

    or

    getlists(\@a, \@b); print join(" ",@a),"\n",join(" ",@b); sub getlists { my ($foo, $bar) = @_; @$foo = (1,2,3); @$bar = (4,5,6); }
      Didn't the OP say that he wants to avoid that?
Re: Returning two lists from a sub
by cdarke (Prior) on Jun 07, 2007 at 13:56 UTC
    Using references is The Right Way To Do It but there's always another (horrible) way.
    use warnings; use strict; my ($len, @temp) = getlists(); my @a = @temp[0..$len-1]; my @b = @temp[$len..@temp-1]; undef @temp; print "@a\n@b\n"; sub getlists { my @foo = (1,2,3); my @bar = (4,5,6); return scalar(@foo), @foo, @bar; }

      The calling code can be simplified some:

      my ($len, @b) = getlists(); my @a = splice(@b, 0, $len);

      Of course, it's still horrible.

Re: Returning two lists from a sub
by Sidhekin (Priest) on Jun 07, 2007 at 14:50 UTC

    There is just one return list.

    In the spirit of horrible solutions, you could pass the second one out-of-band. Right. But there's no sane reason to do that, is there?

    So, in the spirit of insane solutions, have one more:

    use strict; use warnings; sub getlists(\@\@); getlists( my @a, my @b ); print join(" ",@a),"\n",join(" ",@b); sub getlists(\@\@) { my ($foo, $bar) = @_; @$foo = (1,2,3); @$bar = (4,5,6); }

    Most horrible yet? ;-)

    print "Just another Perl ${\(trickster and hacker)},"
    The Sidhekin proves Sidhe did it!

Re: Returning two lists from a sub
by otto (Beadle) on Jun 07, 2007 at 16:02 UTC

    First you need to remember the "limits" of perl:

    As found at perlsub optionally specifying the returned value, which will be evaluated in the appropriate context (list, scalar, or void) depending on the context of the subroutine call. If you specify no return value, the subroutine returns an empty list in list context, the undefined value in scalar context, or nothing in void context. If you return one or more aggregates (arrays and hashes), these will be flattened together into one large indistinguishable list.

    This is not as "limiting" as one might expect.

    There are several ways of passing back. You are fundamentally limited to returning values (1) as a return value or (2) via changed parameters to the sub. (Ok a third way is via a out of bounds global thingy.)

    Within both of these, there are several ways to do it, and the "best" is really context dependent.

    If you are looking for a return value, you could return a scalar, which is a array ref which has array ref elements to each of your lists, or you can return a list, which has array ref elements to each of your lists.

    Whether you use 1 or 2 as your mechanism should really depend upon how you want the rest of your functions to appear. If you're modifying existing code, which technique is domoniate - use that.

    Tis good to remember that this provides far greater functionality than the simple c-language return value :)

    ..Otto

      Why do you need a return value? Is there a specific reason you can't do this? my (@a, @b); &get_lists; print join(" ",@a),"\n",join(" ",@b); sub get_lists{ my @a = (1,2,3); my @b = (4,5,6); }

        ...other than the fact that it does not work...

        Your my in the sub hides the "outer" @a and @b.

        I did mention your technique as (Ok a third way is via a out of bounds global thingy.)

Re: Returning two lists from a sub
by Argel (Prior) on Jun 08, 2007 at 00:13 UTC
    Assuming the real goal is to have actual arrays to work with you could return the array references and then convert them into arrays (lines 4 and 5):
    #!/usr/bin/perl use strict; use warnings; my ($foo_aref,$bar_aref) = getlists(); my @a = @{ $foo_aref }; my @b = @{ $bar_aref }; print "@a @b\n"; sub getlists { my @foo = (1,2,3); my @bar = (4,5,6); return \@foo,\@bar; }
Re: Returning two lists from a sub
by moritz (Cardinal) on Jun 07, 2007 at 13:53 UTC
    I don't know how to do it without references, I just wanted to add that in Perl 6 you can achieve just that in "slice" context.

    So my (@a, @b) = my_sub() will force slice context to my_sub, so if sub my_sub { return @a, @b; } it will not be flattened automatically.

    I've just been corrected that my (@a, @b) = my_function() would only fill @a, so the right way to do it would either be binding or my ($a, $b) = slice my_function(), but you can use $a like a real array (and not just an array ref) in Perl 6

    You can explicitly enforce slice contect with slice or with @@.

    One more reason to look forward to Perl 6 ;-)

Re: Returning two lists from a sub
by jynx (Priest) on Jun 07, 2007 at 17:45 UTC

    I'm not surprised no one has mentioned this one. This is pretty horrible as well. Make the 2 lists have the same size, and then return a hash where the keys are one list and the values are another. Then you can extract both lists from the single list using perl built-ins. I would advise not doing it this way. But just for fun, untested code ahead...

    sub foo { my ($tmp, @list1, @list2, %vals) = ('aaaaa'); @list1 = generate_list1(); @list2 = generate_list2(); if (@list1 > @list2) { push @list2, $tmp++ for 1..@list1-@list2; } else { push @list1, $tmp++ for 1..@list2-@list1; } @vals{@list1} = @list2; return %vals; } my %tmp = foo(); my @list1 = sort grep !/^aaa/, keys %tmp; my @list2 = sort grep !/^aaa/, values %tmp;

      This method would fail if the list used as 'keys' has any duplicate items in it.

        That's one of many problems with it, which is why I did advise not to use it. I just put it here in the spirit of TMTOWTDI and for fun. It's not supposed to be a "serious" way to do it...

        This was a bad time for a joke apparently... sorry about that.

Re: Returning two lists from a sub
by Anonymous Monk on Jun 07, 2007 at 15:47 UTC
    #!/usr/bin/perl -w
    use strict;
    
    # This won't work:
    my (@a,@b) = getlists_1();
    
    print "getlists_1: A ( @a ) and B ( @b ) \n";
    
    # Instead, pass by reference:
    (*a,*b) = getlists_2(\@a, \@b);
    
    print "getlists_2: A ( @a ) and B ( @b ) \n";
    
    # ------------------------------------------------------------------
    # From 'man perlsub':
    # If you return one or more aggregates (arrays and hashes), these will be
    # flattened together into one large indistinguishable list.
    # ------------------------------------------------------------------
    sub getlists_1 {
        my @foo = (1,2,3);
        my @bar = (4,5,6);
        return @foo,@bar;
    }
    
    
    sub getlists_2 {
        my ($foo, $bar) = @_;
        @$foo = (1,2,3);
        @$bar = (4,5,6);
        return ($foo,$bar);
    }
    

      The trick of assigning to the two strict-proof typeglobs is cute, but I can't really recommend it seriously.

Re: Returning two lists from a sub
by princepawn (Parson) on Jun 07, 2007 at 13:25 UTC
    I dont think you can do without that cormanaz ... It is rather less self-documenting when you get back 2 scalars whose contents is actually referring to arrays, so I can see why you would want to get back arrays instead of scalars.

    But I know of no way to get what you want.


    Carter's compass: I know I'm on the right track when by deleting something, I'm adding functionality
Re: Returning two lists from a sub
by 13warrior (Acolyte) on Jun 07, 2007 at 15:30 UTC
    in the case of storing the return values in arrays from function calls we must always use the array variable at the end as the return concatenates the array and sends it so it gets stored in the single array @a, To get over it the best way would be reference and the optimal way as pass by value and pass by reference
Re: Returning two lists from a sub
by Anonymous Monk on Jun 08, 2007 at 13:13 UTC
    I'd create them as anonymous arrays in the sub:

    my $foo = [1,2,3]; my $bar = [4,5,6]; Then return the scalars. My rewrite: #!/usr/bin/perl use strict; my ($a, $b) = getlists(); print join(" ", @$a), "\n", join(" ", @$b), "\n"; sub getlists { my $foo = [1,2,3]; my $bar = [4,5,6]; return $foo,$bar; } I'm sure "There's More Than One Way To Do It", like usual, but this is + my way. Cheers! Howard
Re: Returning two lists from a sub
by Anonymous Monk on Jun 08, 2007 at 19:18 UTC
    @m = list(); $l = shift @m; # $l = $#foo+1 @z = @m[0..$l-1]; # @z = @foo @e = @m[$l..$#m]; # @e = @bar sub list { @bar=('vodka', 'tulips', 'demons'); @foo=('dicnk', 'dsiknvxdx', 'DAEMON!'); return scalar(@foo), @foo, @bar; }

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://619803]
Approved by Corion
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others browsing the Monastery: (8)
As of 2024-03-28 11:12 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found