此答案假设我在 cmets 中的建议是正确的,即您想要检查输入数组的 all 排列以查找一系列字母。也就是说,如果给定['aa', 'ba', 'ac'],那么我们要检查'aabaac'、'aaacba'、'baaaac'、'baacaa'、'acaaba' 和'acbaaa' 以发现'baaaac' 包含一个运行四个a 的。如果不是这种情况,请随意忽略这个。
打破它
为此,我想将其分解为多个部分。我想写一个函数,它接受输入字符串,找到这些字符串的所有排列,将每个排列中的字符串连接成一个字符串,找到每个字符串中最长的单字母条纹,然后选择最长的结果连胜。
我想要一个 main 函数,看起来像这样:
const longestSingleLetterSequence = (arr) =>
maximumBy (x => x .streak) (permutations (arr) .map (xs => xs .join ('')) .map (longestStreak))
会这样使用:
longestSingleLetterSequence (['aa', 'ba', 'ac'])
//=> {"char": "a", "streak": 4, "word": "baaaac"}
但这意味着我们需要编写三个辅助函数,longestStreak、permutations 和 maximumBy。请注意,最后两个可能是真正可重用的函数。
让我们看看创建这些函数。我们可以这样写permutations:
const permutations = (xs) =>
xs .length == 0
? [[]]
: xs .flatMap ((x, i) => permutations (excluding (i) (xs)) .map (p => x + p))
使用简单的帮助器excluding,它接受一个索引和一个数组,并返回该数组的副本,不包括该索引处的值。
permutations firsts 检查输入数组是否为空。如果是,我们只需返回一个仅包含空数组的数组。否则,对于每个元素,我们将其从列表中删除,递归排列剩余元素,并将初始元素添加到每个结果中。
我们的助手很简单。它可以内联到permutations,但我认为如果它们以这种方式分开,它们会更干净。它可能看起来像这样:
const excluding = (i) => (xs) =>
[... xs .slice (0, i), ... xs .slice (i + 1)]
然后我们可以写maximumBy,它接受一个函数并返回一个函数,该函数接受一个数组并返回该函数返回最大值的元素。它通过一个简单的reduce 调用来做到这一点:
const maximumBy = (fn) => (xs) =>
xs .reduce (({val, max}, x, i, _, xVal = fn (x)) =>
(i == 0 || xVal > max) ? {max: xVal, val: x} : {val, max},
{}
) .val
值得注意的是,这并不特定于数字。它适用于任何可以与< 进行比较的值,例如数字、字符串、日期或具有valueOf 方法的对象。
到目前为止,这些函数都是通用的实用函数,我们可以很容易地想象跨项目重用。下一个,longestStreak,更具体地针对这个项目:
const longestStreak = ([... chars]) => {
let {streakChar, streak} = chars .reduce (
({currChar, count, streakChar, streak}, char) => ({
currChar: char,
count: char == currChar ? count + 1 : 1,
streakChar: (char == currChar ? count + 1 : 1) > streak ? currChar : streakChar,
streak: Math .max (char == currChar ? count + 1 : 1, streak),
}),
{streak: -1}
)
return {char: streakChar, streak, word: chars .join ('')}
}
像许多极大值问题一样,我们可以使用.reduce 来解决这个问题。这里我们将输入字符串重构为一个字符数组,参数为[... cs]。然后当我们折叠我们的数组时,我们不断返回一个具有{currChar, count, streakChar, streak} 结构的对象,它包含我们正在跟踪的 currentChar、到目前为止看到的它们的计数,以及迄今为止看到的最长连击的字符和那个计数。我们从 {streak: -1} 的初始值开始。
当我们遍历字符串时,我们返回条纹字符及其长度(以及包含它的单词;这里不需要,但似乎对更大的问题很有用。)
这是它的整体外观:
// Utility functions
const excluding = (i) => (xs) =>
[... xs .slice (0, i), ... xs .slice (i + 1)]
const permutations = (xs) =>
xs .length == 0
? [[]]
: xs .flatMap ((x, i) => permutations (excluding (i) (xs)) .map (p => [x, ... p]))
const maximumBy = (fn) => (xs) =>
xs .reduce (({val, max}, x, i, _, xVal = fn (x)) =>
(i == 0 || xVal > max) ? {max: xVal, val: x} : {val, max},
{}
) .val
// Helper function
const longestStreak = ([... chars]) => {
let {streakChar, streak} = chars .reduce (
({currChar, count, streakChar, streak}, char) => ({
currChar: char,
count: char == currChar ? count + 1 : 1,
streakChar: (char == currChar ? count + 1 : 1) > streak ? currChar : streakChar,
streak: Math .max (char == currChar ? count + 1 : 1, streak),
}),
{streak: -1}
)
return {char: streakChar, streak, word: chars .join ('')}
}
// Main function
const longestSingleLetterSequence = (arr) =>
maximumBy (x => x .streak) (permutations (arr) .map (xs => xs .join ('')) .map (longestStreak))
// Demos
console .log (longestSingleLetterSequence (['aa', 'ba', 'ac']))
console .log (longestSingleLetterSequence (["ccdd", "bbbb", "bbab"]))
.as-console-wrapper {max-height: 100% !important; top: 0}
使用额外的帮助器进行清理
我真的不喜欢 main 函数的一件事。您需要非常仔细地阅读以了解操作顺序:
const longestSingleLetterSequence = (arr) =>
maximumBy (x => x .streak) (permutations (arr) .map (xs => xs .join ('')) .map (longestStreak))
//`-------- step 4 --------' `---- step 1 ---' `--------- step 2 -------' `------ step 3 ------'
我是Ramda 的主要作者之一,它提供了一些非常有用的工具来帮助管理这种复杂性。但是这些工具很容易自己编写。所以有了一些额外的助手,我实际上会这样写 main 函数:
const longestSingleLetterSequence = pipe (
permutations,
map (join ('')),
map (longestStreak),
maximumBy (prop ('streak'))
)
这里的步骤只是逐行按顺序运行。我不会详细介绍这些辅助函数,但如果你想看到它的实际效果,你可以展开这个 sn-p:
// Utility functions
const pipe = (...fns) => (...args) =>
fns .slice (1) .reduce ((a, fn) => fn (a), fns[0] (...args))
const map = (fn) => (xs) => xs .map (x => fn (x))
const prop = (p) => (o) => o [p]
const join = (sep) => (xs) => xs .join (sep)
const excluding = (i) => (xs) => [... xs .slice (0, i), ... xs .slice (i + 1)]
const permutations = (xs) =>
xs .length == 0
? [[]]
: xs .flatMap ((x, i) => permutations (excluding (i) (xs)) .map (p => [x, ... p]))
const maximumBy = (fn) => (xs) =>
xs .reduce (({val, max}, x, i, _, xVal = fn (x)) =>
(i == 0 || xVal > max) ? {max: xVal, val: x} : {val, max},
{}
) .val
// Helper function
const longestStreak = ([... chars]) => {
let {streakChar, streak} = chars .reduce (
({currChar, count, streakChar, streak}, char) => ({
currChar: char,
count: char == currChar ? count + 1 : 1,
streakChar: (char == currChar ? count + 1 : 1) > streak ? currChar : streakChar,
streak: Math .max (char == currChar ? count + 1 : 1, streak),
}),
{streak: -1}
)
return {char: streakChar, streak, word: chars .join ('')}
}
// Main function
const longestSingleLetterSequence = pipe (
permutations,
map (join ('')),
map (longestStreak),
maximumBy (prop ('streak'))
)
// Demo
console .log (longestSingleLetterSequence (['aa', 'ba', 'ac']))
console .log (longestSingleLetterSequence (["ccdd", "bbbb", "bbab"]))
.as-console-wrapper {max-height: 100% !important; top: 0}