清除代码以查找列表中的元组交集

时间:2016-04-23 06:02:35

标签: math functional-programming set

我已经编写了一个算法来查找命令行工具中的参数,并希望清理我的代码但是卡住了。

任务

我的程序接收参数:flag output input ... | input flag output 示例包括:-d one/path second/path/to/file.txtsecond/path/to/file.txt --dir one/path等。每个空格用作分隔符以创建参数数组。参数可以是标记,例如-d路径

我将每个标志映射到两个数组中,我将其压缩为一个元组数组。我称之为搜索集。

在数学符号

我对FP和数学符号都不熟悉所以请原谅我的错误(我从维基百科和其他网站上学到了)。

搜索 S 参数的 P

S = { S₁, S₂, S₃ }
where Sn = { flagShort, flagLong }
where flagShort = '-d' | '-r' | '-o'
      flagLong = '--dir' | '--replace' | '--output'

P = { P₁, P₂, ... }
where Pn = flag | path
where path = dir | file

所以我需要找到输出,方法是在标志之后搜索P出现Sn +的下一个参数。

Ps = Sn ∩ P + Pₙ+₁

输入只是Ps ∉ P,所以如果我可以获得Ps就很容易。

这引导我进行以下转变:

P -> Pn -> S -> Sn -> Sn = Pn -> Pn + Pₙ+₁

在javascript中它可以写成:

const flagsShort = ["-r","-d","-o"]
const flagsLong = ["--replace","--dir","--output"]
const search =  _.zip(flagsShort, flagsLong)

let Ps = tuplesIntersectionParametersPlusNextP(
    ['one/path', '--dir', 'two/path', '-d', 'one/path', '-d'],
    search
)
// Ps -> [ '--dir', 'two/path', '-d', 'one/path' ]

function tuplesIntersectionParametersPlusNextP(P, S) {
    const Ps = [];
    P.forEach(  (Pn, n) => {
        S.forEach(Sn => {
            Sn.forEach(flag => {
                if(flag===Pn) Ps.push(Pn, P[n+1])
            })
        })
    })
    return Ps
}

虽然上面的代码有效,但它看起来并不干净。我一直在寻找不同的FP库,例如underscore和不同的python文章,但还没有弄清楚如何使用所有这些聪明的FP函数来清理我的代码。

我会接受任何语言的答案,Python,Haskell,Scala等,但请不要使用列表推导。虽然我非常有信心可以将代码移植到js,但我发现列表理解有点难以移植。更好地使用map each reduce等。

如果你也可以用设定的符号指出我正确的方向,我将非常感激!

1 个答案:

答案 0 :(得分:1)

声明

我倾向于远离Javascript。在Javascript中可能有更好的方法来做某些事情,但我相信一般原则仍然存在。

你的代码很好,虽然嵌套得有点深。

使代码更清晰的技巧是提取抽象功能。在最深的嵌套中,您真正要求的是" 元素Pn是否存在于列表列表S中?"这是我可以想象自己在一个应用程序中不止一次要求的东西,所以它很适合变成一个函数。您甚至可以对任何级别的嵌套进行递归:

function ElementInNestedLists(e, l) {
    if (l instanceof Array) {
        return l.reduce(function(prev, cur, idx, arr) {
            if (prev || ElementInNestedLists(e, cur)) { 
                return true;
            }
            return false;
        }, false);
    } else {
        return l == e;
    }
}

您不必在此使用reduce。没有什么可以禁止你在函数编程中用支持它的语言进行实际的for循环。在找到元素后,这样可以更好地防止函数继续运行:

function ElementInNestedLists(e, l) {
    if (l instanceof Array) {
        for (elem of l) {
            if (ElementInNestedLists(e, elem)) {
                return true;
            }
        }
        return false;
    } else {
        return l == e;
    }
}

使用此新功能,您可以简化tuplesIntersectionParametersPlusNextP

function tuplesIntersectionParametersPlusNextP(P, S) {
    const Ps = [];
    P.forEach(  (Pn, n) => {
        if (ElementInNestedLists(Pn, S)) {
            Ps.push(Pn, P[n + 1]);
        }
    });
    return Ps;
}

但是有一个错误。给定输入时,此函数的输出为[ '--dir', 'two/path', '-d', 'one/path', _undefined_ ],因为最后一个标志后面没有参数。我们需要添加一个测试,以确保至少有两个元素需要检查。

function tuplesIntersectionParametersPlusNextP(P, S) {
    const Ps = [];
    P.forEach(  (Pn, n) => {
        if (n + 1 < P.length && ElementInNestedLists(Pn, S)) {
            Ps.push(Pn, P[n + 1]);
        }
    });
    return Ps;
}

但还有另一个错误。考虑输入['-d', '-d', 'foo']。输出为['-d', '-d', '-d', 'foo'],这是不正确的。所需的输出为['-d', '-d']。您可以决定添加变量以跟踪是否需要处理下一个字段:

function tuplesIntersectionParametersPlusNextP(P, S) {
    const Ps = [];
    skip = false;
    P.forEach(  (Pn, n) => {
        if (skip) {
            skip = false;
            return;
        }
        if (n+1 < P.length && ElementInNestedLists(Pn, S)) {
            Ps.push(Pn, P[n + 1]);
            skip = true;
        }
    })
    return Ps
}

虽然这样做你想要的,但你现在又失去了清洁。解决方案(通常在函数式编程中就是这种情况)是递归地解决这个问题。

function Parse(cmd, flags, acc = []) {
    if (cmd.length < 2) {
        return acc;
    }

    if (ElementInNestedLists(cmd[0], flags)) {
        acc.push(cmd[0], cmd[1]);
        return Parse(cmd.slice(2, cmd.length), flags, acc)
    }
    return Parse(cmd.slice(1, cmd.length), flags, acc)
}

如果你不想切片:

function Parse(cmd, flags, idx = 0, acc = []) {
    if (idx + 1 >= cmd.length) {
        return acc;
    }

    if (ElementInNestedLists(cmd[idx], flags)) {
        acc.push(cmd[idx], cmd[idx + 1]);
        return Parse(cmd, flags, idx + 2, acc);
    }
    return Parse(cmd, flags, idx + 1, acc);
}

当然,您可能想要检查并丢弃 - 或以其他方式处理 - 旗帜后面的旗帜。可能还有其他更复杂的要求,你或者没有提到或者还没想到(但是),但这些要求超出了这个答案的范围。