
# SUMMARY NOTES
# -------------
# => 

=pod Letter

Here's my take on Regular Quiz #12. It's pretty straightforward. First
it finds the minimum and maximum numbers in the list, uses those to
find the final height of the chart, and that to find the scaling factor.
It uses the number of items in the list to determine the width scaling
factor. Then for each number in the list, it goes through the two arrays
that hold the strings, appending either the bar character or a space,
depending on how high the bar goes. Then it returns the strings.
Notes:
  * All heights are scaled. The biggest number's bar will reach the full
     chart height, and all others will be scaled to relative size.
  * Widths are also scaled. Unfortunately, using a width that is not a
     multiple of the number of items can make the chart look very odd.
  * Negative numbers are handled, even though the quiz spec did not
     mention them and the quiz example did not use them. I'm not sure
     if the definition of "histogram" says it will only include positive
     numbers, but since it was so easy to add support for negatives once
     I had the algorithm, I did.
  * It actually uses the first character of $HISTOGRAM_CHAR instead of
     either assuming the length is 1 or dying if it's longer.

=cut

use strict;
use warnings;

# print histogram(1, -4, 2, 8, 5, -7);

1;

##
# Functn: histogram
# Params: list of bar heights
# Return: list of strings forming the chart
# Descrp: This function accepts a list of numbers representing individual
#         bar heights and returns a list of strings that, when printed,
#         will form a chart of bars of the requested heights.
##
sub histogram {
  # Find max and min
  my ($max, $min) = (0, 0); # Initialize to zero b/c we only care about
                            # both if they have opposite signs
  for my $num (@_) {
    $max = $num if $num > $max;
    $min = $num if $num < $min;
  }
  my $height = $max - $min;
  my $width  = $#_ + 1;

  # Set some reasonable defaults if global parameters not defined
  my ($global_height, $global_width, $global_char);
  {
    # Shut off strict for a bit so Perl doesn't complain if these aren't
    # defined.
    no strict 'vars';
    $global_height = defined $HISTOGRAM_HEIGHT ? $HISTOGRAM_HEIGHT :
$height;
    $global_width  = defined $HISTOGRAM_WIDTH  ? $HISTOGRAM_WIDTH  : $width;
    $global_char   = defined $HISTOGRAM_CHAR   ? 
                     substr  $HISTOGRAM_CHAR,0,1 : '*';
  }

  # Now find the scale amounts
  my $yscale = $global_height / $height;
  my $xscale = $global_width  / $width;

  # Main loop for building the strings
  
  # Separate positive and negative bars so we don't have to do weird
  # height calcs when adding new bars. Initialize to the heights we want.
  my (@positive, @negative);
  my $pos_scaled_height = round($max * $yscale);
  my $neg_scaled_height = round($min * $yscale * -1);
  $positive[ $pos_scaled_height - 1 ] = undef if $pos_scaled_height > 0;
  $negative[ $neg_scaled_height - 1 ] = undef if $neg_scaled_height > 0;

  for my $num (@_) {
    if ($num > 0) {
      # Add a bar to the positive side, and a blank area in the negative
      make_bar(\@positive,
               round($num * $yscale),
	       round($xscale),
	       $global_char);
      make_bar(\@negative, 0, round($xscale), $global_char);
    }
    elsif ($num < 0) {
      # Add a bar to the negative side, and a blank area in the positive
      make_bar(\@negative,
               round($num * -1 * $yscale),
	       round($xscale),
	       $global_char);
      make_bar(\@positive, 0, round($xscale), $global_char);
    }
    else {
      # Add a blank area on both sides so we show nothing
      make_bar(\@positive, 0, round($xscale), $global_char);
      make_bar(\@negative, 0, round($xscale), $global_char);
    }
  }

  # Because of the way we built the charts ("downward"), we need to
  # reverse the direction of the positives before we return.
  return 
    map { $_ . "\n" }
        (reverse(@positive), @negative);
}

##
# Functn: round
# Params: number
# Return: number
# Descrp: A bit overkill, but I thought all the sprintf's looked worse. It
#         just takes a number and uses sprintf to round it to an integer.
##
sub round {
  sprintf('%.0f', $_[0]);
}

##
# Functn: make_bar
# Params: ref to array holding strings,
#         bar height,
#         bar width,
#         char to use
# Return: nothing
# Descrp: This function goes through the each line in the given array,
#         appending the given character until we reach the correct height,
#         and spaces thereafter.
##
sub make_bar {
  my ($strings, $height, $width, $char) = @_;
  my $i = 0;

  for my $line ( @$strings ) {
    $line .= ($i < $height ? $char : ' ') x $width;
    $i++;
  }
}

