Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

grantm (164)

grantm
  (email not shown publicly)
http://www.mclean.net.nz/

Just a simple [cpan.org] guy, hacking Perl for fun and profit since way back in the last millenium. You may find me hanging around in the monestary [perlmonks.org].

What am I working on right now? Probably the Sprog project [sourceforge.net].

GnuPG key Fingerprint:
6CA8 2022 5006 70E9 2D66
AE3F 1AF1 A20A 4CC0 0851

Journal of grantm (164)

Monday March 20, 2006
03:52 AM

Unreadable Perl Code

[ #29051 ]

I have a replication process which applies updates. Usually the updates are incremental 'patches' sometimes they are full copies. The incoming queue of updates might look like this:

my @files = qw(
    00000.diff
    00001.diff
    00002.diff
    00003.copy
    00004.diff
    00005.diff
    00006.copy
    00007.diff
);

If there are one or more full copies in the queue, there is no point processing any updates before the last full copy. So what I needed was an algorithm to reduce the above example to just the last two files.

My first thought was that I ought to be able to do it with some form of grep using the flip-flop operator. Alas, my initial attempts were not successful so I wrote a loop that popped things off one array and unshifted them onto another - breaking out of the loop if a full copy file was found. It wasn't complicated code but it took more lines than I'd have liked and it needed a one-line comment explaining what the loop was doing (perhaps, in retrospect, I should have put it into a subroutine).

I later realised what I was doing wrong in my first attempts and came up with this version:

my $i = 0;
@files = reverse grep !$i++../\.copy$/, reverse @files;

Having to use $i was annoying but I couldn't think of another way to code an expression that was true the first time it was evaluated and false from then on.

Anyway, I ran this version past a couple of my colleagues and while they were intrigued, they felt it was basically unreadable and didn't want to be saddled with maintaining it. So I 'committed' it here instead :-)

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • The only assumption I make is that the files in the list are in sequential order.
    my @keep;
    foreach (@files) {
        @keep = () if /\.copy$/;
        push @keep, $_;
    }
    • Of all the suggestions, this one definitely gets my vote for clarity. I did think along the lines of discarding accumulated filenames whenever a '.copy' was encountered but at the time I had grep blinkers on and it can't be done with grep.


  • Perhaps try the List::MoreUtils module?
    use List::MoreUtils qw/before_incl/;
    my @keep =
         reverse
         before_incl { /\.copy\z/ }
         reverse @files;
  • ...So what I needed was an algorithm to reduce the above example to just the last two files....

    Or, at least, you want to get the last index of a /\.copy$/ match from the list.

    my $index = (
        grep({$files[$_] =~ m/\.copy$/}
            reverse(0..$#files)
        )
    )[0];
    splice(@files, 0, $index);
  • my $seen_full_copy;
    @files = reverse grep { not( $seen_full_copy ||= /\.copy$/ ) } reverse @files;

    The double reverse is still unsightly though.

  • There's a "reduce" in List::Util, but its interface is fairly horribly broken.

    Anyway, here's a fold-using implementation:


    use strict;

    sub fold {
        my $accum = shift;
        my $code = shift;
        return $accum unless @_;
        return fold( $code->( $accum, shift ), $code, @_ );
    }

    my @files = qw(
            00000.diff
            00001.diff
            00002.diff
            00003.copy