Reputation: 1449
I have the following file:
a b 5
c d 6
e f 7
g h 4
i j 3
k l 10
and I want to find which line present the minimum value in the third column and erase it from the initial file. After this, I want to iterate again the program and find again which line present the minimum and make the same thing for 2 more times.
The output file should be
c d 6
e f 7
k l 10
I tried to write the following code:
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
my $file1 = "try.dat";
open my $fg, "<", $file1 or die "Error during $file1 opening: $!";
my @vector;
while (<$fg>) {
push @vector, [ split ];
}
my $minimum = $vector[0][2];
my @blacklist;
for my $i (0 .. $#vector) {
if ($minimum > $vector[$i][2]){
$minimum = $vector[$i][2];
push @blacklist, @vector[$i+1];
}
}
#say "my minimum is $minimum";
#say "the blacklist is composed by @blacklist";
I don't know how to erase the elements contained by the @blacklist
(that in the first case should be i j 3
) and how to iterate everything.
Any help for the iteration?
Upvotes: 1
Views: 112
Reputation: 20280
Taking Borodin's Tie::File suggestion even further. I have written a cute module called Tie::Array::CSV which allow you to treat a delimited file as an array (and because it uses Tie::File underneath, it is both read and write). Because of this I can use Perlish operations like map and sort (and Schwartzian transform!) to perform this task:
#!/usr/bin/env perl
use strict;
use warnings;
use Tie::Array::CSV;
tie my @data, 'Tie::Array::CSV', 'data', sep_char => ' ';
# get a list of row ids sorted by last value (inc)
my $i = 0;
my @sorted =
map { $_->[0] }
sort { $a->[1] <=> $b->[1] }
map { [$i++, $_->[-1]] }
@data;
#splice the rows by index of the lowest three (from bottom to top)
splice @data, $_, 1 for reverse sort @sorted[0..2];
Note that in the end you want to remove rows from the bottom so that you don't have to reindex every time.
Upvotes: 0
Reputation: 1921
When you say @blacklist = @vector
you are adding the entire @vector
array to the black list. You probably want to do a push @blacklist, $vector[$i]
. That will push the array reference into blacklist.
Now, blacklist has an array ref in it, so you have to deference it to print it.
say "the blacklist is composed by @{$blacklist[0]}";
Edit: For iterating and writing:
I would skip the @blacklist
array (unless you need it for something else) and remove the min values from @vector
. Then you can write @vector
to some file.
my $num_elts_to_remove = 3;
for (my $j = 0; $j < $num_elts_to_remove; $j++) {
my $minimum = $vector[0][2];
my $min_idx = 0;
for my $i (0 .. $#vector) {
if ($minimum > $vector[$i][2]){
$minimum = $vector[$i][2];
$min_idx = $i;
}
}
push @blacklist, $vector[$min_index];
splice @vector, $min_idx, 1; #remove array with smallest value
}
Now write the array to a file
open OUT, ">", $outfile or die "Error: $!";
foreach(@vector) {
print OUT join " ", @$_;
print OUT "\n";
}
close(OUT);
Prints:
c d 6
e f 7
k l 10
Upvotes: 1
Reputation: 126722
This sort of thing is what Tie::File
was made for. It allows you to modify the file in-place by modfying a tied array.
This program does what you want. The helper function minidx
returns the first index of the element of the passed array that holds the smallest value.
The program works by copying the third field of the file records into array @field3
, and finding the index of the smallest value in there. The element at that index is then deleted from both the file and @field3
using splice
.
use strict;
use warnings;
use Tie::File;
tie my @file, 'Tie::File', 'file.txt' or die $!;
my @field3 = map { (split)[2] } @file;
for (1 .. 3) {
my $i = minidx(\@field3);
splice @file, $i, 1;
splice @field3, $i, 1;
}
sub minidx {
my ($arr) = @_;
my ($i, $v);
for (0 .. $#$arr) {
($i, $v) = ($_, $arr->[$_]) unless defined $v and $arr->[$_] >= $v;
}
return $i;
}
output
c d 6
e f 7
k l 10
Upvotes: 2