Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

Read File In Four-Line Chunks / TMTOWTDI / Golf

by Cody Pendant (Prior)
on Jun 06, 2003 at 06:52 UTC ( [id://263605]=perlmeditation: print w/replies, xml ) Need Help??

I asked this question on the chatterbox, and it provoked a flurry of interesting responses -- seven or eight different ones from tye alone! -- so I thought I might post it and see what monks came up with.

Problem -- I have a file. I want to read it in four-line chunks, because it goes

name1 address1 phone1 fax1 name2 address2 phone2 fax2
and so on.

Solution? My first one was to slurp and then do like this:

for($i=0;$<scalar(@slurpedarray);$i+=4){ # work with $slurpedarray[$i], $slurpedarray[($i+1)], # $slurpedarray[($i+2)] and $slurpedarray[($i+2)] }
which makes me wince to look at it.

Now I'm going to do it with the much more Perlish

while(@slurpedarray){ ($name,$address,$phone,$fax) = splice(@slurpedarray,0,3) # work with nice named vars }

Interesting bit: all these solutions are based on whole-file slurpage (fine for me, very small files) but the interesting solutions were the ones which didn't do that, the ones for actually reading the file itself.

Any interesting solutions? (Stands back...)
--

“Every bit of code is either naturally related to the problem at hand, or else it's an accidental side effect of the fact that you happened to solve the problem using a digital computer.”
M-J D

Replies are listed 'Best First'.
Re: Read File In Four-Line Chunks / TMTOWTDI / Golf
by larsen (Parson) on Jun 06, 2003 at 08:14 UTC
    According to me, the simplest way is to abstract the operation of "reading a chunk of data" in a procedure, like in this example:
    use strict; use warnings; while ( my @s = read_chunk() ) { print join " | ", @s; print "\n"; } sub read_chunk { my @stuff = (); while( my $line = <DATA> ) { chomp $line; push @stuff, $line; last if $. % 4 == 0; } return @stuff; } __DATA__ first1 second1 third1 fourth1 first2 second2 third2 fourth2 first3 second3
    So you can treat a read_chunk() operation as an atomic one.

    A more clever option you could explore is using Tie::File, which allows you to manipulate the file as an array of lines, even if simgle lines are actually read only when they're needed (details on the documentation).

Re: Read File In Four-Line Chunks / TMTOWTDI / Golf
by Aristotle (Chancellor) on Jun 06, 2003 at 11:45 UTC
    My first thought:
    #!/usr/bin/perl -w use strict; open my $fh, '<', 'details.txt' or die $!; until(grep !defined, my @details = map scalar <$fh>, 1 .. 4) { print @details, "\n"; }
    A C programmer's well trained reflex:
    #!/usr/bin/perl -w use strict; open my $fh, '<', 'details.txt' or die $!; my $i; my @details; while(<$fh>) { push @details, $_; next if ++$i % 4; print @details, "\n"; @details = (); }
    A LISP hacker's immediate reaction:
    #!/usr/bin/perl -w use strict; open my $fh, '<', 'details.txt' or die $!; sub read_lines { my ($fh, $amnt) = @_; return unless defined(my $line = <$fh>); return ( $line, $amnt > 1 ? read_lines($fh, $amnt - 1) : () ); } while(my @details = read_lines($fh, 4)) { print @details, "\n"; }

    Makeshifts last the longest.

      Some further perversions of the LISPish way:
      #!/usr/bin/perl -w use strict; open my $fh, '<', 'details.txt' or die $!; my $read_lines; while( my @details = ($read_lines = sub { my $amnt = shift; return unless defined(my $line = <$fh>); return ( $line, $amnt > 1 ? $read_lines->($amnt - 1) : () ); })->(4) ) { print @details, "\n"; }
      Refined perversion:
      #!/usr/bin/perl -w use strict; sub make_read_lines { my ($fh, $lines) = @_; return $lines == 1 ? sub { return unless defined(my $line = <$fh>); $line } : do { my $next_lines = make_read_lines($fh, $lines - 1); sub { return unless defined(my $line = <$fh>); $line, $next_lines->() }; }; } open my $fh, '<', 'details.txt' or die $!; my $read_4_lines = make_read_lines($fh, 4); while(my @details = $read_4_lines->()) { print @details, "\n"; }

      Makeshifts last the longest.

Re: Read File In Four-Line Chunks / TMTOWTDI / Golf
by tilly (Archbishop) on Jun 06, 2003 at 08:40 UTC
    Untested code which should not be sprung on unwary maintainance programmers:
    while(grep {$_=<FH>; chop} my($name,$address,$phone,$fax) ) { # Do stuff }
    (No Perl here, I can't test it. And I will hang my head in shame if it doesn't work...)
      Cute (though no Perl here either), but you can chomp and it'll work the same, while that chop is dangerous.

      Makeshifts last the longest.

      Nice. You can prevent it from dealing with partial records at the end of the input too...

      while ( 4 == scalar grep {$_ = <FH>; chomp} my($name,$address,$phone,$ +fax) ) { # Do stuff }

      Update: See Aristotle's comments below. This will fail to process a four line record if the last record is missing an EOL. Also, using an explicit scalar is superfluous... and if you are already torturing your maintenance programmer with this construct, why be explicit? :-)

      -sauoq
      "My two cents aren't worth a dime.";
      
        Careful - if the last line of the file has no newline, chomp will return 0, causing grep to return 3, rather than 4, causing the while to terminate early even though the record is complete. Also, you don't need the explicit scalar - == provides scalar context for you (case in point: @a == @b checks for identical array size).
        while(4 == grep { chomp($_ = <FH>); defined } my($name,$address,$phone +,$fax)){ # Do stuff }

        Makeshifts last the longest.

Re: Read File In Four-Line Chunks / TMTOWTDI / Golf
by Lachesis (Friar) on Jun 06, 2003 at 08:05 UTC
    Not a big change but you can do
    while (my ($name,$address,$phone,$fax) = splice(@slurpedarray,0,4)){ #work with named vars }
    The last splice argument there should be 4 since its a length not an index.

      Instead of slurping everything at once, just read the handle four times. Then you can deal with multi-gigabyte phonebooks.

      while (my ($name,$address,$phone,$fax) = (<FILE>, <FILE>, <FILE>, <FIL +E>)) { #work with named vars (don't forget chomp) }

      Update: just did a little test, and it does one iteration and stops. Hm.

      --
      [ e d @ h a l l e y . c c ]

        You're using <FH> in a list context so it would read in the entire file in the first iteration
        Update: apologies I'm speaking rubbish there. I did a quick test my self and it picks up the data you would expect but only does iterate the once.
Re: Read File In Four-Line Chunks / TMTOWTDI / Golf
by BrowserUk (Patriarch) on Jun 06, 2003 at 10:32 UTC

    #! perl -slw use strict; require 5.008; use constant { NAME=>0, ADDR=>1, PHONE=>2, FAX=>3 }; open my $fh, '<', 'details.txt' or die $!; while( !eof($fh) ){ my @details = map{ scalar <$fh> } 1..4; chomp @details; print "@details[ NAME, ADDR, PHONE, FAX ]"; }

    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "When I'm working on a problem, I never think about beauty. I think only how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong." -Richard Buckminster Fuller


(jeffa) Re: Read File In Four-Line Chunks / TMTOWTDI / Golf
by jeffa (Bishop) on Jun 06, 2003 at 15:23 UTC
    Here's my take it at:
    use strict; use warnings; use Tie::File; use Data::Dumper; my @file; my @key = qw(name address phone fax); tie @file, 'Tie::File', 'address.txt' or die $!; my @record = map { my %hash; @hash{@key} = @file[$_..$_+4-1]; \%hash; } range(0,$#file,4); print Dumper \@record; sub range {grep!(($_-$_[0])%($_[2]||1)),$_[0]..$_[1]}

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    
Re: Read File In Four-Line Chunks / TMTOWTDI / Golf
by trs80 (Priest) on Jun 06, 2003 at 18:35 UTC
    UPDATE - first post didn't keep them in named variables
    use strict; use Data::Dumper; my @four; my @all; my $count = 0; while (<DATA>) { s/\s+$//; push @four , $_; $count++; if ( ($count % 4) == 0) { my %hash; @hash{'name','address','phone','fax'} = @four; push @all , \%hash; @four = (); } } print Dumper(\@all); __DATA__ name1 address1 phone1 fax1 name2 address2 phone2 fax2
    original post below
    use strict; use Data::Dumper; my @four; my @all; my $count = 0; while (<DATA>) { s/\s+$//; push @four , $_; $count++; if ( ($count % 4) == 0) { push @all , [ @four ]; @four = (); } } print Dumper(\@all); __DATA__ name1 address1 phone1 fax1 name2 address2 phone2 fax2

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://263605]
Approved by Tanalis
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others chilling in the Monastery: (4)
As of 2024-04-10 05:56 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found