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);
}



Java: import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MinimalStepsDownToOne {

    public static void main(String[] args) {
        runTasks(getFunctions1());
        runTasks(getFunctions2());
        runTasks(getFunctions3());
    }
    
    private static void runTasks(List<Function> functions) {
        Map<Integer,List<String>> minPath = getInitialMap(functions, 5);

        
        int max = 10;
        populateMap(minPath, functions, max);
        System.out.printf("%nWith functions:  %s%n", functions);
        System.out.printf("  Minimum steps to 1:%n");
        for ( int n = 2 ; n <= max ; n++ ) {
            int steps = minPath.get(n).size();
            System.out.printf("    %2d: %d step%1s: %s%n", n, steps, steps == 1 ? "" : "s", minPath.get(n));
        }
        
        
        displayMaxMin(minPath, functions, 2000);

        
        displayMaxMin(minPath, functions, 20000);

        
        displayMaxMin(minPath, functions, 100000);
    }
    
    private static void displayMaxMin(Map<Integer,List<String>> minPath, List<Function> functions, int max) {
        populateMap(minPath, functions, max);
        List<Integer> maxIntegers = getMaxMin(minPath, max);
        int maxSteps = maxIntegers.remove(0);
        int numCount = maxIntegers.size();
        System.out.printf("  There %s %d number%s in the range 1-%d that have maximum 'minimal steps' of %d:%n    %s%n", numCount == 1 ? "is" : "are", numCount, numCount == 1 ? "" : "s", max, maxSteps, maxIntegers);
        
    }
    
    private static List<Integer> getMaxMin(Map<Integer,List<String>> minPath, int max) {
        int maxSteps = Integer.MIN_VALUE;
        List<Integer> maxIntegers = new ArrayList<Integer>();
        for ( int n = 2 ; n <= max ; n++ ) {
            int len = minPath.get(n).size();
            if ( len > maxSteps ) {
                maxSteps = len;
                maxIntegers.clear();
                maxIntegers.add(n);
            }
            else if ( len == maxSteps ) {
                maxIntegers.add(n);
            }
        }
        maxIntegers.add(0, maxSteps);
        return maxIntegers;
    }

    private static void populateMap(Map<Integer,List<String>> minPath, List<Function> functions, int max) {
        for ( int n = 2 ; n <= max ; n++ ) {
            if ( minPath.containsKey(n) ) {
                continue;
            }
            Function minFunction = null;
            int minSteps = Integer.MAX_VALUE;
            for ( Function f : functions ) {
                if ( f.actionOk(n) ) {
                    int result = f.action(n);
                    int steps = 1 + minPath.get(result).size();
                    if ( steps < minSteps ) {
                        minFunction = f;
                        minSteps = steps;
                    }
                }
            }
            int result = minFunction.action(n);
            List<String> path = new ArrayList<String>();
            path.add(minFunction.toString(n));
            path.addAll(minPath.get(result));
            minPath.put(n, path);
        }
        
    }

    private static Map<Integer,List<String>> getInitialMap(List<Function> functions, int max) {
        Map<Integer,List<String>> minPath = new HashMap<>();
        for ( int i = 2 ; i <= max ; i++ ) {
            for ( Function f : functions ) {
                if ( f.actionOk(i) ) {
                    int result = f.action(i);
                    if ( result == 1 ) {
                        List<String> path = new ArrayList<String>();
                        path.add(f.toString(i));
                        minPath.put(i, path);
                    }
                }
            }
        }
        return minPath;
    }

    private static List<Function> getFunctions3() {
        List<Function> functions = new ArrayList<>();
        functions.add(new Divide2Function());
        functions.add(new Divide3Function());
        functions.add(new Subtract2Function());
        functions.add(new Subtract1Function());
        return functions;
    }

    private static List<Function> getFunctions2() {
        List<Function> functions = new ArrayList<>();
        functions.add(new Divide3Function());
        functions.add(new Divide2Function());
        functions.add(new Subtract2Function());
        return functions;
    }

    private static List<Function> getFunctions1() {
        List<Function> functions = new ArrayList<>();
        functions.add(new Divide3Function());
        functions.add(new Divide2Function());
        functions.add(new Subtract1Function());
        return functions;
    }
    
    public abstract static class Function {
        abstract public int action(int n);
        abstract public boolean actionOk(int n);
        abstract public String toString(int n);
    }
    
    public static class Divide2Function extends Function {
        @Override public int action(int n) {
            return n/2;
        }

        @Override public boolean actionOk(int n) {
            return n % 2 == 0;
        }

        @Override public String toString(int n) {
            return "/2 -> " + n/2;
        }
        
        @Override public String toString() {
            return "Divisor 2";
        }
        
    }

    public static class Divide3Function extends Function {
        @Override public int action(int n) {
            return n/3;
        }

        @Override public boolean actionOk(int n) {
            return n % 3 == 0;
        }

        @Override public String toString(int n) {
            return "/3 -> " + n/3;
        }

        @Override public String toString() {
            return "Divisor 3";
        }

    }

    public static class Subtract1Function extends Function {
        @Override public int action(int n) {
            return n-1;
        }

        @Override public boolean actionOk(int n) {
            return true;
        }
    
        @Override public String toString(int n) {
            return "-1 -> " + (n-1);
        }

        @Override public String toString() {
            return "Subtractor 1";
        }

    }

    public static class Subtract2Function extends Function {
        @Override public int action(int n) {
            return n-2;
        }

        @Override public boolean actionOk(int n) {
            return n > 2;
        }
    
        @Override public String toString(int n) {
            return "-2 -> " + (n-2);
        }

        @Override public String toString() {
            return "Subtractor 2";
        }

    }

}