CUFP
roboticus
<p>I'm having to buy a car, and didn't know how to compare them, so I whipped up a little thing this morning to compare cars. It could use a bit more (estimated repairs/month, etc), but it does what I want.</p>
<p>Warning: Long, hence the readmore tags, though I can't recall if readmore tags actually do any good on a "top level" node.</p>
<readmore>
<c>
#!/usr/bin/perl
#
# How much does it cost to drive a car?
#
use strict;
use warnings;
use Data::Dumper;
use Getopt::Long;
my $fuel_gal = "2.80";
my $weekly_miles=300;
my $rpt_weekly=0;
my $rpt_fuel =0;
my $result = GetOptions(
"miles=i" => \$weekly_miles,
"weekly" => \$rpt_weekly,
"fuel" => \$rpt_fuel,
"gas=s" => \$fuel_gal,
);
#####
# STEP 1: Fill out the distances between the locations you typically
# drive between. We'll build our routes from them. Only one
# one direction is required, unless you want asymmetric routes.
#####
my %Distances = (
Home => { Work => 18.1, Son=>12.4, School=>12.4, Grandma=>25.3 },
Work => { Son => 11.3, School => 12.8 },
);
fixup_Distances(); # Fill out the missing table entries
#####
# STEP 2: Define your common routes
#####
my %Routes = map {
my ($name, @locations) = @$_;
$name => { Route => \@locations }
} (
# Route_Name List of locations
[ qw( Morning Home Work ) ],
[ qw( Morning_w_Son Home School Work ) ],
[ qw( Evening Work Home ) ],
[ qw( Evening_w_Son Work Son Home ) ],
[ qw( Get_Son Home Son Home ) ],
[ qw( Visit_Grandma Home Grandma Home ) ],
);
#####
# STEP 3: Build your daily itinerary
#####
my @Itinerary = (
# DAY => qw( LIST OF ROUTES YOU TAKE THAT DAY )
[ SUN => qw( Visit_Grandma ) ],
[ MON => qw( Morning_w_Son Evening ) ],
[ TUE => qw( Morning Evening_w_Son ) ],
[ WED => qw( Morning_w_Son Evening ) ],
[ THU => qw( Morning Evening ) ],
[ FRI => qw( Morning Evening ) ],
[ SAT => qw( Get_Son ) ],
);
#####
# STEP 4: Compute your weekly minimum mileage
#####
if ($rpt_weekly) {
print "\nWEEKLY MILEAGE\n\n";
my $weekly_ttl=0;
for (@Itinerary) {
my ($day, @routes) = (@$_);
my @res;
my $ttl=0;
for (@routes) {
my $d = route_length($_);
$ttl += $d;
push @res, $_, $d;
}
printf "%s %6.2f mi (", $day, $ttl;
$weekly_ttl += $ttl;
while (@res) {
my $name=shift @res;
my $dist=shift @res;
print "$name=$dist ";
}
print ")\n";
}
printf "\nTTL %6.2f mi MINIMUM (misc trips)\n", $weekly_ttl;
}
#####
# STEP 5: Set your weekly mileage to something more reasonable
# (account for grocery shopping, visiting friends, etc.
#####
printf "\nComputing remainder of report using $weekly_miles mi/wk\n";
if ($rpt_fuel) {
my @Gas_Prices = ( 2.50, 2.75, 2.80, 2.90, 3.00, 3.10, 3.25 );
my @MPGs = ( 20, 25, 28, 30, 33, 35, 40 );
print "Fuel costs (MPG / GalPrice)\n\n";
print "MPG ";
printf "%6.2f ", $_ for @Gas_Prices;
print "\n";
for my $mpg (@MPGs) {
printf "% 3u: ", $mpg;
for my $GP (@Gas_Prices) {
printf "%6.2f ", $GP*$weekly_miles / $mpg;
}
print "\n";
}
print "\n\n";
}
#####
# STEP 6: Plug in the cars you want to compare
#####
my %Cars = (
"Optima EX" => {
Mileage => [ 26, 35 ],
Insurance => 448.70,
Price => 13998.0,
},
"PT Cruiser" => {
Mileage => [ 20, 28 ],
Insurance => 368.14,
Price => 10998.0,
},
"Solstice 2006" => {
Mileage => [ 23.1, 30.2 ],
Insurance => 230.89,
Price => 14888,
},
"Solstice 2006b" => {
Mileage => [ 23.1, 30.2 ],
Insurance => 230.89,
Price => 10995,
},
"Crossfire 2005" => {
Mileage => [ 22.9, 27.3 ],
Insurance => 230.89,
Price => 12870,
},
"Corolla 2010" => {
Mileage => [ 26, 34 ],
Insurance => 420.52,
Price => 13599,
},
"Elantra 2010" => {
Mileage => [26, 34 ],
Insurance => 403.74,
Price => 14998,
},
"Fusion 2008" => {
Mileage => [ 20, 28 ],
Insurance => 390.62,
Price => 15998,
},
);
print <<EOHDR;
Monthly cost of ownership (weekly mi=$weekly_miles, fuel=\$$fuel_gal/gal)
Car Price /mo Ins Fuel
-------------------- ------ ------ ------ -------/-------
EOHDR
for my $car (sort keys %Cars) {
my ($miMax, $miMin) = map { 4 * $fuel_gal * $weekly_miles / $_ }
@{$Cars{$car}{Mileage}}[0,1];
my $cost = $Cars{$car}{Price};
my $cost_mo = $cost/(5*12);
my $ins_mo = $Cars{$car}{Insurance}/6;
my $min = $ins_mo+$cost_mo+$miMin;
my $max = $ins_mo+$cost_mo+$miMax;
printf "%-20.20s %6u% 7.2f % 6.2f % 7.2f/%7.2f: (%6.2f / %6.2f)\n",
$car, $cost, $cost_mo, $ins_mo, $miMin, $miMax, $min, $max;
}
#----------------------------------------------------------------------------
# Miscellaneous subroutines
#----------------------------------------------------------------------------
sub fixup_Distances {
# Fill in the missing %Distances entries by reversing $from
# and $to in the table.
for my $from (keys %Distances) {
for my $to (keys %{$Distances{$from}}) {
# Don't mirror leg if it's already declared
# (allows for asymmetric routes)
next if exists($Distances{$to})
and exists($Distances{$to}{$from});
$Distances{$to}{$from} = $Distances{$from}{$to};
}
}
}
sub route_length {
my $route = shift;
die "Route '$route' not defined!\n" if ! exists $Routes{$route};
if (! exists $Routes{$route}{LEN}) {
my $ar = $Routes{$route}{Route};
my $cur = shift @$ar;
my $ttl = 0;
while (my $next = shift @$ar) {
$ttl += leg_distance($cur, $next);
$cur = $next;
}
$Routes{$route}{LEN} = $ttl;
}
return $Routes{$route}{LEN};
}
sub leg_distance {
# Distance between any two locations
my ($from, $to) = (@_);
die "Unknown location '$from'!\n" unless exists $Distances{$from};
die "Unknown location '$to'!\n" unless exists $Distances{$from}{$to};
return $Distances{$from}{$to};
}
</c>
</readmore>
<p>...[roboticus]</p>