pythonlistmath

Subdivide integers using a given ratio without producing floats


I need to subdivide a given amount of items (lets say 10) using a given ratio [0.55, 0.45]. The result here should either be 6:4 or 5:5. The usual approach [0.55*10, 0.45*10] would result in [6, 5] (11, not 10).

Another example: divide 7 using ratio: [0.36, 0.44, 0.07, 0.07, 0.03, 0.03] which ideally should yield something like [3, 3, 1, 0, 0, 0] or [3, 3, 0, 1, 0, 0].

What would be a good approach to this problem?


Solution

  • Here's my try on the matter :) The hardest part being reversing the sort operation and matching it with results... If you don't need to keep the original order of ratios, then you can delete part of the last function.

    def scale_ratio(ratios: list) -> list:
        sum_ = sum(ratios)
        return [x/sum_ for x in ratios]
    
    def ratio_breakdown_recursive(x: int, ratios: list) -> list:
        top_ratio = ratios[0]
        part = round(x*top_ratio)
        if x <= part:
            return [x]
        x -= part
        return [part] + ratio_breakdown_recursive(x, scale_ratio(ratios[1:]))
    
    
    def ratio_breakdown(x: int, ratios: list) -> list:
        sorted_ratio = sorted(ratios, reverse=True)
        assert(round(sum(ratios)) == 1)
        sorted_result = ratio_breakdown_recursive(x, sorted_ratio)
        assert(sum(sorted_result) == x)
        # Now, we have to reverse the sorting and add missing zeros
        sorted_result += [0]*(len(ratios)-len(sorted_result))
        numbered_ratios = [(r, i) for i, r in enumerate(ratios)]
        sorted_numbered_ratios = sorted(numbered_ratios, reverse=True)
        combined = zip(sorted_numbered_ratios, sorted_result)
        combined_unsorted = sorted(combined, key=lambda x: x[0][1])
        unsorted_results = [x[1] for x in combined_unsorted]
        return unsorted_results
    

    Results:

    ratio_breakdown(7, [0.36, 0.44, 0.07, 0.07, 0.03, 0.03])
    [3, 3, 1, 0, 0, 0]
    ratio_breakdown(10, [0.55, 0.45])
    [6, 4]
    ratio_breakdown(16, [0.16, 0.47, 0.13, 0.24])
    [2, 8, 2, 4]
    

    EDIT: That's Python3.