Finding the next file in a sequence

N

Niall Macpherson

I have a hash which uses a filename as a key. The filenames should all
have an extension which consists of three digits. I need a function
will return the name of the next filename in the sequence - e.g if I
have /a/b/c/xx.001, /a/b/c/xx.002, /a/b/c/xx.003 as my hash keys I
need to return /a/b/c/xx.004. It is permitted for the sequence to have
gaps, eg we could have /a/b/c/xx.001, /a/b/c/xx.006, /a/b/c/xx.012 and
the next filename generated would be /a/b/c/xx.013

The following code seems to do what I want but it does seem very long
winded. Am I missing a far neater way of doing this ? Thanks

use strict;
use warnings;
##------------------------------------------------------------------
sub GetNextFileName
{
## There must be a more elegant way of doing this !

my($rh_chunkinfo) = @_;

my $biggest = -1;
my($filebase, $number) = ("", -1);

foreach my $fname ( keys %$rh_chunkinfo )
{
($filebase, $number) = split (/\./, $fname);

if($number !~ /\d\d\d/)
{
return('Error');
}
my $fno = -1;
## get the filenumber
if($number =~ /^0*$/)
{
## all zeroes
$fno = 0;
}
else
{
## strip leading zeroes
$number =~ s/^0*//;
$fno = $number;
}

if($fno > $biggest)
{
$biggest = $fno;
}
}
## Assume 3 digits, left padded with 0's

my $zeroes = '0' x (3 - length($biggest + 1));
my $nextfilename = sprintf("%s.%s%d", $filebase, $zeroes, $biggest +
1);
return($nextfilename);
}
##------------------------------------------------------------------
my %chunkinfo;
my($id, $fname, $size, $used, $nchunks) = (-1, "", -1, -1, -1);

while (<DATA>)
{
chomp;
($id, $fname, $size, $used, $nchunks) = split(/\|/);

$chunkinfo{$fname}{id} = $id;
$chunkinfo{$fname}{fname} = $fname;
$chunkinfo{$fname}{size} = $size;
$chunkinfo{$fname}{used} = $used;
$chunkinfo{$fname}{nchunks} = $nchunks;
}
my $nextfilename = GetNextFileName(\%chunkinfo);
print 'Next filename is ' . $nextfilename . "\n";
exit(0);
__END__
1|C:/somepath/file.001|1000|100|3
2|C:/somepath/file.006|2000|300|5
3|C:/somepath/file.003|2000|600|5
4|C:/somepath/file.004|1000|100|2
5|C:/somepath/file.002|500|200|6
6|C:/somepath/file.005|750|150|8
 
A

Anno Siegel

Niall Macpherson said:
I have a hash which uses a filename as a key. The filenames should all
have an extension which consists of three digits. I need a function
will return the name of the next filename in the sequence - e.g if I
have /a/b/c/xx.001, /a/b/c/xx.002, /a/b/c/xx.003 as my hash keys I
need to return /a/b/c/xx.004. It is permitted for the sequence to have
gaps, eg we could have /a/b/c/xx.001, /a/b/c/xx.006, /a/b/c/xx.012 and
the next filename generated would be /a/b/c/xx.013

The following code seems to do what I want but it does seem very long
winded. Am I missing a far neater way of doing this ? Thanks

use strict;
use warnings;
##------------------------------------------------------------------
sub GetNextFileName
{
## There must be a more elegant way of doing this !

my($rh_chunkinfo) = @_;

my $biggest = -1;
my($filebase, $number) = ("", -1);

foreach my $fname ( keys %$rh_chunkinfo )
{
($filebase, $number) = split (/\./, $fname);

if($number !~ /\d\d\d/)
{
return('Error');
}
my $fno = -1;
## get the filenumber
if($number =~ /^0*$/)
{
## all zeroes
$fno = 0;
}
else
{
## strip leading zeroes
$number =~ s/^0*//;
$fno = $number;
}

if($fno > $biggest)
{
$biggest = $fno;
}
}
## Assume 3 digits, left padded with 0's

my $zeroes = '0' x (3 - length($biggest + 1));
my $nextfilename = sprintf("%s.%s%d", $filebase, $zeroes, $biggest +
1);
return($nextfilename);
}
##------------------------------------------------------------------
my %chunkinfo;
my($id, $fname, $size, $used, $nchunks) = (-1, "", -1, -1, -1);

while (<DATA>)
{
chomp;
($id, $fname, $size, $used, $nchunks) = split(/\|/);

$chunkinfo{$fname}{id} = $id;
$chunkinfo{$fname}{fname} = $fname;
$chunkinfo{$fname}{size} = $size;
$chunkinfo{$fname}{used} = $used;
$chunkinfo{$fname}{nchunks} = $nchunks;
}
my $nextfilename = GetNextFileName(\%chunkinfo);
print 'Next filename is ' . $nextfilename . "\n";
exit(0);
__END__
1|C:/somepath/file.001|1000|100|3
2|C:/somepath/file.006|2000|300|5
3|C:/somepath/file.003|2000|600|5
4|C:/somepath/file.004|1000|100|2
5|C:/somepath/file.002|500|200|6
6|C:/somepath/file.005|750|150|8

First off, your sub tries to do too many things at once. Write one
sub that reads the data, then another that extracts the next file name.

I haven't bothered to wrap the code up in subroutines but strung it
out in the main program:

use List::Util qw( max);

my %chunkinfo;

# read data
while (<DATA>)
{
chomp;
my ($id, $fname, $size, $used, $nchunks) = split(/\|/);

$chunkinfo{$fname}{id} = $id;
$chunkinfo{$fname}{fname} = $fname;
$chunkinfo{$fname}{size} = $size;
$chunkinfo{$fname}{used} = $used;
$chunkinfo{$fname}{nchunks} = $nchunks;
}

# get next file name
my $next_file = sprintf 'C:/somepath/file.%03d',
1 + max map { /(\d\d\d)$/ ? $1 : () } keys %chunkinfo;
print "$next_file\n";

__END__
1|C:/somepath/file.001|1000|100|3
2|C:/somepath/file.006|2000|300|5
3|C:/somepath/file.003|2000|600|5
4|C:/somepath/file.004|1000|100|2
5|C:/somepath/file.002|500|200|6

Anno
 
N

Niall Macpherson

Anno Siegel wrote:

# get next file name
my $next_file = sprintf 'C:/somepath/file.%03d',
1 + max map { /(\d\d\d)$/ ? $1 : () } keys %chunkinfo;
print "$next_file\n";


Thanks Anno . In the time that I have been using clpm I have seen a
number of solutions that use map but I have never yet had the
confidence to use it myself.

This is very neat so I think it is finally time to spend a while
getting my head around the delights of map !
 
A

Anno Siegel

Niall Macpherson said:
Anno Siegel wrote:




Thanks Anno . In the time that I have been using clpm I have seen a
number of solutions that use map but I have never yet had the
confidence to use it myself.

This is very neat so I think it is finally time to spend a while
getting my head around the delights of map !

Thank you!

It gets a little bit neater still. I just noticed that (in list context)
"/(\d\d\d)$/ ? $1 : ()" is exactly the same as "/(\d\d\d)$/". So:

my $next_file = sprintf 'C:/somepath/file.%03d',
1 + max map /(\d\d\d)$/, keys %chunkinfo;

Anno
 
I

it_says_BALLS_on_your_forehead

Anno said:
use List::Util qw( max);

my %chunkinfo;

# read data
while (<DATA>)
{
chomp;
my ($id, $fname, $size, $used, $nchunks) = split(/\|/);

$chunkinfo{$fname}{id} = $id;
$chunkinfo{$fname}{fname} = $fname;
$chunkinfo{$fname}{size} = $size;
$chunkinfo{$fname}{used} = $used;
$chunkinfo{$fname}{nchunks} = $nchunks;
}

is there a reason you use the above data structure rather than:

$chunkinfo{$fname} = { id => $id,
fname => $fname,
size => $size,
used => $used,
nchunks => $nchunks,
};

? or is it just purely a matter of taste?
 
I

it_says_BALLS_on_your_forehead

it_says_BALLS_on_your_forehead said:
is there a reason you use the above data structure rather than:

$chunkinfo{$fname} = { id => $id,
fname => $fname,
size => $size,
used => $used,
nchunks => $nchunks,
};

? or is it just purely a matter of taste?

actually, i think they are the same data structure, just a different
format...nvm.
 
A

Anno Siegel

it_says_BALLS_on_your_forehead said:
actually, i think they are the same data structure, just a different
format...nvm.

Yes, they are.

In my posting I used the code from the OP as far as possible, so I just
kept it the way it was written. If I had written it from scratch, I'd
probably have done (untested)

@{ $chunkinfo{ $fname}}{
qw( id fname size used nchunks)
} = split( /\|/);

That saves the intermediate variables. Since their names are duplicated
in form of the hash keys, there is no loss in documentation value.

Anno
 
N

Niall Macpherson

Anno Siegel wrote:

In my posting I used the code from the OP as far as possible, so I just
kept it the way it was written. If I had written it from scratch, I'd
probably have done (untested)

@{ $chunkinfo{ $fname}}{
qw( id fname size used nchunks)
} = split( /\|/);

That saves the intermediate variables. Since their names are duplicated
in form of the hash keys, there is no loss in documentation value.


All very useful stuff . Of course the example I posted was just a stand
alone script that could be run and demonstrate the issue. In my actual
code the hash is populated from the results of some Informix SQL calls.


However seeing examples like this is very useful for perl newbies like
me especially when I have to knock together small test scripts to try
to figure out a problem. I will certainly look at using this in future
since it is concise without being unreadable.

Thanks all !
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top