VDC
VDC

Reputation: 1449

Iterate a simple program

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

Answers (3)

Joel Berger
Joel Berger

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

Nate
Nate

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

Borodin
Borodin

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

Related Questions