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!
Re: How to use sprintf %n formatting pattern
by GrandFather (Saint) on Jun 09, 2022 at 03:12 UTC
|
#!/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
| [reply] [d/l] [select] |
|
Ha! Yes, that would be pretty simple. Got so caught up trying to understand %n that I missed it. :)
| [reply] |
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
| [reply] [d/l] |
|
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.
| [reply] [d/l] |
|
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
| [reply] [d/l] |
|
|
|
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.
| [reply] |
Re: How to use sprintf %n formatting pattern
by kcott (Archbishop) on Jun 09, 2022 at 07:36 UTC
|
#!/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.
| [reply] [d/l] [select] |
|
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.)
| [reply] [d/l] |
|
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.
| [reply] [d/l] [select] |
|
|
| [reply] |
Re: How to use sprintf %n formatting pattern
by AnomalousMonk (Archbishop) on Jun 09, 2022 at 05:26 UTC
|
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: <%-{-{-{-<
| [reply] [d/l] [select] |
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
%
| [reply] [d/l] |
|
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
| [reply] [d/l] |
|
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?
| [reply] [d/l] [select] |
|
|
| [reply] [d/l] [select] |
|
|
| [reply] |
Re: How to use sprintf %n formatting pattern (chain printf)
by LanX (Saint) on Jun 09, 2022 at 11:06 UTC
|
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.
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)
;
}
| [reply] [d/l] [select] |
Re: How to use sprintf %n formatting pattern
by Anonymous Monk on Jun 09, 2022 at 09:18 UTC
|
1170
1171 {
1172 # gh #17221
1173 my ($off1, $off2);
1174 my $x = eval { sprintf "%n0%n\x{100}", $off1, $off2 };
1175 is($@, "", "no exception");
1176 is($x, "0\x{100}", "reasonable result");
1177 is($off1, 0, "offset at start");
1178 is($off2, 1, "offset after 0");
1179 }
1180
https://perl5.git.perl.org/perl5.git/blob/HEAD:/t/op/sprintf2.t#l1174
Instructions
173 # In each of the following lines, there are three required fields:...
https://perl5.git.perl.org/perl5.git/blob/HEAD:/t/op/sprintf.t#l173
https://perl5.git.perl.org/perl5.git/blob/HEAD:/t/op/sprintf.t#l481
481 >%s< >sprintf('%%n%n %d', $n, $n)< >%n 2< >Slight sneakiness to te
+st %n<
482 >%s< >$n="abc"; sprintf(' %n%s', substr($n,1,1), $n)< > a1c< >%n
+w/magic<
483 >%s< >no warnings; sprintf('%s%n', chr(256)x5, $n),$n< >5< >Unico
+de %n<
484 >
I imagine running the test file might show whats executed
| [reply] [d/l] [select] |
|
|