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

Dear Monks

Normally I wouldn't have the problem shown below, but for some reason I like one-liners. Why ? I don't know, maybe because they look cool and make my code unreadable :)

Anyway, what I'm trying to accomplish this time is that I would like to treat an array as if it was a hash. Each array element has a string containing two words (the key and the value)
#! /usr/bin/perl -lw # use strict ; my $key = "b" ; printf "key=%s, value is %s\n", $key, get_value($key) ; sub get_value { my $key = shift ; my @ta = ("a x", "b y", "c z") ; # test array return (map{ /^$key/ and s/^$key//, $_ } grep( /^$key/, @ta))[1] ; }
This works great, but for some reason I don't think I've the best solution, because I have to add (...)[1], around the one-liner in order to get the 'value', which doesn't feel right. Element [0] contains a 1.

Is this the only way, or do I something wrong here ?
What I don't understand too is that when I replace the map expression with
map{ /^$key/ and s/^$key// and print "val=$_\n", $_ }
it doesn't work verywell anymore, I only added a print statement ?

Thnx
LuCa

Replies are listed 'Best First'.
Re: map and grep one-liner problem
by johngg (Canon) on Mar 14, 2007 at 11:45 UTC
    I don't think using /^$key/ is a good idea because you want an exact match so that a key of "b" doesn't find an entry of "bbking" => "legend". A test for string equality would be safer.

    use strict; use warnings; my @ta = (q{a x}, q{b y}, q{c z}); my $key = q{b}; print qq{key = $key, value is @{[getValue($key)]}\n}; sub getValue { my $key = shift; return map { $_->[1] } grep { $_->[0] eq $key } map { [ split ] } @ta; }

    The output is

    key = b, value is y

    I hope this is of use.

    Cheers,

    JohnGG

    Update: Thinking about it, you could do most of the work inside the print statement, getting rid of the subroutine

    use strict; use warnings; my @ta = (q{a x}, q{b y}, q{c z}); my $key = q{b}; print qq{key = $key, value is @{ [ map { $_->[1] } grep { $_->[0] eq $key } map { [ split ] } @ta ] }\n};

    This produces the same output as the first version.

Re: map and grep one-liner problem
by shmem (Chancellor) on Mar 14, 2007 at 12:04 UTC
    return (map{ /^$key/ and s/^$key//, $_ } grep( /^$key/, @ta))[1] ;

    Are you sure you want the comma operator inside the map? Try

    map{ /^$key/ and s/^$key//; $_ } grep( /^$key/, @ta)

    The s/// inside the map evaluates to 1.

    What I don't understand too is that when I replace the map expression with
    map{ /^$key/ and s/^$key// and print "val=$_\n", $_ }
    it doesn't work verywell anymore, I only added a print statement ?

    Same here. print "val=$_\n", $_ prints $_ twice, and map does return 1 (the result of print, if successful).

    --shmem

    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
Re: map and grep one-liner problem
by Anno (Deacon) on Mar 14, 2007 at 12:45 UTC
    Your specification is incomplete. What result do you want if the same key appears multiple times in your array? That is something that can't happen with a hash, but it can with your list-as-a-hash.

    The complete result of a query for some key would have to be a list of matches. Using map() to generate such a result is reasonable, but let's look at your map again:

    map{ /^$key/ and s/^$key//, $_ } grep( /^$key/, @ta))
    For one, why do you check for the key with grep() and then again in the map block? One of the tests can go.

    Secondly, you are using s/// to change the given array element into just the value. Apart from being ugly, this changes your array @ta, so it wouldn't work again the next time. Print @ta after your code has run to see what I mean. Simply extract the value using regex capture.

    Third (as has been noted), you want a semicolon instead of comma to separate the statements in the map block. The map block is evaluated in list context, so the comma is a list operator and map will collect both values. That isn't what you want, and it's the reason you need to select the second element from the result ((...)[1]).

    Here is one way to do what you want:

    my @ta = ("a x", "b y", "c z", 'b yyy'); my $key = 'b'; my @values = map / (.*)/, grep /^$key /, @ta; print "@values\n";
    If you only want the first hit, write
    my ( $value) = map ...;
    Anno
Re: map and grep one-liner problem
by Not_a_Number (Prior) on Mar 14, 2007 at 13:55 UTC

    TIMTOWDI (I had some time to kill :-)

    I assume your 'keys' are unique.*

    my @ta = ( 'a x', 'b y', 'c z' ); my $key = 'b'; printf "key=%s, value=%s\n", map split, grep { ! index $_, $key . ' ' +} @ta; printf "key=%s, value=%s\n", $key, ${{ map split, @ta }}{$key};

    * If not, the first snippet will select the first hit, while the second will give you the last hit.

    Update: I could of course have simplified the second snippet further:

    printf "key=%s, value=%s\n", $key, { map split, @ta }->{$key};
      This is really getting close to unreadability :)
      There is one thing in the first printf that I don't understand:  index $_, $key . ' '
      What exactly does the last part do ( . ' ' ), adding a space ? why ?

      LuCa
        What exactly does the last part do ( . ' ' ), adding a space ?

        Yes, that's exactly what it does. Otherwise, as johngg points out above, a search for 'b' will match not just 'b', but also, for example, 'bbking'. And if you look closely at this line in Anno's solution:

        my @values = map / (.*)/, grep /^$key /, @ta;

        you'll see that he does the same thing (note the space after ^$key ?)

Re: map and grep one-liner problem
by jeanluca (Deacon) on Mar 14, 2007 at 13:13 UTC
    Thnx for revealing a little bit more of the map magic :-)

    LuCa