Re: [QUIZ] Perl 'Easy' Quiz of the Week #2006-04-02 - Rounded Fractions

"John J. Trammell" <[email protected]> Wed, 5 Apr 2006 10:23:48 -0500
Newsgroups gmane.comp.lang.perl.qotw.discuss
Message-ID <[email protected]>
--d6Gm4EdcadzBjdND
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

Solution is attached.

-- 
ROCK ON code warrior!!!

--d6Gm4EdcadzBjdND
Content-Type: text/x-perl; charset=us-ascii
Content-Disposition: attachment; filename="round.pl"

#!/usr/bin/perl -l
# $Id: round.pl,v 1.6 2006/04/05 15:18:46 trammell Exp $

# usage:
#    perl round.pl                  // runs test suite
#    perl round.pl 'ratio' 'quant'  // multiplies r * q with rounding
#
# NOTE: this code only works with positive ratios and quantities.

use strict;
use warnings;
use Data::Dumper;

@ARGV ? print(doit(@ARGV)) : runtests();

# apply ratio to quantity
sub doit {
    my ($ratio, $quantity) = @_;
    return f2s(s2f($ratio) * s2f($quantity));
}

# convert string to float safely (uses string eval-yikes!)
sub s2f {
    (my $x = $_[0]) =~ y/+0-9\/ //cd;
    $x =~ y/ /+/;
    return eval $x;
}

# convert float to string
sub f2s {
    my $i = int($_[0]);
    my $f = $_[0] - $i;
    my ($den, $best, %num) = (0, 10);
    for (2..4) {
        my $num = $num{$_} = bestnum($f,$_);
        my $delta = abs( $f - $num/$_ );
        ($den, $best) = ($_,$delta) if $delta < $best;
    }
    ($i, $num{$den}) = ($i+1, 0) if $den == $num{$den};
    my @out;
    push(@out,"$i")               if $i;
    push(@out, "$num{$den}/$den") if $num{$den};
    @out = ("0") if @out == 0;
#   warn Data::Dumper->Dump([\@out],['*out']);
    return join ' ', @out;
}

# return best numerator so that $numer/$denom = $frac
sub bestnum {
    my ($frac, $denom) = @_;
    sprintf '%.0f',  $frac * $denom;
}

# test suite
sub runtests {

    # test data
    my @test = (
        # ratio, quantity, correct product
        [      1, '1 1/2' ],
        [      1, '2 3/4' ],
        [      1, '3 6/8' => '3 3/4' ],
        [      2, '1/2'   => '1'     ],
        [      2, '2/3'   => '1 1/3' ],
        [      2, '2/5'   => '3/4'   ],
        [      2, '2/7'   => '1/2'   ],  # 0.57
        [      3, '3/7'   => '1 1/4' ],  # 1.29
        [  '5/3', '7/4'   => '3'     ],  # 2.92
        [ '3/14', '7/8'   => '1/4'   ],  # 0.1875
    );

    foreach my $i (0 .. $#test) {
        my $ratio   = $test[$i][0];
        my $quant   = $test[$i][1];
        my $product = $test[$i][2] || $quant;
        my $out     = doit($ratio, $quant);
        (print("ok $i"), next) if $out eq $product;
        print "not ok $i";
        warn "$i: error converting '$ratio' * '$quant' => '$product' (got '$out')\n";
    }
}

__END__

Date: Sun, 2 Apr 2006 13:21:17 -0700
From: Yitzchak Scott-Thoennes <[email protected]>
To: Perl Quiz of the Week <[email protected]>
Subject: [QUIZ] Perl 'Easy' Quiz of the Week #2006-04-02 - Rounded Fractions
Message-ID: <[email protected]>

IMPORTANT: Please do not post solutions, hints, or other spoilers
until at least 60 hours after the date of this message.  Thanks.

Cooking often involves multiplication and rounding of fractions, at
least for those of us stuck under the tyranny of U.S. customary units.
For instance, to make oatmeal, I use 7 parts water to 4 parts
thick-cut rolled oats.  If I have 1 2/3 cups oats, about how much
water should I use?  5/3 * 7/4 = 35/12 = ~3 cups.

Your task: create a script that prompts for a ratio of two integers
and a quantity given as a whole number and/or a fraction (e.g. "1
3/5", "12", "3/7") and print out a rounded result of multiplying the
quantity by the ratio in the same format.

The result should be rounded to the nearest half, third, or fourth,
whichever is most accurate (in cases exactly between two quantities,
using the lowest denominator).  You may assume none of the integers are
overly large.


--d6Gm4EdcadzBjdND--