认识你的祖先
递归是一种函数式遗产,因此将其与函数式风格一起使用会产生最佳效果。这意味着要避免诸如突变、变量重新分配和其他副作用之类的事情。
- 如果第一个字符
s0 不存在,则已达到基本情况。返回空结果。
- (归纳)第一个字符是 not null。如果等于下一个字符
s1,则这两个字符是相邻的重复。丢弃第一个字符,返回子问题[s1, ...s]的结果。
- (归纳)第一个字符不为空并且它确实 not 等于第二个字符。这两个字符不重复。将第一个字符附加到子问题的结果
[s1, ...s]。
function removeAdjacentDuplicates ([s0, s1, ...s])
{ if (s0 == null)
return "" // #1
else if (s0 == s1)
return removeAdjacentDuplicates([s1, ...s]) // #2
else
return s0 + removeAdjacentDuplicates([s1, ...s]) // #3
}
console.log(removeAdjacentDuplicates("AAABBCCCDDDEEEEE"))
// ABCDE
console.log(removeAdjacentDuplicates("ABBXYY"))
// ABXY
console.log(removeAdjacentDuplicates("LMNOP"))
// LMNOP
以函数式风格构建复杂的程序是将几个简单的程序组合在一起的问题。为了得到你的问题的答案,我们可以写solution如下-
const solution = s =>
s.length - removeAdjacentDuplicates(s).length
console.log(solution("AAABBCCCDDDEEEEE")) // 11
console.log(solution("ABBXYY")) // 2
console.log(solution("LMNOP")) // 0
展开下面的sn-p,在自己的浏览器中验证结果-
function removeAdjacentDuplicates ([s0, s1, ...s])
{ if (s0 == null)
return ""
else if (s0 == s1)
return removeAdjacentDuplicates([s1, ...s])
else
return s0 + removeAdjacentDuplicates([s1, ...s])
}
const solution = s =>
s.length - removeAdjacentDuplicates(s).length
console.log(solution("AAABBCCCDDDEEEEE")) // 11
console.log(solution("ABBXYY")) // 2
console.log(solution("LMNOP")) // 0
11
2
0
减少,简化
确定removeAdjacentDuplicates 的逻辑结构后,我们可以使用逻辑变换来操作它,就像我们使用定律来变换数学公式一样。下面我们将两个逻辑分支合并为一个以减少一些代码重复-
function removeAdjacentDuplicates ([s0, s1, ...s])
{ if (s0 == null)
return ""
else
return (s0 == s1 ? "" : s0)
+ removeAdjacentDuplicates([s1, ...s])
}
console.log(removeAdjacentDuplicates("AAABBCCCDDDEEEEE"))
// ABCDE
console.log(removeAdjacentDuplicates("ABBXYY"))
// ABXY
console.log(removeAdjacentDuplicates("LMNOP"))
// LMNOP
请给一个函数!
如果我们愿意的话,我们可以把所有东西都写成一个大函数,但这违背了函数式风格。在上面的实现中,removeAdjacentDuplicates 本身就是一个有用的函数。由于我们程序的多个关注点的分离,它很容易编写,solution 也很容易编写。作为奖励,分离的函数可以在其他程序中重复使用。
下面,solution 被重写为单个函数,其意图现在不太清楚。它更难编写,更难调试,也不容易在其他程序中重用 -
function solution ([s0, s1, ...s], n = 0)
{ if (s0 == null)
return n
else
return solution([s1, ...s], n + Number(s0 == s1))
}
console.log(solution("AAABBCCCDDDEEEEE"))
// 11
console.log(solution("ABBXYY"))
// 2
console.log(solution("LMNOP"))
// 0
请用一个表情!
我们可以使用另一种变体,将尾递归形式换成树递归形式。交换揭示了一个不需要带有默认参数的额外参数的表单 -
function solution ([s0, s1, ...s])
{ if (s0 == null)
return 0
else
return Number(s0 == s1) + solution([s1, ...s])
}
正如 Scott 所指出的,这是一种不同于单一、纯函数表达式的语法转换 -
const countAdjacentDuplicates = ([s0, s1, ...s]) =>
s0 == null
? 0
: Number(s0 == s1) + countAdjacentDuplicates([s1, ...s])
console.log(countAdjacentDuplicates("AAABBCCCDDDEEEEE"))
// 11
console.log(countAdjacentDuplicates("ABBXYY"))
// 2
console.log(countAdjacentDuplicates("LMNOP"))
// 0
在某种程度上,这消除了我试图教授的一些课程,但希望你仍然能够从这篇文章中学到一些东西。