Task: Minimal steps down to 1



C#: using System;
using System.Collections.Generic;
using System.Linq;

public static class MinimalSteps
{
    public static void Main() {
        var (divisors, subtractors) = (new int[] { 2, 3 }, new [] { 1 });
        var lookup = CreateLookup(2_000, divisors, subtractors);
        Console.WriteLine($"Divisors: [{divisors.Delimit()}], Subtractors: [{subtractors.Delimit()}]");
        PrintRange(lookup, 10);
        PrintMaxMins(lookup);
        lookup = CreateLookup(20_000, divisors, subtractors);
        PrintMaxMins(lookup);
        Console.WriteLine();

        subtractors = new [] { 2 };
        lookup = CreateLookup(2_000, divisors, subtractors);
        Console.WriteLine($"Divisors: [{divisors.Delimit()}], Subtractors: [{subtractors.Delimit()}]");
        PrintRange(lookup, 10);
        PrintMaxMins(lookup);
        lookup = CreateLookup(20_000, divisors, subtractors);
        PrintMaxMins(lookup);
    }

    private static void PrintRange((char op, int param, int steps)[] lookup, int limit) {
        for (int goal = 1; goal <= limit; goal++) {
            var x = lookup[goal];
            if (x.param == 0) {
                Console.WriteLine($"{goal} cannot be reached with these numbers.");
                continue;
            }
            Console.Write($"{goal} takes {x.steps} {(x.steps == 1 ? "step" : "steps")}: ");
            for (int n = goal; n > 1; ) {
                Console.Write($"{n},{x.op}{x.param}=> ");
                n = x.op == '/' ? n / x.param : n - x.param;
                x = lookup[n];
            }
            Console.WriteLine("1");
        }
    }

    private static void PrintMaxMins((char op, int param, int steps)[] lookup) {
        var maxSteps = lookup.Max(x => x.steps);
        var items = lookup.Select((x, i) => (i, x)).Where(t => t.x.steps == maxSteps).ToList();
        Console.WriteLine(items.Count == 1
            ? $"There is one number below {lookup.Length-1} that requires {maxSteps} steps: {items[0].i}"
            : $"There are {items.Count} numbers below {lookup.Length-1} that require {maxSteps} steps: {items.Select(t => t.i).Delimit()}"
        );
    }

    private static (char op, int param, int steps)[] CreateLookup(int goal, int[] divisors, int[] subtractors)
    {
        var lookup = new (char op, int param, int steps)[goal+1];
        lookup[1] = ('/', 1, 0);
        for (int n = 1; n < lookup.Length; n++) {
            var ln = lookup[n];
            if (ln.param == 0) continue;
            for (int d = 0; d < divisors.Length; d++) {
                int target = n * divisors[d];
                if (target > goal) break;
                if (lookup[target].steps == 0 || lookup[target].steps > ln.steps) lookup[target] = ('/', divisors[d], ln.steps + 1);
            }
            for (int s = 0; s < subtractors.Length; s++) {
                int target = n + subtractors[s];
                if (target > goal) break;
                if (lookup[target].steps == 0 || lookup[target].steps > ln.steps) lookup[target] = ('-', subtractors[s], ln.steps + 1);
            }
        }
        return lookup;
    }

    private static string Delimit<T>(this IEnumerable<T> source) => string.Join(", ", source);
}





Python: from functools import lru_cache




DIVS = {2, 3}
SUBS = {1}

class Minrec():
    "Recursive, memoised minimised steps to 1"

    def __init__(self, divs=DIVS, subs=SUBS):
        self.divs, self.subs = divs, subs

    @lru_cache(maxsize=None)
    def _minrec(self, n):
        "Recursive, memoised"
        if n == 1:
            return 0, ['=1']
        possibles = {}
        for d in self.divs:
            if n % d == 0:
                possibles[f'/{d}=>{n // d:2}'] = self._minrec(n // d)
        for s in self.subs:
            if n > s:
                possibles[f'-{s}=>{n - s:2}'] = self._minrec(n - s)
        thiskind, (count, otherkinds) = min(possibles.items(), key=lambda x: x[1])
        ret = 1 + count, [thiskind] + otherkinds
        return ret

    def __call__(self, n):
        "Recursive, memoised"
        ans = self._minrec(n)[1][:-1]
        return len(ans), ans


if __name__ == '__main__':
    for DIVS, SUBS in [({2, 3}, {1}), ({2, 3}, {2})]:
        minrec = Minrec(DIVS, SUBS)
        print('\nMINIMUM STEPS TO 1: Recursive algorithm')
        print('  Possible divisors:  ', DIVS)
        print('  Possible decrements:', SUBS)
        for n in range(1, 11):
            steps, how = minrec(n)
            print(f'    minrec({n:2}) in {steps:2} by: ', ', '.join(how))

        upto = 2000
        print(f'\n    Those numbers up to {upto} that take the maximum, "minimal steps down to 1":')
        stepn = sorted((minrec(n)[0], n) for n in range(upto, 0, -1))
        mx = stepn[-1][0]
        ans = [x[1] for x in stepn if x[0] == mx]
        print('      Taking', mx, f'steps is/are the {len(ans)} numbers:',
              ', '.join(str(n) for n in sorted(ans)))
        
        print()