Quick actions

cmd+k|ctrl+k

Navigation

Languages

Untitled

Snippet info

Language

TypeScript

Visibility

unlisted

Author

legacy-anonymous

Created

2020-04-28T21:01:10Z

Updated

2020-04-28T21:01:10Z

function grid(w, h) {
    const arr = new Array(w);
    for(let i = 0; i < w; i++) {
        arr[i] = new Array(h);
    }
    return arr;
}

function canCoerce(param: string, arg: string) {
    return param === arg;
}

function checkParameters(
    params: string[],
    args: string[]
) {
    const compatibility: boolean[][] = grid(args.length, params.length);
    let allCompatible: boolean = params.length === args.length; // Make sure our happy path is fast
    for(let i = 0; i < Math.min(params.length, args.length); i++) {
        let paramCompatible = canCoerce(params[i], args[i]);
        allCompatible = allCompatible && paramCompatible;
        compatibility[i][i] = paramCompatible;
    }
    if(allCompatible) {
        console.log("Function call allowed!");
        return;
    }
    // We had some kind of incompatibility.  Either some params didn't match, or the lengths didn't match
    // To provide good heuristics, probe to see if adjacent arguments can line up
    // Start by filling in a compatibility grid
    for(let i = 0; i < args.length; i++) {
        for(let j = 0; j < params.length; j++) {
            if(i === j) continue; // We've already computed these
            compatibility[i][j] = canCoerce(args[i], params[j]);
        }
    }
    
    function directly_invalid(i) {
        if(i >= args.length || i >= params.length) {
            return false;
        }
        // Check if the input is completely unsatisfied
        for(let j = 0; j < args.length; j++) {
            if(compatibility[j][i]) {
                return false;
            }
        }
        // Check if the parameter is useless
        for(let j = 0; j < params.length; j++) {
            if(compatibility[i][j]) {
                return false;
            }
        }
        return true;
    }
    function argument_extra(i) {
        if(i >= compatibility.length) {
            return false;
        }
        if(compatibility[i].length == 0) {
            return true;
        }
        if(!compatibility[i].some(x => x)) {
            return true;
        }
        return false;
    }
    function param_missing(i) {
        if(i >= params.length) {
            return false;
        }
        if(!compatibility.some(r => r[i])) {
            return true;
        }
        return false;
    }
    function permuted() {
        // Find the rows that might be part of a permutation
        let possible_rows = [];
        for(let i = 0; i < args.length; i++) {
            if(compatibility[i].filter(j => j).length === 1) {
                possible_rows.push(i);
            }
        }
        let possible_cols = possible_rows.filter(i => {
            let j = compatibility[i].indexOf(true);
            let mr = possible_rows.filter(r => compatibility[r][j]);
            return mr.length === 1;
        });
        let permuted_cols = possible_cols.filter(c => !compatibility[c][c]);
        return permuted_cols;
    }
    function swapped(i) {
        if(i >= args.length || i >= params.length) {
            return false;
        }
        if(compatibility[i][i]) {
            return false;
        }
        for(let j = 0; j < Math.min(args.length, params.length); j++) {
            if(i == j) continue;
            if(compatibility[j][j]) {
                continue;
            }
            if(compatibility[i][j] && compatibility[j][i]) {
                return j;
            }
        }
        return false;
    }
    
    function removeArg(i) {
        args.splice(i, 1);
        compatibility.splice(i, 1);
    }
    function removeParam(i) {
        params.splice(i, 1);
        compatibility.forEach(r => r.splice(i, 1));
    }

    let limit = 10;
    while((params.length || args.length) && limit > 0) {

        let issueFound = false;
        for(let i = 0; i < Math.max(args.length, params.length); i++) {
            if (directly_invalid(i)) {
                console.log(`Expected ${params[i]}, got ${args[i]}`);
                removeArg(i);
                removeParam(i);
                issueFound = true;
                break;
            }
            if (argument_extra(i)) {
                console.log(`${args[i]} is extra!`);
                removeArg(i);
                issueFound = true;
                break;
            }
            if (param_missing(i)) {
                console.log(`${params[i]} is missing!`);
                removeParam(i);
                issueFound = true;
                break;
            }
        }
        let permutable = permuted();
        if(permutable.length > 1) {
            if(permutable.length == 2) {
                console.log(`${args[permutable[0]]} and ${args[permutable[1]]} are swapped!`);
            } else {
                console.log(`The following arguments are in the wrong order: ${permutable.map(p => args[p]).join(', ')}`);
            }
            permutable.sort().reverse().forEach(p => {
                removeArg(p);
                removeParam(p);
            });
            issueFound = true;
            continue;
        }
        if(!issueFound) {
            // Now that there are no obvious issues, eliminate one correctly mapped parameter
            for(let i = 0; i < Math.min(args.length, params.length); i++) {
                if(compatibility[i][i]) {
                    removeArg(i);
                    removeParam(i);
                    break;
                }
            }
        }
        limit--;
    }
    if(limit <= 0) {
        console.log("Diverged!!");
    }
}

type TestCase = [string, string[], string[]]

const testCases: TestCase[] = [
    /**/
    ['Valid', [], []],
    ['Valid', ['A'], ['A']],
    ['Valid', ['A','B'], ['A','B']],
    ['Valid', ['A', 'A'], ['A', 'A']],
    ['Invalid', ['A'], ['B']],
    ['Invalid', ['A', 'B'], ['A', 'C']],
    ['Invalid', ['A', 'B', 'C'], ['A', 'D', 'C']],
    ['Invalid', ['A', 'A', 'B'], ['A', 'A', 'C']],
    ['Invalid', ['A', 'A'], ['B', 'B']],
    ['Extra', [], ['A']],
    ['Extra', ['A'], ['A', 'B']],
    ['Extra', ['A'], ['A', 'B', 'C']],
    ['Extra', ['A', 'C'], ['A', 'B', 'C']],
    ['Extra', ['A', 'C'], ['B', 'A', 'C']],
    /**/
    ['Extra', ['A'], ['A', 'A']],
    /**/
    ['Extra', ['A', 'A'], ['A','A','A']],
    ['Extra', ['A', 'B'], ['A', 'A', 'B']],
    ['Extra', ['A', 'B'], ['A', 'B', 'B']],
    ['Extra', ['A', 'B'], ['A', 'A', 'B', 'B']],
    ['Extra and Invalid', ['A', 'B'], ['A', 'C', 'D']],
    ['Extra and Invalid', ['A', 'B', 'C'], ['A', 'C', 'F', 'C']],
    ['Missing', ['A'], []],
    ['Missing', ['A', 'B'], ['A']],
    ['Missing', ['A', 'B'], ['B']],
    ['Missing', ['A', 'A'], ['A']],
    ['Missing', ['A', 'A', 'A'], ['A']],
    ['Missing', ['A', 'B', 'C'], ['A', 'C']],
    ['Missing', ['A', 'B', 'C'], ['A', 'C']],
    ['Missing', ['A', 'B', 'B', 'C'], ['A', 'C']],
    ['Missing', ['A', 'B', 'A', 'B', 'C'], ['A', 'C']],
    ['Missing and Invalid', ['A', 'B', 'C'], ['A', 'D']],
    ['Missing and Extra', ['A', 'B', 'C'], ['A', 'C', 'F']],
    ['Swapped', ['A', 'B'], ['B', 'A']],
    ['Swapped', ['A', 'B', 'C'], ['B', 'A', 'C']],
    ['Swapped', ['A', 'B', 'C'], ['A', 'C', 'B']],
    ['Swapped', ['A', 'B', 'C'], ['C', 'B', 'A']],
    ['Permuted', ['A', 'B', 'C', 'D', 'E'], ['D', 'E', 'C', 'A', 'B']],
    ['Permuted', ['A', 'B', 'C'], ['B', 'C', 'A']],
    ['Swapped and Invalid', ['A', 'B', 'C'], ['C', 'F', 'A']],
    ['Swapped and Missing', ['A', 'B', 'C'], ['C', 'A']],/**/
    ['Missing, Extra, Invalid, Swapped', ["A", "B", "C", "D", "E", "F"], ["A", "C", "X", "G", "F", "E"]],
];

for(const c of testCases) {
    console.log(`fn MyFunc(${c[1].join(', ')}) { ... }`);
    console.log(`   MyFunc(${c[2].join(', ')});`);
    checkParameters(c[1], c[2]);
    console.log('----------------------');
}
INFO