I wrote this after I made a major mistake converting miles-per-hour to meters-per-second with a calculator. The "while(<DATA>)" can be replaced with "while(<>)" to accept command-line input.
Give the script input in the format:
Item1 = ## Item2
(e.g., inch = 2.54 cm)
Query conversions with either:
Item1 = ? Item2
(inch = ? cm)
..or..
N1/D1 = ? N2/D2
(meters/second = ? miles/hour)
#!/usr/bin/perl
use strict;
use warnings;
# All primes less than 4096, used in factorization functions
my @primes =
(2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,9
+7,101,
103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,19
+3,197,
199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,30
+7,311,
313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,42
+1,431,
433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,54
+7,557,
563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,65
+9,661,
673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,79
+7,809,
811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,92
+9,937,
941,947,953,967,971,977,983,991,997,1009,1013,1019,1021,1031,1033,1039
+,1049,
1051,1061,1063,1069,1087,1091,1093,1097,1103,1109,1117,1123,1129,1151,
+1153,
1163,1171,1181,1187,1193,1201,1213,1217,1223,1229,1231,1237,1249,1259,
+1277,
1279,1283,1289,1291,1297,1301,1303,1307,1319,1321,1327,1361,1367,1373,
+1381,
1399,1409,1423,1427,1429,1433,1439,1447,1451,1453,1459,1471,1481,1483,
+1487,
1489,1493,1499,1511,1523,1531,1543,1549,1553,1559,1567,1571,1579,1583,
+1597,
1601,1607,1609,1613,1619,1621,1627,1637,1657,1663,1667,1669,1693,1697,
+1699,
1709,1721,1723,1733,1741,1747,1753,1759,1777,1783,1787,1789,1801,1811,
+1823,
1831,1847,1861,1867,1871,1873,1877,1879,1889,1901,1907,1913,1931,1933,
+1949,
1951,1973,1979,1987,1993,1997,1999,2003,2011,2017,2027,2029,2039,2053,
+2063,
2069,2081,2083,2087,2089,2099,2111,2113,2129,2131,2137,2141,2143,2153,
+2161,
2179,2203,2207,2213,2221,2237,2239,2243,2251,2267,2269,2273,2281,2287,
+2293,
2297,2309,2311,2333,2339,2341,2347,2351,2357,2371,2377,2381,2383,2389,
+2393,
2399,2411,2417,2423,2437,2441,2447,2459,2467,2473,2477,2503,2521,2531,
+2539,
2543,2549,2551,2557,2579,2591,2593,2609,2617,2621,2633,2647,2657,2659,
+2663,
2671,2677,2683,2687,2689,2693,2699,2707,2711,2713,2719,2729,2731,2741,
+2749,
2753,2767,2777,2789,2791,2797,2801,2803,2819,2833,2837,2843,2851,2857,
+2861,
2879,2887,2897,2903,2909,2917,2927,2939,2953,2957,2963,2969,2971,2999,
+3001,
3011,3019,3023,3037,3041,3049,3061,3067,3079,3083,3089,3109,3119,3121,
+3137,
3163,3167,3169,3181,3187,3191,3203,3209,3217,3221,3229,3251,3253,3257,
+3259,
3271,3299,3301,3307,3313,3319,3323,3329,3331,3343,3347,3359,3361,3371,
+3373,
3389,3391,3407,3413,3433,3449,3457,3461,3463,3467,3469,3491,3499,3511,
+3517,
3527,3529,3533,3539,3541,3547,3557,3559,3571,3581,3583,3593,3607,3613,
+3617,
3623,3631,3637,3643,3659,3671,3673,3677,3691,3697,3701,3709,3719,3727,
+3733,
3739,3761,3767,3769,3779,3793,3797,3803,3821,3823,3833,3847,3851,3853,
+3863,
3877,3881,3889,3907,3911,3917,3919,3923,3929,3931,3943,3947,3967,3989,
+4001,
4003,4007,4013,4019,4021,4027,4049,4051,4057,4073,4079,4091,4093);
my %definitions = ();
my $define_re = qr!^(\w+)=([\d./]+)(\w+)$!;
my $find_re = qr/^(\w+)=\?(\w+)$/;
my $ratio_re = qr!^(\w+)/(\w+)=\?(\w+)/(\w+)$!;
sub shrink {
my ($num,$den) = @_;
while ($num != int($num) || $den != int($den)) { $num *= 10; $den
+*= 10; }
for my $p (@primes) {
my $lim = $num > $den ? int(sqrt($num)) : int(sqrt($den));
last if $p > $lim;
while ($num % $p == 0 && $den % $p == 0) { $num /= $p; $den /=
+ $p; }
}
return ($num,$den);
}
sub num_den {
my $value = shift;
return ($value,1) unless $value =~ m!^([^/]+)/([^/]+)!;
return ($1,$2);
}
sub list_definitions {
for (keys %definitions) {
print "Unit : $_\n";
my $tmp = $definitions{$_};
for (keys %$tmp) {
my $num = $$tmp{$_}{num};
my $den = $$tmp{$_}{den};
print "= $num/$den $_ (" . $num/$den . ")\n";
}
print "\n";
}
}
sub add_item {
my ($unit1,$unit2,$num,$den) = @_;
($num,$den) = shrink ($num,$den);
$definitions{$unit1}{$unit2}{num} ||= $num;
$definitions{$unit1}{$unit2}{den} ||= $den;
$definitions{$unit2}{$unit1}{num} ||= $den;
$definitions{$unit2}{$unit1}{den} ||= $num;
}
sub add_definition {
my ($unit1, $multiple, $unit2) = @_;
my ($num,$den) = num_den($multiple);
add_item($unit1,$unit2,$num,$den);
my $growth = 1;
while ($growth) {
$growth = 0;
for $unit1 (keys %definitions) {
for $unit2 (keys %{$definitions{$unit1}}) {
$num = $definitions{$unit1}{$unit2}{num};
$den = $definitions{$unit1}{$unit2}{den};
for my $key (keys %{$definitions{$unit2}}) {
next if ($definitions{$key}{$unit1} || $key eq $un
+it1);
$growth++;
my $n = $definitions{$unit2}{$key}{num};
my $d = $definitions{$unit2}{$key}{den};
add_item($key, $unit1, $den*$d, $num*$n);
}
}
}
}
print "$unit1/$unit2 conversion added\n";
}
sub seek_definition {
my ($unit1, $unit2) = @_;
if ($definitions{$unit1}{$unit2}) {
my $num = $definitions{$unit1}{$unit2}{num};
my $den = $definitions{$unit1}{$unit2}{den};
print "$unit1 = $num/$den $unit2 (" . $num/$den . ")\n";
} else {
print "Not enough information\n";
}
}
sub seek_ratio {
my ($num1, $den1, $num2, $den2) = @_;
if ($definitions{$num1}{$num2} && $definitions{$den1}{$den2}) {
my $num = $definitions{$num1}{$num2}{num} * $definitions{$den1
+}{$den2}{den};
my $den = $definitions{$num1}{$num2}{den} * $definitions{$den1
+}{$den2}{num};
($num, $den) = shrink($num, $den);
print "$num1/$den1 = $num/$den $num2/$den2 (" . $num/$den . ")
+\n";
} else {
print "Not enough information\n";
}
}
print ': ';
while(<DATA>) {
$_ = lc $_;
s/\s+//g;
add_definition ($1, $2, $3) if (/$define_re/);
seek_definition($1, $2) if /$find_re/;
seek_ratio($1, $2, $3, $4) if /$ratio_re/;
list_definitions if /^list$/;
%definitions = () if /^clear$/;
print ': ';
}
__DATA__
inch = 2.54 cm
foot = 12 inch
list
mile = 5280 foot
meter = 100 cm
km = 1000 meter
hour = 60 minute
minute = 60 second
mile = ? km
meter/second = ? mile/hour