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

Hello Monks,

I have a script that reads from stdin, expecting the first line to contain column headings (delimited by commas) that will be stored in an array for use as hash keys later. Each remaining line should contain the values for an individual record (also delimited by commas). I have an array of hashes (LoH) that stores the data from each line in a record.
#!/usr/bin/perl -w use strict; # first line has column headings my @fieldnames = split /,/, <>; # @entries = ( { Last => 'zacks', First => 'evan', ... }, ... ) my @entries; # create and store a record for each line of input while (<>) { my %h; @h{@fieldnames} = split /,/; push @entries, \%h; } foreach my $h (@entries) { foreach my $k (@fieldnames) { print "$k: $h->{$k}\n" } } __DATA__ sample input: Last,First,Address,Apt,City,St,Zip,Email,Notes zacks,evan,123 main,,ann arbor,mi,48104,evan@zacks.org,perlmonks++
Given the pleasant spring weather as of late, I started thinking about golf. Then I came up with a few different ways to create and store each record. Some of mine are below. The problem is creating the mapping from the column name (from @fieldnames) to values (retrieved from split).

Does anyone have some other solutions?
# pretty much the same as above (weak, i know) while (<>) { my %h; push @entries, do { my %h; @h{@fieldnames} = split /,/; \%h }; } # different, but still using a temp var while (<>) { my @f = @fieldnames; push @entries, { map { shift @f => $_ } split /,/ }; } # hmm. gets rid of the temp var, but it's pretty ugly. while (<>) { push @entries, { map { push @fieldnames, shift @fieldnames; $fieldnames[-1] => $_ } split /,/ }; } # tye++ (see http://www.perlmonks.org/index.pl?node_id=44763) use mapcar; while (<>) { push @entries, { mapcar { @_ } \@fieldnames, [ split /,/ ] }; }
Golf (or at least other solutions), anyone?

--sacked

Replies are listed 'Best First'.
Re: golfing hash slices (Russ = 57)
by Russ (Deacon) on Apr 13, 2001 at 08:55 UTC
    This appears to work:
    @{$entries[@entries]}{@fieldnames} = split ',' while(<>);
    57 characters (not counting whitespace). :-)

    Russ
    Brainbench 'Most Valuable Professional' for Perl

      You can drop the round brackets arount the <>, as we all use the $. (input line number variable) instead of @entries:

      @{$entries[$.-2]}{@fieldnames}=split','while<>;  #47 characters

      Of course, shortening the variable names would also reduce the size of this:

      @{$e[@e]}{@f}=split','while<>;  #30 characters

      In which case it is shorter to use the @e array size rather than $.

      Ahh! That's what I couldn't see. You know how when you get stuck on something, sometimes it is difficult to approach it from other points of view? Well, that's where I was after staring at this problem for a while this afternoon. I knew I was missing something obvious. And your solution illustrates it-- autovivification. Thanks.

      --sacked
Re: golfing hash slices
by riffraff (Pilgrim) on Apr 13, 2001 at 00:27 UTC
    I did this same thing once, manipulating csv files. My program could rearrange csv columns, alter values, and more (I had an implementation in C and in perl). (When I get home I'll see if I can find the source somewhere).

    What I did was create a double-indirected list. I loaded up the values as a strict array of arrays, then had the column names hashed to the column index (0 .. #columns). If I had to rearrange the columns, I just had to switch the index numbers around. I also had an array of column names (for reverse indexing).

    lsd

Re: golfing hash slices
by petral (Curate) on Apr 13, 2001 at 08:57 UTC
    Not golf exactly, but clean: you might want to look at ixhash.

    p
Re: golfing hash slices
by premchai21 (Curate) on Apr 13, 2001 at 02:17 UTC
    No shorter than tye's solution, but an alternative would be:
    {my$i;push@entries ,{map{$fieldnames[ $i++%@fieldnames]=> $_}split/,/}while( <>);}