# 
# This is my first attempt at one of the quizzes. So far as I can tell, it
# works.
# 
# But I felt pretty clever when I decided to multiply my argument numbers
# according to the decimal precision desired. I used "working with money"
# as my analogy; the usual advice is to use integer cents instead of
# floating-point dollars in order to avoid rounding problems. Multiplying
# the argument numbers here let me use increment/decrement to do
# adjustments, instead of worrying about floating-point decimals. But my
# experience has been that when I feel clever about something I've
# written, I'm doing something that will confuse the maintenance
# programmer.
# 
# I appreciate any comments/criticisms/suggestions. Even on matters of
# style (like the if/elsif/else block where else is a no-op), I'm always
# looking for ways to learn and improve.
# 
# (The code after the __DATA__ token is what I used to test my sub against
# the quiz description. Feel free to critique that as well.)
# 
# Thank you.


#!/usr/local/bin/perl
# csw_qotw7.pl: my attempt at Quiz of the Week #7
use strict;
use warnings;
use List::Util qw(sum);

sub fudge_numbers {
    my $precision = shift;
    my @arguments = @_;

    return 100 if @arguments == 1;

    # I want to work with integers
    my $multiplier = 10**($precision + 2);

    my @percentages = map {
        sprintf("%.0f", $_ * $multiplier / (sum @arguments))
    } @arguments;

    # If something has to be added to reach 100, add it to the "front"
    # elements of the array. If something has to be subtracted, subtract
    # from the "end" elements.
    my     $difference = $multiplier - (sum @percentages);
    if    ($difference > 0) { ++$_ for @percentages[0..$difference-1] }
    elsif ($difference < 0) { --$_ for @percentages[-1..$difference] }
    else                    { } # $difference == 0

    # Convert from integers ("1234") to "percentage format" ("12.34")
    return map {
        sprintf("%.${precision}f", $_ / 10**$precision)
    } @percentages;
}

__DATA__

use Array::Compare;
use Test::Simple tests => 6;
{
    my @tests = (
        [ [ 1, 100, 100, 100 ], [  33.4,  33.3,  33.3  ] ],
        [ [ 0, 100, 100, 100 ], [  34,    33,    33    ] ],
        [ [ 2, 100, 100, 100 ], [  33.34, 33.33, 33.33 ] ],
        [ [ 2,   7,   7,   7 ], [  33.34, 33.33, 33.33 ] ],
        [ [ 1,  30,  30,  10 ], [  42.9,  42.9,  14.2  ] ],
        [ [ 1,  42           ], [ 100                  ] ],
    );

    my $comp = Array::Compare->new;
    for my $test (@tests) {
        ok(
            $comp->compare([fudge_numbers(@{$test->[0]})], $test->[1]),
            "fudge_numbers(@{$test->[0]})",
        );
    }
}

__END__


csw
-- 
 /Ż\
 \ /   ASCII RIBBON CAMPAIGN
  X  Against HTML Mail and News
 / \
