Problem 122

completed January 24, 2021

Comments

I initially got the wrong answer for this problem. That doesn’t happen all too often for me, so it was definitely a new and interesting challenge! What I was doing wrong initially was that I was attepting to memoize my _m closure. This wouldn’t work since a later search with the same inputs could yield a smaller result. It’s not guaranteed that each input has a single value for its output. I was able to uncover my mistake by writing tests for ever number between 1 and 200 that pass when memoized. I then ran the tests again without memoization (very slowly) and saw some fail. This is how I knew I was wrong. Instead, the solution still searches everything in a depth first manner, however, it stops and returns early if the answer would be larger than one already found. That saved enough time for the program to run in a reasonable amount of time: 45 seconds with Pypy3.

The search algorithm I used is one very similar to the ones I’ve written when doing integer partitions.

Code

def m(n):
    arr = [1] + [0] * (n - 1)
    minimal = [float('inf')]

    def _m(last, index):
        if index == minimal[0]:
            return
        elif last == n:
            minimal[0] = index
            return
        index += 1
        for val in arr[:index]:
            this = last + val
            if this > n:
                return
            arr[index] = this
            _m(this, index)

    _m(1, 0)
    return minimal[0]

def solve(n):
    total = 0
    for k in range(1, n + 1):
        total += m(k)
    return total

if __name__ == '__main__':
    import sys
    n = eval(sys.argv[1])
    print(solve(n))

Tests

import pytest
from problem import solve, m

_test_m = (
        (1, 0),
        (2, 1),
        (3, 2),
        (4, 2),
        (5, 3),
        (6, 3),
        (7, 4),
        (8, 3),
        (9, 4),
        (10, 4),
        (11, 5),
        (12, 4),
        (13, 5),
        (14, 5),
        (15, 5),
        (16, 4),
        (17, 5),
        (18, 5),
        (19, 6),
        (20, 5),
        (21, 6),
        (22, 6),
        (23, 6),
        (24, 5),
        (25, 6),
        (26, 6),
        (27, 6),
        (28, 6),
        (29, 7),
        (30, 6),
        (31, 7),
        (32, 5),
        (33, 6),
        (34, 6),
        (35, 7),
        (36, 6),
        (37, 7),
        (38, 7),
        (39, 7),
        (40, 6),
        (41, 7),
        (42, 7),
        (43, 7),
        (44, 7),
        (45, 7),
        (46, 7),
        (47, 8),
        (48, 6),
        (49, 7),
        (50, 7),
        (51, 7),
        (52, 7),
        (53, 8),
        (54, 7),
        (55, 8),
        (56, 7),
        (57, 8),
        (58, 8),
        (59, 8),
        (60, 7),
        (61, 8),
        (62, 8),
        (63, 8),
        (64, 6),
        (65, 7),
        (66, 7),
        (67, 8),
        (68, 7),
        (69, 8),
        (70, 8),
        (71, 9),
        (72, 7),
        (73, 8),
        (74, 8),
        (75, 8),
        (76, 8),
        (77, 8),
        (78, 8),
        (79, 9),
        (80, 7),
        (81, 8),
        (82, 8),
        (83, 8),
        (84, 8),
        (85, 8),
        (86, 8),
        (87, 9),
        (88, 8),
        (89, 9),
)

@pytest.mark.parametrize('n,expect', _test_m)
def test_m(n, expect):
    assert expect == m(n)

_test_solve = (
        (1, 0),
        (2, 1),
        (3, 3),
        (4, 5),
        (5, 8),
        (6, 11),
        (7, 15),
        (8, 18),
        (9, 22),
        (10, 26),
        (11, 31),
        (12, 35),
        (13, 40),
        (14, 45),
        (15, 50),
        (20, 75),
        (25, 104),
        (30, 135),
)

@pytest.mark.parametrize('n,expect', _test_solve)
def test_solve(n, expect):
    assert expect == solve(n)