Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

How to use sprintf %n formatting pattern

by ibm1620 (Hermit)
on Jun 09, 2022 at 02:33 UTC ( [id://11144534]=perlquestion: print w/replies, xml ) Need Help??

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

I want to print a record with 5 fields, truncating the final one if necessary to prevent its wrapping at the edge of the terminal window, whose width is $max_width.

For example, if max_width = 60:

1.......10........20........30........40........50........60 79.30 2022/1/8 The quick brown fox jumps over the lazy dog| 394571.00 22/10/81 The quick brown fox jumps over the lazy| 123456.78 12345/123/1234 The quick brown fox jumps over th|
The printf pattern %n "*stores* the number of characters output so far into the next argument in the parameter list." That sounds like part of the solution (max_width - n would be the remaining space on the line and the length to which to truncate the final argument with a %-.*s construct) but I can't see any way to perform a calculation on the value that %n inserts.

Here's the code I've written, with the truncation value manually calculated:

#!/usr/bin/env perl use 5.010; use warnings; use strict; my $max_width = 60; my $scale = '1.......10........20........30........40........50....... +.60'; my $long_string = q{The quick brown fox jumps over the lazy dog}; my $pat = "%.2f %d/%d/%d %-.*s|\n"; my @aoa = ( [ qw/79.3 2022 1 8 43/, $long_string ], [ qw/394571 22 10 81 39/, $long_string ], [ qw/123456.78 12345 123 1234 33/, $long_string ], # compute ^^ as (60 - pos) ); say $scale; for my $aref (@aoa) { printf( $pat, @$aref ); }
I can't easily imagine why the %n value itself would be useful except as part of some calculation. Any clarification would be much appreciated!

Replies are listed 'Best First'.
Re: How to use sprintf %n formatting pattern
by GrandFather (Saint) on Jun 09, 2022 at 03:12 UTC

    Alternatively you could generate the whole string then truncate it for printing:

    #!/usr/bin/env perl use 5.010; use warnings; use strict; my $max_width = 60; my $scale = '1.......10........20........30........40........50....... +.60'; my $long_string = q{The quick brown fox jumps over the dog}; my $pat = "%.2f %d/%d/%d %-*s"; my @aoa = ( [ qw/79.3 2022 1 8 /], [ qw/394571 22 10 81 /], [ qw/123456.78 12345 123 1234/], ); say $scale; for my $aref (@aoa) { my $str = sprintf $pat, @$aref, $max_width, $long_string; print substr($str, 0, $max_width - 1), "|\n"; }

    Prints:

    1.......10........20........30........40........50........60 79.30 2022/1/8 The quick brown fox jumps over the dog | 394571.00 22/10/81 The quick brown fox jumps over the dog | 123456.78 12345/123/1234 The quick brown fox jumps over th|
    Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
      Ha! Yes, that would be pretty simple. Got so caught up trying to understand %n that I missed it. :)
Re: How to use sprintf %n formatting pattern
by GrandFather (Saint) on Jun 09, 2022 at 02:57 UTC

    You could break the print into two parts so you can take advantage of knowing the prefix length using %n:

    #!/usr/bin/env perl use 5.010; use warnings; use strict; my $max_width = 60; my $scale = '1.......10........20........30........40........50....... +.60'; my $long_string = q{The quick brown fox jumps over the lazy dog}; my $pat = "%.2f %d/%d/%d %n"; my @aoa = ( [ qw/79.3 2022 1 8 /], [ qw/394571 22 10 81 /], [ qw/123456.78 12345 123 1234/], ); say $scale; for my $aref (@aoa) { my $prefixLen; printf $pat, @$aref, $prefixLen; my $space = max_width - $prefixLen - 1; printf "%-*s|\n", $space, substr $long_string, 0, $space; }

    Note that you can't do it in a single statement because the arguments to printf need to be evaluated before printf goes about the business of generating its output.

    Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond

      the arguments to printf need to be evaluated before printf goes about the business of generating its output

      I'm not sure what you mean by this - as far as I know the arguments are scalars, and it doesn't look at them until it needs to.

      I suspect you probably could achieve this with a tied scalar dynamically returning $max_width - $stored_width. I think it'd be a pretty insane thing to do unless there was some really strong reason not to split it into two prints, but I'm ok with a bit of insanity, I might have a go at it after some sleep.

        Consider:

        my $str = '1234567890' x 6; my $prefixLen; printf "Prefix stuff %n%s\n", $prefixLen, substr $str, 0, 35 - $prefix +Len;

        What value does substr see for $prefixLen? How can printf get the substr generated parameter generated after printf has set about generating the output?

        Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
      This shows me that I misunderstood how %n works. I had imagined that, at execution, the arguments were copied into a temporary list which the pattern then consumed, and that %n "unshifted" its value onto the head of the list, to be the next thing to be consumed. I didn't realize that the value was actually plugged into the next variable.
Re: How to use sprintf %n formatting pattern
by kcott (Archbishop) on Jun 09, 2022 at 07:36 UTC

    G'day ibm1620,

    You can achieve that with this code:

    #!/usr/bin/env perl use strict; use warnings; my $scale = '1.......10........20........30........40........50........60'; my $max_width = length($scale) - 1; my $long_string = q{The quick brown fox jumps over the lazy dog}; my @records = ( [qw{79.3 2022 1 8}, $long_string], [qw{394571 22 10 81}, $long_string], [qw{123456.78 12345 123 1234}, $long_string], ); my $sprintf_fmt = '%.2f %d %d %d %n'; my $printf_fmt = "%s%.*s|\n"; print "$scale\n"; for my $record (@records) { my $used_so_far; printf $printf_fmt, sprintf($sprintf_fmt, @{$record}[0..3], $used_so_far), $max_width - $used_so_far, $record->[4]; }

    Output:

    1.......10........20........30........40........50........60 79.30 2022 1 8 The quick brown fox jumps over the lazy dog| 394571.00 22 10 81 The quick brown fox jumps over the lazy| 123456.78 12345 123 1234 The quick brown fox jumps over th|

    Update: I've just noticed the '%.2f %d %d %d  %n' format would've been better as '%.2f %d/%d/%d  %n' to align with the original OP code. However, it's purely cosmetic and doesn't affect the functionality or the technique being demonstrated.

    — Ken

      Got it.

      I had thought %n would make for a more elegant solution. It doesn't look like %n saves a whole lot of work since a follow-on (s)printf statement (or clause, as you demonstrate) seems to be required, at which point the length function would provide the same info. (I think.)

        I agree. My post was mainly intended to demonstrate the use of %n as you'd requested. It was not a recommendation for production-grade code.

        I would probably just generate the potentially long string with sprintf("%.2f %d/%d/%d  %s", @$record); then use printf to truncate and print. You have a number of options here; I've shown a selection below — the first pair indicates a gotcha which you should avoid; the remaining three pairs are all potential candidates (depends on the final output you want).

        $ perl -e ' my $x = "12345"; my $y = "1234567890"; printf q{%-*2$s|}."\n", $x, 9; printf q{%-*2$s|}."\n", $y, 9; printf q{%-.*2$s|}."\n", $x, 9; printf q{%-.*2$s|}."\n", $y, 9; printf q{%-*2$.*2$s|}."\n", $x, 9; printf q{%-*2$.*2$s|}."\n", $y, 9; printf q{%-*2$.*3$s|}."\n", $x, 10, 9; printf q{%-*2$.*3$s|}."\n", $y, 10, 9; ' 12345 | 1234567890| 12345| 123456789| 12345 | 123456789| 12345 | 123456789 |

        The (possibly odd-looking) 'q{...}."\n"' is needed to avoid a 'Global symbol "$s" requires explicit package name ...' error. You could escape the '$' signs (e.g. "%-*2\$.*3\$s|\n") but, in my opinion, that makes the already somewhat cryptic format even less readable.

        I hadn't previously used '%n', so that was an interesting learning exercise for me. Thanks for the question.

        — Ken

        > at which point the length function would provide the same info

        yep, exactly my thought, it's just shorter syntax.

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery

Re: How to use sprintf %n formatting pattern
by AnomalousMonk (Archbishop) on Jun 09, 2022 at 05:26 UTC

    Another alternative without %n. See sprintf.

    Win8 Strawberry 5.8.9.5 (32) Thu 06/09/2022 1:14:50 C:\@Work\Perl\monks >perl use strict; use warnings; my $str = 'The quick brown fox jumps over the lazy dog'; printf "\$str len %d \n", length $str; print " 1 2 3 4 5 6 \ +n" . "1234567890123456789012345678901234567890123456789012345678901 \ +n"; for my $max_width (reverse 56 .. 61) { print "\$max_width == $max_width ------------------------------ \ +n"; for my $date ( '79.30 2022/1/8', '394571.00 22/10/81', '123456.78 12345/123/1 +234', ) { my $width = $max_width - length($date) - 2; printf "%s %*.*s| \n", $date, -$width, $width, $str; } } ^Z $str len 43 1 2 3 4 5 6 1234567890123456789012345678901234567890123456789012345678901 $max_width == 61 ------------------------------ 79.30 2022/1/8 The quick brown fox jumps over the lazy dog | 394571.00 22/10/81 The quick brown fox jumps over the lazy d| 123456.78 12345/123/1234 The quick brown fox jumps over the | $max_width == 60 ------------------------------ 79.30 2022/1/8 The quick brown fox jumps over the lazy dog | 394571.00 22/10/81 The quick brown fox jumps over the lazy | 123456.78 12345/123/1234 The quick brown fox jumps over the| $max_width == 59 ------------------------------ 79.30 2022/1/8 The quick brown fox jumps over the lazy dog| 394571.00 22/10/81 The quick brown fox jumps over the lazy| 123456.78 12345/123/1234 The quick brown fox jumps over th| $max_width == 58 ------------------------------ 79.30 2022/1/8 The quick brown fox jumps over the lazy do| 394571.00 22/10/81 The quick brown fox jumps over the laz| 123456.78 12345/123/1234 The quick brown fox jumps over t| $max_width == 57 ------------------------------ 79.30 2022/1/8 The quick brown fox jumps over the lazy d| 394571.00 22/10/81 The quick brown fox jumps over the la| 123456.78 12345/123/1234 The quick brown fox jumps over | $max_width == 56 ------------------------------ 79.30 2022/1/8 The quick brown fox jumps over the lazy | 394571.00 22/10/81 The quick brown fox jumps over the l| 123456.78 12345/123/1234 The quick brown fox jumps over|


    Give a man a fish:  <%-{-{-{-<

Re: How to use sprintf %n formatting pattern
by hv (Prior) on Jun 09, 2022 at 12:13 UTC

    Here's an approach that achieves what you ask for. As mentioned earlier, it is pretty insane to do this - doing it in two separate steps makes far more sense - but it is possible.

    % cat t0 use strict; use warnings; my $max_width = shift @ARGV; my $prefix = "foo"; my $postfix = "0123456789"; my $stored_width; tie my $limit, 'LazyScalar', sub { $max_width - $stored_width }; printf "%s%n%.*s\n", $prefix, $stored_width, $limit, $postfix; # A simple tied class for deferred evaluation of a scalar's value package LazyScalar { sub TIESCALAR { my($class, $cb) = @_; return bless \$cb, $class; } sub FETCH { my($self) = @_; return $$self->(); } }; % perl t0 6 foo012 % perl t0 7 foo0123 % perl t0 8 foo01234 %

      When I run your code I get:

      Use of uninitialized value $max_width in subtraction (-) at delme.pl l +ine 26. foo0123456789

      which is about what I'd have expected due to the arguments to printf being evaluated before printf is called. What am I missing here?

      Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond

        which is about what I'd have expected due to the arguments to printf being evaluated before printf is called

        I'm still not sure what you mean by that.

        If you call foo($a + $b), perl will calculate $a + $b putting the result in a temporary scalar variable, and put that temporary scalar variable on the stack to call foo(). If you call foo($a), perl will put the scalar variable $a on the stack, and call foo().

        In the case of printf("%s%n%.*s\n", $prefix, $stored_width, $limit, $postfix), we get (effectively) references to each of those variables on the stack (at the C level, they are just SV* pointers) - you know they have to be references, since printf needs to be able to write to $stored_width. At the point the printf implementation needs the next argument as a number, it'll ask the relevant scalar what its numeric value is; if it needs a string, it'll ask for that instead, etc.

        Is that making it any clearer?

        If $max_width is undefined, you probably ran it without providing a command-line argument. Try setting $max_width to something.

        It works fine for me: This is perl 5, version 34, subversion 0 (v5.34.0) built for x86_64-linux-thread-multi.


        🦛

Re: How to use sprintf %n formatting pattern (chain printf)
by LanX (Saint) on Jun 09, 2022 at 11:06 UTC
    my take on this is to KISS and to chain 2 (s)printf (similar to GF's second solution)

    use 5.012; use warnings; my $width = 60 - 1; my $scale = '1.......10........20........30........40........50....... +.60'; my $long_string = q{The quick brown fox jumps over the lazy dog}; my $short_string = q{lazy dog}; my $pat = "%.2f %d/%d/%d %s"; my @aoa = ( [ qw/79.3 2022 1 8 /, $long_string ], [ qw/394571 22 10 81 /, $long_string ], [ qw/123456.78 12345 123 1234 /, $long_string ], [ qw/79.3 2022 1 8 /, $short_string ], ); say $scale; for my $aref (@aoa) { printf "%.*s|\n" , $width => sprintf ($pat => @$aref) . " "x$width; }
    OUTPUT:
    1.......10........20........30........40........50........60 79.30 2022/1/8 The quick brown fox jumps over the lazy dog | 394571.00 22/10/81 The quick brown fox jumps over the lazy | 123456.78 12345/123/1234 The quick brown fox jumps over the| 79.30 2022/1/8 lazy dog |

    NB: I appended " "x $max_width for left alignment by minimal length.

    There is most probably a possibility to enforce left alignment in the pattern, that I'm ignoring/missing.

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

    update

    off by one, corrected 60 to 59

    update

    > to enforce left alignment in the pattern, that I'm ignoring/missing.

    prepending - to the pattern signals a left align, but we'll still need $witdh twice

    use 5.012; use warnings; my $width = 60 - 1; my $scale = '1.......10........20........30........40........50....... +.60'; my $long_string = q{The quick brown fox jumps over the lazy dog}; my $short_string = q{lazy dog}; my $pat = "%.2f %d/%d/%d %s"; my @aoa = ( [ qw/79.3 2022 1 8 /, $long_string ], [ qw/394571 22 10 81 /, $long_string ], [ qw/123456.78 12345 123 1234 /, $long_string ], [ qw/79.3 2022 1 8 /, $short_string ], ); say $scale; for my $aref (@aoa) { printf "%-*.*s|\n" , $width, $width => sprintf ($pat => @$aref) ; }
Re: How to use sprintf %n formatting pattern
by Anonymous Monk on Jun 09, 2022 at 09:18 UTC

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others learning in the Monastery: (2)
As of 2024-04-18 23:26 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found