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

hi, im trying to read a file line by line, and write it out to another file, but alternating each write such that it writes the line to the start of the file one time and to the end of the file the next. the code i have (below) gives me an output file where it seems that each time i move the filepointer to the start of the file and write, i'm overwriting the last thing i wrote at the start of the file. i am trying to have it so that each time i seek to the beginning, it seeks to the beginning and writes what i want before whatever is there (while preserving the existing data instead of overwriting it). here is the code i have:
open (FIN, "<$ARGV[0]") || die "Input file: $!\n"; open (FOUT, ">$ARGV[0]-randomized") || die "Output file: $!\n"; $flag = 1; #set autoflush on output filehandle $oldfh = select(FOUT); $| = 1; select ($oldfh); while (<FIN>) { if ($flag) { seek (FOUT, 0, 0) || die "Seek to beginning: $!\n"; $flag = 0; } else { seek (FOUT, 0, 2) || die "Seek to end: $!\n"; $flag = 1; } print FOUT $_; } close (FOUT); close (FIN);
any suggestions on how to actually accomplish what i'm going for would be greatly appreciated. :-)

Replies are listed 'Best First'.
Re: rearrange lines in file
by Limbic~Region (Chancellor) on Mar 04, 2003 at 23:34 UTC
    ZxCv,
    Might I suggest using Tie::File for this task. Basically, it allows you to treat a file like a Perl array. It should be straight forward then to read forward and backward from the array and writing to the file.

    Cheers - L~R

    Update: You don't even need to write code to read the array if you make a copy of the original file. You can just use pop and shift to create the new file. This will leave the original file empty unless you have copied it first. If you do not want to copy the file, then you will have to write your own logic.

Re: rearrange lines in file
by tachyon (Chancellor) on Mar 04, 2003 at 23:34 UTC

    You can't write to the beginning of a file without overwriting data that is already there. Provided the file is not to big this is the easiest (but not the only) way to do it:

    my ( @data, $cnt); while(<DATA>) { if ( $cnt++ % 2 ) { push @data, $_; } else { unshift @data, $_; } } # now open new file and just do (to the FILEHANDLE not STDOUT) print STDOUT @data; __DATA__ 1 2 3 4 5 6 7 8 9 10

    This will rearrage the file to look like:

    9 7 5 3 1 2 4 6 8 10

    Another option is to write one half of the data to one file and the other half (alternate lines) to another temp file. As you can see the even lines will be in the right order. The odd lines need reversing. To generate the output file use File::ReadBackwards on the odd line file to pring it out in reverse and then just print out the even lines. This is the way to do it if the file is too big to fit in an array in memory, otherwise I would not bother. You could also use Tie::File and probably a few other things as well.

    cheers

    tachyon

    s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print

Re: rearrange lines in file
by tachyon (Chancellor) on Mar 04, 2003 at 23:47 UTC

    I notice you call your output file '-randomize' which it is most certainly not. If you want to make a random arrangement of lines then do a Fisher-Yates Shuffle

    open FILE, $file or die $!; my @array = <FILE>; close FILE; my $i = @array; while( $i-- ){ my $j=int rand(1+$i); next if $i == $j; @array[$i, $j]=@array[$j, $i] } open OUT, ">$out" or die $!; print OUT @array; close OUT:

    Use Tie::File if you can't fit the entire file in an array in memory but don't expect if to finish in a hurry.

    tachyon

    s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print

Re: rearrange lines in file
by jasonk (Parson) on Mar 04, 2003 at 23:42 UTC

    Just sticking text in at the beginning of the file will not make the rest of the file move out of the way, your code will need to do the work of moving the other data to make room for the new line. If the file is small enough to hold in memory, this may be an easier randomizing technique:

    open(FIN,"<$ARGV[0]") or die "Input file: $!"; chomp(@data = <FIN>); close(FIN); open(FOUT,">$ARGV[0]-randomized") or die "Output file: $!"; while(@data) { print FOUT splice(@data,rand(@data),1)."\n"; } close(FOUT);
Re: rearrange lines in file
by Dr. Mu (Hermit) on Mar 05, 2003 at 04:51 UTC
    Another way to go about this is to read through the file once to create an index:
    my @index; do {push @index, tell FIN} while <FIN>; pop @index;
    (That pop is necessary to delete the value that points one byte past the end of the file.)

    Then you can read lines from the file in any order you want, using seek with elements of @index, and print them to the output file in sequence. For example, to read and print the 5th line from the input file:

    seek FIN, $index[4], 0; print FOUT scalar <FIN>;
    (The scalar is necessary to keep <FIN> from being evaluated in print's default list context, which would read and print the rest of the input file instead of just one line.)