Beefy Boxes and Bandwidth Generously Provided by pair Networks
"be consistent"
 
PerlMonks  

Breaking output lines into N-element chunks

by FoxtrotUniform (Prior)
on Apr 11, 2002 at 16:33 UTC ( [id://158356]=perlquestion: print w/replies, xml ) Need Help??

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

Monks,

I've recently been asked to make some changes to a script I wrote several months ago. The script generates an input file for a statistics package we use, and as we discover limitations in the package, the script has to be changed. The problem isn't terribly difficult: the script outputs lines of arbitrary length (for this problem, length is measured in variables, not characters), and must be modified to output lines of N variables or fewer. So if N is 4:

# current output directive: v0 v1 v2 v3 v4 v5 # desired output directive: v0 v1 v2 v3 directive: v4 v5

Not terribly difficult. But these projects are usually refactors, not rewrites, and need to be done MFP. So mucking with the data structure isn't an option. What I've done so far is to convert a simple:

print "directive: "; for my $var (@vars) { print munge_var($var), " "; } print "\n";
and hacked on some counting:
my $v_count = 0; print "directive: "; for my $var (@vars) { print &munge_vars($var), " "; $v_count++; if($v_count == $LIMIT) { print "\ndirective: "; $v_count = 0; } } print "\n";

Ugh. But it gets the job done, even early in the morning before my first cup of coffee.

Always looking for more elegance, I'm contemplating this technique for the next time this comes up:

my $v_count = 0; for my $var (@vars) { if(($v_count % $LIMIT) == 0) { print "\ndirective: "; } print &munge_var($var), " "; } print "\n";

A bit cleaner, but outputs a leading newline, and the mod in the conditional might mess up a maintainance programmer (I've seen it happen before).

The "hey, there's a leading separator that shouldn't be there" problem that I noticed above makes me think that I should be keeping a list of lines, and doing a print join("\n", @lines), "\n"; at the bottom of the loop. All of my attemts to write a list- based solution end up being three or four times longer and harier than either of the above, though.

TIMTOADY, but how would you do it? I'm sure there's a cleaner solution to this problem than I've seen. What am I missing? (Besides a decent title on this node, that is....)

--
Good luck, tilly
:wq

Replies are listed 'Best First'.
•Re: Breaking output lines into N-element chunks
by merlyn (Sage) on Apr 11, 2002 at 17:52 UTC
Re: Breaking output lines into N-element chunks
by mdillon (Priest) on Apr 11, 2002 at 16:54 UTC
    I would do it like this:
    while (@vars) { print "directive: "; print join " ", map munge_var($_), splice @vars, 0, 4; print $/; }

    Update: if I were feeling crazy, I might instead do it like this:

    sub print_directive { return unless @_; print "directive: "; print join " ", map munge_var($_), splice @_, 0, 4; print $/; &print_directive; } print_directive @vars;
Re: Breaking output lines into N-element chunks
by talexb (Chancellor) on Apr 11, 2002 at 16:53 UTC
    If you use splice you'll get something that works OK:
    my @a = qw/v0 v1 v2 v3 v4 v5/; while ( $#a >= 0 ) { print "Directive: " . join ( " ", splice ( @a, 0, 4 ) ) . "\n"; }
    The groovy part about this is that if the array's empty, nothing prints.

    --t. alex

    "Here's the chocolates, and here's the flowers. Now how 'bout it, widder hen, will ya marry me?" --Foghorn Leghorn

    Update Well, mdillon remembered the map munge() part and I didn't. ++ to him.

Re: Breaking output lines into N-element chunks
by mrbbking (Hermit) on Apr 11, 2002 at 16:57 UTC
    the mod in the conditional might mess up a maintainance programmer
    ...or maybe even the original programmer. :-)
    You forgot to increment $v_count, so the conditional is always true. 0 always == 0.

    How about a C-style loop?

    for( my $i=0; $i < scalar(@vars); $i++ ){ unless( $i % $LIMIT ){ print "\ndirective: "; } print &munge_var($vars[$i]) , " "; }
    HTH --
    ______________________________ s!!password!;y?sordid?binger?; y.paw.mrk.;;print chr 0x5b ;;; print;print chr(0x5b+0x2);;;;;
      How about a C-style loop?

      Whats wrong with a perl loop? Why use a loop form that is inclined toward error when you dont have to?

      for my $i (0..@vars-1){ }

      Yves / DeMerphq
      ---
      Writing a good benchmark isnt as easy as it might look.

          Whats wrong with a perl loop? Why use a loop form that is inclined toward error when you dont have to?
          for my $i (0..@vars-1){ }

        Oww oww oww! Make the hurting stop! Avoiding confusion between number-of-elements and last-valid-index is exactly why Perlish for loops are (usually) safer than C for loops. If you're going to be using $i as an array index, at least loop from 0 to $#vars instead of mucking about with length-1. IMO, your loop is more error-prone than a C-style loop, because eventually someone will forget the -1.

        --
        Good luck, tilly
        :wq

Re: Breaking output lines into N-element chunks
by particle (Vicar) on Apr 11, 2002 at 17:18 UTC
    forget all this join and % business... use the nice perl-supplied variables!

    #!/usr/bin/perl -w use strict; $|++; my @a = qw/v0 v1 v2 v3 v4 v5/; sub LIMIT () { 4 } while(scalar @a) { local $,=' '; print 'd:', splice(@a,0,LIMIT), $/; }
    Update: made changes caught by Juerd (below)

    ~Particle ;̃

      sub LIMIT { 4 }

      What is the point in defining LIMIT when you don't use it? :) Oh, and adding an empty prototype makes your LIMIT inlineable, which will increase speed (okay, in scripts like these you'll hardly notice the difference, but hey, it's only two extra characters (or three if you want whitespace, like I do)).

      sub LIMIT () { 4 }
      No, this is not a bad use of prototypes. Actually, this is a very good feature which allows for constants to be used (the constant pragma does the same)
      use constant LIMIT => 4;
      I don't like that syntax, though, so I stick to subs with empty prototypes.

      Yes, I reinvent wheels.
      

        What is the point in defining LIMIT when you don't use it? :)

        oops! i hastily added the LIMIT sub between preview and submit, and deleted both the empty prototype, and the call in the splice. thanks for catching my bug and optimizing my code!

        ~Particle ;̃

        I don't like that syntax, though, so I stick to subs with empty prototypes

        While I agree with you that the constant syntax is not that nice I believe that in the long run you are probably doing yourself a disservice with not using it.

        The advantage of using constant is that it clearly announces to a maintainence coder or equiv what is going on. They would need to be fairly experienced to know what the purpose is of such an unusual looking sub, and if they were like me might even remove the prototype on principle.

        A second point is that by using the module you are implicitly documenting what you are doing. Not just on a literal programming level but also simply on the level that the maintainence coder can then review constant.pm documentation and see that there _are_ quirks with this technique. Now these quirks apply whether you do it by hand or by using contant.pm, the only difference being that if you do all your constants by hand its up to YOU to document these quirks to future programmers.

        Being a lazy type, especially when it comes to documentation I would use constant.pm for this reason alone...

        Yves / DeMerphq
        ---
        Writing a good benchmark isnt as easy as it might look.

Re: Breaking output lines into N-element chunks
by belden (Friar) on Apr 11, 2002 at 18:20 UTC
    I've been having fun with the x operator lately. My first thought:

    #!/usr/bin/perl use strict; use warnings; use constant LIMIT => 4; my @a = qw ( a bc def ghij klmno ); printf "%s " x LIMIT, @a; print "\n";
    a bc def ghij

    blyman
    setenv EXINIT 'set noai ts=2'

      printf "%s " x LIMIT, @a;

      That does some of the job, but only uses the first four (LIMIT) elements. To process an array in chunks, you need some sort of loop. The loop has to use an array index, or must destroy the array while running. Your solution is more or less equal to:

      print join ' ', @a[0..(LIMIT - 1)]; print " \n";
      This leaves alle elements in the array, and the indexes are constant (although you flatten the entire array, the general idea is using the first four (LIMIT) items, leaving the array intact).

      Solutions offered increase array indexes by four at a time, or destroy the array 4 elements at a time. The latter is done by looping until the array is empty (while it has elements), and spliceing to remove four elements. merlyn offered a compact solution that has the actual splice in the while-condition, which works because splice does not pad with undef elements, but actually returns an empty list when the array it spliced didn't have elements. The array assignment takes splices list and returns the array itself, which in scalar context evaluates to the number of elements. When the main array has no more elements, splice returns no elements, the array in scalar context returns 0, which is false so the loop stops.

      There is (as far as I know) no way to solve this without a smart loop.

      Yes, I reinvent wheels.
      

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://158356]
Approved by Rex(Wrecks)
Front-paged by Rex(Wrecks)
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others sharing their wisdom with the Monastery: (6)
As of 2024-03-28 15:32 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found