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

Sometimes, I realize that I'm chasing my own tail... but I just can't stop myself. :) I'm hoping my fellow Monks will help me with this particular chase; it's not all that important, but it's driving me nuts.

I have an array (returned by a dereferenced "selectcol_arrayref" from DBI::mysql, but that's not important) that consists of a set of triples - one "key" item and two "values". What I want to do is to use the key as a key in a hash, and the two other items as elements in an arrayref which will be the value. What I'm doing right now is

# @ref is the returned array for ( 0 .. $#ref / 3 ){ my $m = $_ * 3; $out{$ref[$m]} = [ @ref[$m+1, $m+2] ]; }

but I can't get rid of the feeling that there's a prettier, more elegant, more *Perlish* way to do it. Even more, it's not an unusual requirement in programming, and it just irks me that my brain is refusing to give up the answer.

So, fellow hackers - what's your answer? I'd really appreciate someone else's input on this, since my own creative juices seem to be exhausted here.

Replies are listed 'Best First'.
Re: Elegance, dammit!
by Util (Priest) on Jun 16, 2007 at 00:45 UTC

    In real life, I would normally use a splice like Errto and jdporter.

    Before *any* solution, I would put:

    die if @ref / 3;

    Most elegant, non-destructive Perl5 solution I could think of:

    my %out = map { $ref[$_] => [ @ref[$_+1..$_+2] ] } grep { not $_ % 3 } 0 .. $#ref;

    Even better, but requires Perl6:

    for @ref -> $key, $val1, $val2 { %out{$key} = [ $val1, $val2 ]; }

    Update: Better yet, this also works in Perl6:

    my %out = map -> $k, $v, $w { $k => [ $v, $w ]; }, @ref;

      Ah-HAH! :)

      (I *really* liked that Perl6 solution, BTW; I see that I'm going to have to get off my lazy butt and dig into it.)

      All of that - particularly Util's solution - has finally caused a reticent neuron to fire. Originally, I thought of this:

      $out{shift @ref} = [splice @ref,0,2] while @ref;

      ...but there's a precedence problem with that, so that %out ends up containing stuff like

      2 => [ 0, 1 ], 5 => [ 3, 4 ] etc.

      so my final cut at it ends up being this:

      die if @ref % 3; $out{$a} = [splice @ref,0,2] while $a = shift @ref;

      I'm definitely up for seeing more solutions, though. You folks come up with some very pretty thought patterns. :)

      Thank you all!

      I can't resist offering a shorter Perl 6-solution:

      my %out = map {$^k => [$^v, $^w]}, @ref;

      The ^ secondary sigil ("twigil") is a self declaring formal paramter, all of the variables starting with $^ will be filled in the block in lexicographic order.

      die if @ref / 3;

      I really like the idea of an assertion (especially during debugging), but I'd probably write it this way:

      die 'Assertion failed: @ref not divisible by 3' if scalar @ref % 3;

      If I'm not mistaken, yours dies every time @ref has any elements.

Re: Elegance, dammit!
by Errto (Vicar) on Jun 15, 2007 at 23:54 UTC
    first pass
    while (@array) { my ($key, @vals) = splice @array, 0, 3; $hash{$key} = [ @vals ]; }
    assuming you no longer need the original array once you're done.
Re: Elegance, dammit!
by jdporter (Paladin) on Jun 15, 2007 at 23:54 UTC
    one way:
    my @destructible = @$ref; while ( @destructible ) { my( $key, @val ) = splice @destructible, 0, 3; $out{ $key } = \@val; }
    A word spoken in Mind will reach its own level, in the objective world, by its own weight
Re: Elegance, dammit!
by un-chomp (Scribe) on Jun 16, 2007 at 00:29 UTC
    Non-destructively:
    my %hoa = map { $array[$_], [ @array[ $_+1 .. $_+2 ] ] } grep { !($_ % 3) } 0 .. $#array;
Re: Elegance, dammit!
by BrowserUk (Patriarch) on Jun 16, 2007 at 04:05 UTC

    Elegant? Fun!

    my @ref = map{ ("key$_", "value1_$_", "value2_$_") } 0 .. 9;; my $i=0; $h{shift @$_}=$_ while $i < @ref and $_=[@ref[$i++,$i++,$i++] +];; print "$_ => @{$h{ $_ }}" for sort keys %h;; key0 => value1_0 value2_0 key1 => value1_1 value2_1 key2 => value1_2 value2_2 key3 => value1_3 value2_3 key4 => value1_4 value2_4 key5 => value1_5 value2_5 key6 => value1_6 value2_6 key7 => value1_7 value2_7 key8 => value1_8 value2_8 key9 => value1_9 value2_9

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Elegance, dammit!
by ysth (Canon) on Jun 16, 2007 at 02:50 UTC
    Use selectall_hashref instead, mapping the resulting HoH into a HoA if you really truly must.

      I'm afraid that doesn't work for me, ysth. 'selectall' rather than 'selectcol' is the deal-killer here (I don't see the need for fetching the entire content of that table, extracting the columns, and then doing all this processing.)

      This gets me exactly the info I need - although the structure is a little different from what I want:

      my @ref = @{ $dbh->selectcol_arrayref( "select date, label, subject from travel where date>= curdate()-5 order by date", {Columns=>[1,2,3]} ) };

      I do indeed appreciate your attempt to approach it from a broader viewpoint - I try not to lose track of the fact that stepping back can be as important as focusing down when trying to solve a problem. [Grin] Hell, my wife is Japanese-American. She'd beat my butt if I was ever so silly as to lose track of context.

        I'm not understanding you at all. There's either something I'm missing or something you are. selectcol_arrayref and selectall_hashref will both read all the available rows. Yes, selectcol_arrayref by default throws away all but the first column, but in your invokation, you are telling it to keep all three columns.
Re: Elegance, dammit!
by sfink (Deacon) on Jun 16, 2007 at 21:10 UTC
    Purely for completeness.

    Option 1:

    while (@ref) { my $k = shift(@ref); $out{$k} = [ shift(@ref), shift(@ref) ]; }
    or if the temporary bothers you:
    while (@ref) { $out{$_} = [ shift(@ref), shift(@ref) ] for shift(@ref); }

    Option 2: Change your SQL to something like SELECT key, value1, key, value2, then do:

    push @{ $out{shift(@ref)} }, shift(@ref) while (@ref);
    Personally, I'd stick with your original.