lib/inch/utils/weighted_list.rb

Summary

Maintainability
A
0 mins
Test Coverage
module Inch
  module Utils
    class WeightedList
      # Trims down a list of object lists to given sizes.
      # If there are not enough objects in any particular tier,
      # they are filled up from the tier below/above.
      #
      # @example
      #
      #   list = [
      #     [:B1, :B2],
      #     [:C1, :C2, :C3, :C4, :C5, :C6, :C7, :C8, :C9, :C10],
      #     [:U1, :U2, :U3, :U4, :U5, :U6, :U7, :U8, :U9, :U10]
      #   ]
      #   counts = [4, 8, 8]
      #
      # This means there should be 4 +:B+s, 8 +:C+s, and 8 +:U+s in the
      # resulting list. But we only have 2 +:B+s, so the result is filled up
      # with +:U+s:
      #
      #   WeightedList.new(list, counts).to_a
      #   # => [
      #       [:B1, :B2],
      #       [:C1, :C2, :C3, :C4, :C5, :C6, :C7, :C8],
      #       [:U1, :U2, :U3, :U4, :U5, :U6, :U7, :U8, :U9, :U10]
      #     ]
      #
      # @param list_of_lists [Array<Array>]
      # @param counts [Array<Fixnum>]
      def initialize(list_of_lists, counts)
        @original = list_of_lists
        @max_counts = counts
        compute_list
      end

      # @return [Array]
      def to_a
        @list
      end

      private

      def compute_list
        @list = init_empty_list(@original.size)
        still_missing = fill_list_with_predefined_sizes

        # all sublists are now filled up to their max capacity but we might
        # have missed some objects (+still_missing+), because there weren't
        # enough

        @original.reverse_each.with_index do |sublist, rear_index|
          if still_missing > 0
            index = @original.size - rear_index - 1
            present_count = @max_counts[index]
            not_yet_in_list = sublist[present_count..-1]
            if not_yet_in_list
              put_in_list = not_yet_in_list[0...still_missing]
              @list[index].concat put_in_list
              still_missing -= put_in_list.size
            end
          end
        end
      end

      def init_empty_list(sublist_count)
        list = []
        sublist_count.times { list << [] }
        list
      end

      def fill_list_with_predefined_sizes
        missing = 0
        @original.each_with_index do |sublist, index|
          target_count = @max_counts[index]
          @list[index].concat sublist[0...target_count]

          next unless sublist.size < target_count
          missing += target_count - sublist.size
        end
        missing
      end
    end
  end
end