How to use the Perl format function to print multiple columns - perl

I have a Perl hash like %word. The key is the word and the value is its count. Now I want to display %word like:
the 20 array 10 print 2
a 18 perl 8 function 1
of 12 code 5
I search and Perl format can solve this, and I learn this page perlform, but I still don't how to do it.

I knew about format and that it could be vary handy to generate nice forms... at the time we still had a world where all was monospaced...
So, I researched it a bit and found the following solution:
use strict;
use warnings;
my %word = (
the => 20,
array => 10,
print => 2,
a => 18,
perl => 8,
function => 1,
of => 12,
code => 5,
);
my #word = %word; # turn the hash into a list
format =
#<<<<<<<<<<< #>>>> #<<<<<<<<<<< #>>>> #<<<<<<<<<<< #>>>>~~
shift #word, shift #word, shift #word, shift #word, shift #word, shift #word
.
write;
The nasty problem sits in the ~~ which makes the line repeating and that for each field in the format line you do need a corresponding scalar value... In order to get those scalar values, I shifted them off from the #word array.
There is a lot more to know about format and write.
Have fun!

Related

How to create a numeric ruler and evenly spaced numeric width markers

I am new to Perl. An exercise, where I am to create a numeric ruler from which, I size columns for data at 20 characters-width, is proving a little difficult to complete. So far, I have,
printf “%10d” x 5, (1..6);
#ruler = (1..10) x 7;
Print #ruler, “\n”;
It should look something like,
1 2 3 4
1234567890123456789012345678901234567890
What I get for the top row of numbers is an error, ‘Redundant argument in printf at <script.pl> line #; the bottom row produces numbers from 1 to 10, as it ought with the range operator, but I would like it to produce 1 to 9 with a zero on the end. I did think to start the range from 0, but I haven’t figured out how to remove the first index and only the first index.
I would be grateful for your guidance with both issues.
The warning is due to the fact that you pass 6 numbers to printf, but the format only requires 5.
To me,
1 2 3 4
1234567890123456789012345678901234567890
reads as
11, 12, 13, ..., 19, 10, 21, 22, 23, ...
Why does it start with 11? Why is 10 between 19 and 21?
The following makes more sense:
1 2 3 4
01234567890123456789012345678901234567890 0-based
and
1 2 3 4
1234567890123456789012345678901234567890 1-based
I'm not going to give the solution outright.
If you want the numbers 1 to 9 and 0, that would be 1..9, 0.
%10d will add padding on the left. %-10d will add padding on the right.
Nothing says you can't prefix the output with something that doesn't repeat, like a zero or a space.
Provided desired output starts count from 11 instead 1 -- it doesn't look right.
Perhaps OP intended to start count from 1 until some $max value with placing a digit representing tens above main counter.
Please study following code sample for compliance with your requirements.
use strict;
use warnings;
use feature 'say';
my $max = shift || 45;
rule($max);
sub rule {
my $max = shift;
my($a,$b);
$a .= ' ' x 9 . $_ for 1..$max/10;
$b .= $_ % 10 for 1..$max;
say $a . "\n" . $b;
}
Output
1 2 3 4
123456789012345678901234567890123456789012345
Original OP's code requires slight modification to achieve desired output
use strict;
use warnings;
use feature 'say';
my $max = shift || 45;
printf "%10d" x int($max/10) . "\n", (1..$max/10);
print $_ % 10 for 1..$max;
print "\n";

How to append LF (0A) character to a file using Path::Tiny?

This approach doesn't work - just string "10" is added
use Modern::Perl;
use Path::Tiny qw( path );
use DateTime;
my $d1 = DateTime->new(year => 2019, month => 5, day => 6);
my #lines_to_add;
$lines_to_add[0]= "1|" . $d1->dmy('.') . "|";
$,="\n";
my $filename = "./load";
path($filename)->spew_raw(#lines_to_add);
path($filename)->append({binmode => ":raw"}, 10);
I'd like to generate some data and then insert them into a table (Informix running on AIX). However, the environment requires the LF character at the end of files to load from. And I'd like to use just Path::Tiny library for that. (I am on Windows using Strawberry Perl)
The value produced by the numerical literal
10
is being stringified into the two character string
10
To get a string consisting of character 10, you can use any of the following:
"\n"
"\N{LINE FEED}"
"\N{LF}"
"\N{U+000A}"
"\x{0A}"
"\x0A"
"\012"
chr(10)
pack('C', 10)

How to print specific key in an array (Perl) [duplicate]

This question already has answers here:
Simple hash search by value
(5 answers)
Closed 5 years ago.
I recently started learning Perl, so I'm not too familiar with the functions and syntax.
If I have a Perl array and some variables,
#!/usr/bin/perl
use strict;
use warnings;
my #numbers = (a =>1, b=> 2, c => 3, d =>4, e => 5);
my $x;
my $range = 5;
$x = int(rand($range));
print "$x";
to generate a random number between 1-5, how can I get the program to print the actual key (a, b, c, etc.) instead of just the number (1, 2, 3, 4, 5)?
It seems that you want to do a reverse lookup, key-by-value, opposite to what we get from a hash. Since a hash is a list you can reverse it and use the resulting hash to look up by number.
A couple of corrections: you need a hash variable (not an array), and you need to add 1 to your rand integer generator so to have the desired 1..5 range
use warnings;
use strict;
use feature 'say';
my %numbers = (a => 1, b => 2, c => 3, d => 4, e => 5);
my %lookup_by_number = reverse %numbers; # values need be unique
my $range = 5;
my $x = int(rand $range) + 1;
say $lookup_by_number{$x};
Without reversing the hash you'd need to iterate the hash %numbers over values, testing each against $x so to find its key.
If there are same values for various keys in your original hash then you have to do it by hand since reverse-ing would attempt to create a hash with duplicate keys, in which case only the last one assigned remains. So you'd lose some values. One way
my #at_num = grep { $x == $numbers{$_} } keys %numbers;
as in the post that this was marked as duplicate of.
But then you should build a data structure for reverse lookup so to not search through the list every time information is needed. This can be a hash where keys are the list of unique numbers while their values are then array references (arrayrefs) with corresponding keys from the original hash
use warnings;
use strict;
my %num = (a => 1, b => 2, c => 1, d => 3, e => 2); # with duplicate values
my %lookup_by_num;
foreach my $key (keys %num) {
push #{ $lookup_by_num{$num{$key}} }, $key;
}
say "$_ => [ #{$lookup_by_num{$_}} ]" for keys %lookup_by_num;
This prints
1 => [ c a ]
3 => [ d ]
2 => [ e b ]
A nice way to display complex data structures is via Data::Dumper, or Data::Dump (or others).
The expression #{ $lookup_by_num{ $num{$key} } } extracts the value of %lookup_by_num for the key $num{$key}and dereferences it #{ ... }, so that it can then push the $key to it. The critical part of this is that the first time it encounters $num{$key} it autovivifies the arrayref and its corresponding key. See this post with its references for details.
There's many ways to do it. For example, declare "numbers" as a hash rather than an array. Note that the keys come first in each key-value pair, and here you want to use your random int as the key:
my %numbers = ( 0 => 'a', 1 => 'b', 2 => 'c', 3 => 'd', 4 => 'e' );
Then you can look up the "key" as you call it using:
my $key = $numbers{$x};
Note that rand( $x ); returns a number greater than or equal to zero and less than $x. So if you want integers in the range 1-5, you must add 1 in your code: at the moment you'll get 0-4, not 1-5.
Firstly, arrays don't have keys (well, they kind of do, but they're integers and not the values you want). So I think you want a hash, not an array.
my %numbers = (a =>1, b=> 2, c => 3, d =>4, e => 5);
And if you want to get the letter, given the integer then you need the reverse of this hash:
my %rev_numbers = %numbers;
Note that reversing a hash like this only works if the values in your original hash are unique (because reversing a hash makes the values into keys and hash keys are always unique).
Then, you can just look up an integer in your %rev_hash to get its associated letter.
my $integer = 3;
say $rev_numbers{$integer}; # prints 'c'

How to manipulate output in perl

I'm new to perl and I can't find whether I can manipulate the output format in perl or not.
for a code like
print "$arOne[i] => $arTwo[i]\n";
I want the oputput to be like
8 => 9
10 => 25
7 => 456
If it is possible, then how to do it?
You want to use printf.
printf ("%2d => %-3d\n", $arOne[$i], $arTwo[$i]);
The formatting instructions are embedded between the % and a letter. In your case, you print numbers, so you need the letter d. The number left to the d specifies how many digits you want to reserve for the number. In your case, I made the assumption that the left number consists of at most two digits, while the right number consists of at most three digits. That might vary. Finally, the - in front of the 3d tells printf to left (rather than right) align the number.
In the spirit of TMTOWTDI-ness, there's also the old facility of perl formats:
#! /usr/bin/perl
use strict;
use warnings;
use List::MoreUtils qw(each_array);
my #arOne = (8, 10, 7);
my #arTwo = (9, 25, 456); # #arTwoDeeTwo ? #ceeThreePO ?
my ($one, $two);
format STDOUT =
#> => #<<
$one,$two
.
# Now write to the format we described above
my $next_pair = each_array(#arOne, #arTwo);
while (($one, $two) = $next_pair->()) {
write;
}
UPDATE
Note that this "report generation" capability is little-used in contemporary perl programming. The printf suggestion is typically more flexible (and less surprising). It seemed a pity, however, not to mention formats in perl in question about formatting in perl.

Comparing two hashes in perl using ne

I am trying to understand a piece of code in perl, but I am having some trouble with it being sort of new to perl programming.
I have two hashes, which are being input the same (key,value) pairs in the same order in different iterations of a for loop.
Iteration 1 creates %hash1, and Iteration 2 creates %hash2.
%hash1 = (1 => 10, 2 => 20, 3=> 30);
%hash2 = (1 => 10, 2 => 20, 3=> 30);
Then a command that compares these: goes as,
if (%hash1 ne %hash2) {print "Not Equal"; die;}
My question is:
(1) What exactly is compared in the above if statement?
(2) I tried assigning,
my $a = %hash1; my $b = %hash2;
But these give me outputs like 3/8!
What could that be?
Any help would be greatly appreciated.
ne is the string comparison operator. It's operands are strings, and thus scalars. From perldata,
If you evaluate a hash in scalar context, it returns false if the hash is empty. If there are any key/value pairs, it returns true; more precisely, the value returned is a string consisting of the number of used buckets and the number of allocated buckets, separated by a slash.
So it's comparing that both hashes have the same number of used buckets and that both hashes have the same number of allocated buckets.
One way to compare the hashes would be to stringify them using JSON:XS with canonical set.
JSON::XS->new->canonical(1)->encode(\%hash)
There is a Module Data::Compare available for comparing hashes on CPAN. This works as follows:
use Data::Compare; # exports subroutine: Compare() !
...
my %hash1 = (1 => 10, 2 => 20, 3 => 30);
my %hash2 = (1 => 10, 2 => 20, 3 => 30);
# This won't work:
# if (%hash1 ne %hash2) {print "Not Equal"; die;}
# This works:
if( ! Compare(\%hash1, \%hash2) ) { print "Not Equal"; die; }
...
This is not a core module, you'll have to install it. It is also available under activeperl/windows (in their default repository).
Regards,
rbo