Formatting output with 'printf' in Perl - perl

I'm trying to format my output to look like it's in columns. I'm trying to use the printf function.
Here's what I have:
printf("%s %10s %12s %10s\n", "Qty", "Desc.", "Unit \$", "Total");
for ($he = 0; $he <= 6; $he++) {
if (#quantity[$he] != 0) {
printf("%d %10s %12.2f %10.2f\n", #quantity[$he], #selections[$he], #prices[$he], #prices[$he] * #quantity[$he])
}
}
I'm trying to make it so that the second printf inside of the if statement of the for loop lines up with the "Qty", "Desc.", "Unit \$" and "Total."

You need to use the same numbers in the two formats:
printf("%3s %10s %15s %13s\n", "Qty", "Desc.", "Unit \$", "Total");
and
printf("%3d %10s %12.2f %10.2f\n", #quantity[$he], #selections[$he], #prices[$he], #prices[$he]*#quantity[$he])
Note that 12.2 means (12 digits + 1 point + 2 digits), which is why I wrote 15 in the first format. The same goes for the 13.
Also note that you're accessing array elements incorrectly.
Instead of #quantity[$he] use $quantity[$he]. That is, replace the # with a $.

Long time ago, Perl was mainly used for formatting files. It still has these capabilities although I haven't seen it used in a program since Perl 4.x came out.
Check out the perlform documentation, the format function, and the write function.
I'd give you an example on what the code would look like except I haven't done it in years. Otherwise, use the printf statement. You can limit the size of a text field with a %-10.10s type of format. This says to left justify the string, and pad it out to 10 characters, but not more than 10 characters.
I also suggest you get a book on modern Perl. One that will teach you about references.
I've rewritten your program to use references. Notice that all of the data is now in a single array instead of spread over four separate arrays that you hope you keep the index together.
I can talk about the ENTREE of $item[1] by saying $item[1]->{ENTREE}. It's easier to read and easier to maintain.
Also note that I've changed your for loop. In yours, you had to know that you had seven items. If you added a new item, you'd have to change your loop. In mine, I use $#menu to get the last index of my menu. I then use (0..$#menu) to automatically loop from 0 to the last item in the #menu array.
And, while you're at it:
Notice the printf statement formats.
Notice the use of use strict; and use warnings;. That will catch a lot of errors.
Notice the preferred way I use parentheses and curly braces to mark off blocks of code. This is the preferred method.
And, now the program:
use strict;
use warnings;
my #menu = (
{ ENTREE => "Hamburger", PRICE => 3.49, QUANTITY => 3 },
{ ENTREE => "Frankfurter", PRICE => 2.19, QUANTITY => 0 },
{ ENTREE => "French Fries", PRICE => 1.69, QUANTITY => 0 },
{ ENTREE => "Large Coke", PRICE => 1.79, QUANTITY => 4 },
{ ENTREE => "Medium Coke", PRICE => 1.59, QUANTITY => 0 },
{ ENTREE => "Small Coke", PRICE => 1.39, QUANTITY => 0 },
{ ENTREE => "Onion Rings", PRICE => 1.19, QUANTITY => 8 },
);
printf "%-3.3s %-10.10s %-6.6s %s\n\n", 'Qty', 'Desc.', 'Unit $', 'Total';
# Use $#menu to get the number of items in the array instead of knowing it's 6
foreach my $item (0..$#menu) {
# Dereference $menu[$item] to make $menu_item a hash
# This makes the syntax easier to read.
my %menu_item = %{ $menu[$item] };
if ( $menu_item{QUANTITY} ) {
printf "%3d %-10.10s %9.2f %7.2f\n",
$menu_item{QUANTITY}, $menu_item{ENTREE}, $menu_item{PRICE},
$menu_item{QUANTITY} * $menu_item{PRICE};
}
}
OUTPUT:
Qty Desc. Unit $ Total
3 Hamburger 3.49 10.47
4 Large Coke 1.79 7.16
8 Onion Ring 1.19 9.52

Related

Formatting GD::Graph x-axis in Perl

I'm producing a graph of prices over time. Because I have dates on the x-axis, I have converted them to days since the epoch. Days since the epoch is not a very clear value to display on the graph so I want to convert them back to human readable dates using the x_number_format option.
But...it doesn't appear to be called as the graph is rendered.
I've created the following test code to demonstrate the problem.
use strict;
use GD::Graph::points;
# Generate some random data!
my #x_data;
my #y_data;
for (1...20) {
push #x_data, $_;
push #y_data, rand(20) + 10;
}
# This is never called - possible bug!
sub x_format {
print "X Formatter!\n";
return " - $_[0] - ";
}
# This gets called for every Y-axis point
sub y_format {
print "Y Formatter!\n";
return " - $_[0] - ";
}
my $graph=GD::Graph::points->new(1000,450);
$graph->set(
y_label => 'Random numbers',
y_number_format => \&y_format,
x_number_format => \&x_format,
x_label => 'Sequential meaningless numbers',
x_labels_vertical => 1,
x_plot_values => 1,
);
my #data=(
[ #x_data ],
[ #y_data ],
);
open PNG, ">temp.png";
binmode PNG;
print PNG $graph->plot(\#data)->png;
close PNG;
system("temp.png");
This test code produces a graph as expected and prints Y Formatter! 6 times. One for each point on the y-axis. However, it doesn't print X Formatter! and doesn't format the x-axis.
I have tried formatting the x-axis values more directly with
x_number_format => sub { " - $_[0] - " },
That doesn't format the x-axis either.
Am I doing something glaring stupid or is this a bug in GD:Graph?
There are no bug reports for this issue in the GD::Graph bug page
By inspecting the source, I can see that you need to set
x_tick_number to a defined value for the x_number_format callback to be called.
So you can try something like this:
$graph->set(
y_label => 'Random numbers',
y_number_format => \&y_format,
x_number_format => \&x_format,
x_tick_number => 6,
x_label => 'Sequential meaningless numbers',
x_labels_vertical => 1,
x_plot_values => 1,
);
According to the documentation:
x_tick_number
If set to 'auto', GD::Graph will attempt to format the X
axis in a nice way, based on the actual X values. If set to a number,
that's the number of ticks you will get. If set to undef, GD::Graph
will treat X data as labels. Default: undef.

How do I sort an array of strings given an arbitrary ordering of those strings?

I wish to sort an array of strings so that the strings wind up in the following order:
#set = ('oneM', 'twoM', 'threeM', 'sixM', 'oneY', 'twoY', 'oldest');
As you may notice, these represent time periods so oneM is the first month, etc. My problem is that I want to sort by the time period, but with the strings as they are I can't just use 'sort', so I created this hash to express how the strings should be ordered:
my %comparison = (
oneM => 1,
twoM => 2,
threeM => 3,
sixM => 6,
oneY => 12,
twoY => 24,
oldest => 25,
);
This I was hoping would make my life easier where I can do something such as:
foreach my $s (#set) {
foreach my $k (%comparison) {
if ($s eq $k) {
something something something
I'm getting the feeling that this is a long winded way of doing things and I wasn't actually sure how I would actually sort it once I've found the equivalent... I think I'm missing my own plot a bit so any help would be appreciated
As requested the expected output would be like how it is shown in #set above. I should have mentioned that the values in #set will be part of that set, but not necessarily all of them and not in the same order.
You've choose good strategy in precomputing data to form easy to sort. You can calculate this data right inside sorting itself, but then you'd be wasting time for recalculation each time sort needs to compare value, which happens more than once through process. On the other hand, the drawback of cache is, obviously, that you'd need additional memory to store it and it might slow down your sort under low memory condition, despite doing less calculations overall.
With your current set up sorting is as easy as:
my #sorted = sort { $comparison{$a} <=> $comaprison{$b} } #set;
While if you want to save memory at expense of CPU it'd be:
my #sorted = sort { calculate_integer_based_on_input{$a} <=> calculate_integer_based_on_input{$b} } #set;
with separate calculate_integer_based_on_input function that would convert oneY and the like to 12 or other corresponding value on the fly or just inline conversion of input to something suitable for sorting.
You might also want to check out common idioms for sorting with caching computations, like Schwartzian transform and Guttman Rosler Transform.
Giving an example with the input and you expected result would help. I guess that this is what you are looking for:
my #data = ( ... );
my %comparison = (
oneM => 1, twoM => 2, threeM => 3,
sixM => 6, oneY => 12, twoY => 24,
oldest => 25,
);
my #sorted = sort { $comparison{$a} <=> $comaprison{$b} } #data;
There are plenty of examples in the documentation for the sortfunction in the perlfunc manual page. ("perldoc -f sort")

How can I implement the Gale-Shapley stable marriage algorithm in Perl?

Problem statement:
We have equal number of men and women. Each man has a preference score toward each woman. So do the woman for each man. Each of the men and women have certain interests. Based on the interest, we calculate the preference scores.
So initially, we have an input in a file having x columns. The first column is the person (man/woman) id. Ids are nothing but numbers from 0 ... n. (First half are men and next half women). The remaining x-1 columns will have the interests. These are integers too.
Now, using this n by x-1 matrix, we have come up with an n by n/2 matrix. The new matrix has all men and woman as their rows and scores for opposite sex in columns.
We have to sort the scores in descending order, also we need to know the id of person related to the scores after sorting.
So, here I wanted to use hash table.
Once we get the scores we need to make up pairs, for which we need to follow some rules.
My trouble is with the second matrix of n by n/2 that needs to give information of which man/woman has how much preference on a woman/man. I need these scores sorted so that I know who is the first preferred woman/man, 2nd preferred and so on for a man/woman.
I hope to get good suggestions on the data structures I use. I prefer PHP or Perl.
NB:
This is not homework. This is a little modified version of stable marriage algorithm. I have a working solution. I am only working on optimizing my code.
It is very similar to stable marriage problem but here we need to calculate the scores based on the interests they share. So, I have implemented it as the way you see in the wiki page http://en.wikipedia.org/wiki/Stable_marriage_problem.
My problem is not solving the problem. I solved it and can run it. I am just trying to have a better solution. So I am asking suggestions on the type of data structure to use.
Conceptually I tried using an array of hashes. where the array index give the person id and the hash in it gives the ids <=> scores in sorted manner. I initially start with an array of hashes. Now, I sort the hashes on values, but I could not store the sorted hashes back in an array. So just stored the keys after sorting and used these to get the values from my initial unsorted hashes.
Can we store the hashes after sorting?
Can you suggest a better structure?
I think the following implements the Gale-Shapley algorithm where each person's preference ordering is given as an array of scores over the members of the opposite sex.
As an aside, I just found out that David Gale passed away (see his Wikipedia entry — he will be missed).
The code is wordy, I just quickly transcribed the algorithm as described on Wikipedia and did not check original sources, but it should give you an idea of how to use appropriate Perl data structures. If the dimensions of the problem grow, profile first before trying to optimize.
I am not going to try to address the specific issues in your problem. In particular, you did not fully flesh out the idea of computing a match score based on interests and trying to guess is bound to be frustrating.
#!/usr/bin/perl
use strict; use warnings;
use YAML;
my (%pref, %people, %proposed_by);
while ( my $line = <DATA> ) {
my ($sex, $id, #pref) = split ' ', $line;
last unless $sex and ($sex) =~ /^(m|w)\z/;
$pref{$sex}{$id} = [ map 0 + $_, #pref ];
$people{$sex}{$id} = undef;
}
while ( defined( my $man = bachelor($people{m}) ) ) {
my #women = eligible_women($people{w}, $proposed_by{$man});
next unless #women;
my $woman = argmax($pref{m}{$man}, \#women);
$proposed_by{$man}{$woman} = 1;
if ( defined ( my $jilted = $people{w}{$woman}{m} ) ) {
my $proposal_score = $pref{w}{$woman}[$man];
my $jilted_score = $pref{w}{$woman}[$jilted];
next if $proposal_score < $jilted_score;
$people{m}{$jilted}{w} = undef;
}
$people{m}{$man}{w} = $woman;
$people{w}{$woman}{m} = $man;
}
print Dump \%people;
sub argmax {
my ($pref, $candidates) = #_;
my ($ret) = sort { $pref->[$b] <=> $pref->[$a] } #$candidates;
return $ret;
}
sub bachelor {
my ($men) = #_;
my ($bachelor) = grep { not defined $men->{$_}{w} } keys %$men;
return $bachelor;
}
sub eligible_women {
my ($women, $proposed_to) = #_;
return grep { not defined $proposed_to->{$_} } keys %$women;
}
__DATA__
m 0 10 20 30 40 50
m 1 50 30 40 20 10
m 2 30 40 50 10 20
m 3 10 10 10 10 10
m 4 50 40 30 20 10
w 0 50 40 30 20 10
w 1 40 30 20 10 50
w 2 30 20 10 50 40
w 3 20 10 50 40 30
w 4 10 50 40 30 20

How can I filter a Perl DBIx recordset with 2 conditions on the same column?

I'm getting my feet wet in DBIx::Class — loving it so far.
One problem I am running into is that I want to query records, filtering out records that aren't in a certain date range.
It took me a while to find out how to do a <= type of match instead of an equality match:
my $start_criteria = ">= $start_date";
my $end_criteria = "<= $end_date";
my $result = $schema->resultset('MyTable')->search(
{
'status_date' => \$start_criteria,
'status_date' => \$end_criteria,
});
The obvious problem with this is that since the filters are in a hash, I am overwriting the value for "status_date", and am only searching where the status_date <= $end_date. The SQL that gets executed is:
SELECT me.* from MyTable me where status_date <= '9999-12-31'
I've searched CPAN, Google and SO and haven't been able to figure out how to apply 2 conditions to the same column. All documentation I've been able to find shows how to filter on more than 1 column, but not 2 conditions on the same column.
I'm sure I'm missing something obvious. Can someone here point it out to me?
IIRC, you should be able to pass an array reference of multiple search conditions (each in its own hashref.) For example:
my $result = $schema->resultset('MyTable')->search(
[ { 'status_date' => \$start_criteria },
{ 'status_date' => \$end_criteria },
]
);
Edit: Oops, nervermind. That does an OR, as opposed to an AND.
It looks like the right way to do it is to supply a hashref for a single status_date:
my $result = $schema->resultset('MyTable')->search(
{ status_date => { '>=' => $start_date, '<=' => $end_date } }
);
This stuff is documented in SQL::Abstract, which DBIC uses under the hood.
There is BETWEEN in SQL and in DBIx::Class it's supported:
my $result = $schema->resultset('MyTable')
->search({status_date => {between => [$start_date,$end_date]}});

How can I generate all subsets of a list in Perl?

I have a mathematical set in a Perl array: (1, 2, 3). I'd like to find all the subsets of that set: (1), (2), (3), (1,2), (1,3), (2,3).
With 3 elements this isn't too difficult but if set has 10 elements this gets tricky.
Thoughts?
You can use Data::PowerSet like Matthew mentioned. However, if, as indicated in your example, you only want proper subsets and not every subset, you need to do a little bit more work.
# result: all subsets, except {68, 22, 43}.
my $values = Data::PowerSet->new({max => 2}, 68, 22, 43);
Likewise, if you want to omit the null set, just add the min parameter:
# result: all subsets, except {} and {68, 22, 43}.
my $values = Data::PowerSet->new({min => 1, max => 2}, 68, 22, 43);
Otherwise, to get all subsets, just omit both parameters:
# result: every subset.
my $values = Data::PowerSet->new(68, 22, 43);
See Data::PowerSet, http://coding.derkeiler.com/Archive/Perl/comp.lang.perl/2004-01/0076.html , etc.
Since you say "mathematical set", I assume you mean there are no duplicates.
A naive implementation that works for up to 32 elements:
my $set = [1,2,3];
my #subsets;
for my $count ( 1..(1<<#$set)-2 ) {
push #subsets, [ map $count & (1<<$_) ? $set->[$_] : (), 0..$#$set ];
}
(For the full range of subsets, loop from 0 to (1<<#$set)-1; excluding 0 excludes the null set, excluding (1<<#$set)-1 excludes the original set.)
Update: I'm not advocating this over using a module, just suggesting it in case you are looking to understand how to go about such a problem. In general, each element is either included or excluded from any given subset. You want to pick an element and generate first all possible subsets of the other elements not including your picked element and then all possible subsets of the other elements including your picked element. Recursively apply this to the "generate all possible subsets". Finally, discard the null subset and the non-proper subset. In the above code, each element is assigned a bit. First all subsets
are generated with the high bit on, then all those with it off. For each of those alternatives, subsets are generated first with the next-to-highest bit off, then on. Continuing this until you are just working on the lowest bit, what you end up with is all the possible numbers, in order.
If you don't want to use an existing module or can't then you can simply code your own subset generation algorithm using a bit-mask and a binary counter. Sample code follows -
#!/usr/bin/perl
use strict;
use warnings;
my #set = (1, 2, 3);
my #bitMask = (0, 0, 0); #Same size as #set, initially filled with zeroes
printSubset(\#bitMask, \#set) while ( genMask(\#bitMask, \#set) );
sub printSubset {
my ($bitMask, $set) = #_;
for (0 .. #$bitMask-1) {
print "$set->[$_]" if $bitMask->[$_] == 1;
}
print"\n";
}
sub genMask {
my ($bitMask, $set) = #_;
my $i;
for ($i = 0; $i < #$set && $bitMask->[$i]; $i++) {
$bitMask->[$i] = 0;
}
if ($i < #$set) {
$bitMask->[$i] = 1;
return 1;
}
return 0;
}
Note: I haven't been able to test the code, some bugs might need to be ironed out.
Use Algorithm::ChooseSubsets.
It's a counting problem - for N elements there are exactly 2^N subsets and you have to count from 0 to 2^N - 1 in binary to list them all.
For eg 3 items there are 8 possible subsets: 000, 001, 010, 011, 100, 101, 110 and 111 - the numbers show which members are present.