Re: read a file in both directions
by davido (Cardinal) on May 31, 2006 at 09:42 UTC
|
Why does this have to be done in a single pass? Reading a file backwards is sufficiently awkward that it pays to use a module such as File::ReadBackwards to handle the ugly work for you. I would probably do it something like this:
use strict;
use warnings;
use File::ReadBackwards;
open my $forward, '<', 'filename.txt' or die $!;
while( my $line = <$forward> ) {
last if $line eq "\n";
}
my $blank_position = tell( $forward );
close $forward;
my $bw = File::ReadBackwards->new( 'filename.txt' )
or die "can't read 'filename.txt' $!" ;
while( defined( my $bw_line = $bw->readline ) ) {
last if $bw->tell() <= $blank_position;
print $bw_line;
}
This is untested, but should give you the general framework.
| [reply] [d/l] |
|
|
| [reply] |
Re: read a file in both directions
by davorg (Chancellor) on May 31, 2006 at 09:35 UTC
|
A file handle only remembers one current position. If you want to deal with two then you'll need to store that information yourself. I suggest two variables to store the current forward and backward positions. tell will get you the data to store in those variables and seek will let you move between the two positions.
--
< http://dave.org.uk>
"The first rule of Perl club is you do not talk about
Perl club." -- Chip Salzenberg
| [reply] |
Re: read a file in both directions
by dynamo (Chaplain) on May 31, 2006 at 11:37 UTC
|
Well, the first part is easy:
open FILE, "path/to/file";
local $/ = "\n\n";
my $first_part = <FILE>;
That would work great if you only needed the first part. But since you want the second part also, unless you are working with HUGE files, I think it's better (more efficient) to read it into memory and then play with it.
open FILE, "path/to/file";
local $/ = undef;
my $whole_file = <FILE>;
my $first_blank_pos = index($whole_file,"\n\n")
die "no blank lines"
unless ($first_blank >= 0)
my $first_part =
substr $whole_file, 0, $first_blank_pos;
my $second_part =
reverse substr $whole_file, $first_blank_pos;
That will do it all in memory. Do what you will with the parts after that.
- d | [reply] [d/l] [select] |
|
|
die "no blank lines"
unless ($first_blank > 0)
should have been:
die "no blank lines"
unless ($first_blank >= 0)
| [reply] [d/l] [select] |
Re: read a file in both directions
by punkish (Priest) on May 31, 2006 at 14:14 UTC
|
This is a great example of "incomplete specifications."
- Is the file only "text" file?
- Are there more blank lines after the "first" blank line?
- Is the file small enough that available memory is not going to be an issue?
Knowing answers to the above question would allow creating a suitable, fast, and reliable method for doing the job.
Assuming "yes" to all three #1 and #3, and "no" to #2 of the above questions, reading the entire file in an array and then reversing the array, and reading it till one reaches the blank line would accomplish the job in a "single pass."
# from the venerable cookbook, recipe 8.4
@lines = reverse <FILE>;
foreach $line (@lines) {
# do something with $line
# until blank line, which happens to be
# the first blank line from top
}
--
when small people start casting long shadows, it is time to go to bed
| [reply] [d/l] |
Re: read a file in both directions
by herby1620 (Monk) on May 31, 2006 at 17:10 UTC
|
Well, let's see. You read the 9 track tape in the forward direction till you get a record of EBCDIC blanks, then skip to the end of file and do a read backwards (the operation does exist!) on the tape back to the original record. :-) :-)
Sorry, that was with record oriented files. Now days most file systems are continuous streams of characters, and the record boundaries are defined by context. The normal operation to read from a file does so in the forward direction and advances the pointer by that same amount. This is the problem. if you desire to read a file in the reverse direction, you must do so with forward semanticts[sp?]. This means you must "backup" a record, read the record just backed over, then "backup" again. It isn't easy, but it can be done. Others have indicated there is a Perl module to help you in this, and if done correctly ("tie" to a file handle comes to mind) it might even seem like a normal operation.
That being said, reading in the reverse direction isn't commonly done, as there are usually better ways to do the same thing. Reading the records into a hash, and operating on them that way is probably a "better idea". Also consider the original dataset. It might be better formatted at the source to eliminate the need to do the fancy dance with the data. A good look at "why" you need to access the data in this manner might pay off better in the long run.
Good luck! | [reply] |
|
|
Reading the records into a hash, and operating on them that way is probably a "better idea".
Don't you mean "Reading the records into an array..."? I assume the questioner needs to preserve the order of the records (if only so they can be accurately reversed) and hashes generally destroy that order.
A good look at "why" you need to access the data in this manner might pay off better in the long run.
Indeed!
| [reply] |
Re: read a file in both directions
by TedPride (Priest) on Jun 01, 2006 at 05:27 UTC
|
This is ridiculously easy if you don't care about memory:
open($handle, 'test.dat');
while (<$handle>) {
chomp; last if !$_;
}
chomp(@data = <$handle>);
close($handle);
for (reverse @data) {
print $_, "\n";
}
But you may want to use a more memory-efficient method:
open($handle, 'test.dat');
while (<$handle>) {
chomp; last if !$_;
}
while (<$handle>) {
chomp; push @data, $_;
}
close($handle);
for ($i = $#data; $i > -1; $i--) {
print $data[$i], "\n";
}
And then there's always the really memory-efficient method (which I'm not supplying code for here), which would involve finding the position in the file of the first blank line (keep track of current position by adding up line lengths), then going to the end of the file and working back in chunks of x bytes. This gets a lot more messy and complicated, and I sadly don't have time right now to write up a working code sample. | [reply] [d/l] [select] |