lib/more_math/histogram.rb



module MoreMath
  # A histogram gives an overview of a sequence's elements.
  class Histogram
    Bin = Struct.new(:left, :right, :count)

    # Create a Histogram for the elements of +sequence+ with +bins+ bins.
    def initialize(sequence, bins = 10)
      @sequence = sequence
      @bins = bins
      @result = compute
    end

    # Number of bins for this Histogram.
    attr_reader :bins

    # Return the computed histogram as an array of Bin objects.
    def to_a
      @result
    end

    def each_bin(&block)
      @result.each(&block)
    end

    def counts
      each_bin.map(&:count)
    end

    # Display this histogram to +output+, +width+ is the parameter for
    # +prepare_display+
    def display(output = $stdout, width = 50)
      d = prepare_display(width)
      for l, bar, r in d
        output << "%11.5f -|%s\n" % [ (l + r) / 2.0, "*" * bar ]
      end
      output << "max_count=#{max_count}\n"
      self
    end

    def max_count
      counts.max
    end

    private

    # Returns an array of tuples (l, c, r) where +l+ is the left bin edge, +c+
    # the +width+-normalized frequence count value, and +r+ the right bin
    # edge. +width+ is usually an integer number representing the width of a
    # histogram bar.
    def prepare_display(width)
      factor = width.to_f / max_count
      @result.reverse_each.map { |bin| [ bin.left, (bin.count * factor).round, bin.right ] }
    end

    # Computes the histogram and returns it as an array of tuples (l, c, r).
    def compute
      @sequence.empty? and return []
      last_r = -Infinity
      min = @sequence.min
      max = @sequence.max
      step = (max - min) / bins.to_f
      Array.new(bins) do |i|
        l = min + i  * step
        r = min + (i + 1) * step
        c = 0
        @sequence.each do |x|
          x > last_r and (x <= r || i == bins - 1) and c += 1
        end
        last_r = r
        Bin.new(l, r, c)
      end
    end
  end
end