下面是一个imperative
解决方案:
def longestCommonSubstring(a: String, b: String) : String = {
def loop(m: Map[(Int, Int), Int], bestIndices: List[Int], i: Int, j: Int) : String = {
if (i > a.length) {
b.substring(bestIndices(1) - m((bestIndices(0),bestIndices(1))), bestIndices(1))
} else if (i == 0 || j == 0) {
loop(m + ((i,j) -> 0), bestIndices, if(j == b.length) i + 1 else i, if(j == b.length) 0 else j + 1)
} else if (a(i-1) == b(j-1) && math.max(m((bestIndices(0),bestIndices(1))), m((i-1,j-1)) + 1) == (m((i-1,j-1)) + 1)) {
loop(
m + ((i,j) -> (m((i-1,j-1)) + 1)),
List(i, j),
if(j == b.length) i + 1 else i,
if(j == b.length) 0 else j + 1
)
} else {
loop(m + ((i,j) -> 0), bestIndices, if(j == b.length) i + 1 else i, if(j == b.length) 0 else j + 1)
}
}
loop(Map[(Int, Int), Int](), List(0, 0), 0, 0)
}
我正在寻找一个更紧凑和functional way
找到最长的公共子字符串。
7条答案
按热度按时间de90aj5v1#
首先获取两个字符串在一个集合中的所有可能的子字符串(取自here)(以删除重复项),然后求这些集合的交集,找到公共子字符串中最长的一个。
zbwhf8kr2#
你的代码已经很实用了,也不是那么复杂。它也比目前发布的其他解决方案具有更好的时间效率。
我只想简化一下,清理一下,修复一下bug:
luaexgnf3#
**旁注:**这是我对这个问题的第三个答案,因为StackOverflow策略不允许从根本上替换以前答案的内容。感谢@Kolmar的反馈,这个新答案比my prior answer性能更好。
LCS(最长公共子串)问题空间已经投入了许多时间来寻找最优解决方案策略。要观察更一般的计算机科学问题和最优策略,请查看this Wikipedia article。这篇Wikipedia文章的后面是一些描述实现策略的伪代码。
基于Wikipedia文章的伪代码,我将给出几种不同的解决方案,目的是允许用户复制/粘贴所需的特定解决方案,而无需进行大量的重构:
LCSubstr
:将使用 * 命令式可变样式 * 的Wikipedia文章伪代码翻译成ScalaLCSubstrFp
:将LCSubstr
重构为惯用的 *Scala函数不可变样式 *longestCommonSubstrings
:重构LCSubstrFp
以使用描述性名称(例如left
和right
,而不是s
和t
),并跳过在Map
中存储零长度longestCommonSubstringsFast
:重构longestCommonSubstrings
以深度优化CPU和内存longestCommonSubstringsWithIndexes
:重构longestCommonSubstringsFast
以增强返回值,方法是将每个条目扩展为(String, (Int, Int))
的元组,其中包括找到的子字符串和每个输入String
中找到子字符串的索引(**注意:**如果同一String
出现多次,则会创建索引对的组合扩展)firstLongestCommonSubstring
:longestCommonSubstringsFast
的一个注重效率的版本,当只关心第一个LCS而想忽略相同大小的其他LCS时,它提供了提前终止的机会。1.***奖励:***x1月19日1x:重构
longestCommonSubstringsFast
以增加内部实现的可变性,同时在外部保留函数的引用透明性。对于OP的请求,更直接的答案应该在
LCSubstrFp
和longestCommonSubstringsFast
之间。LCSubstrFp
是最直接的,但效率相当低。使用longestCommonSubstringsFast
效率更高,因为它最终使用的CPU和GC要少得多。如果函数实现中包含和约束的内部互操作性是可以接受的,那么longestCommonSubstringsUltimate
是迄今为止CPU负担和内存占用最小的版本。LC子序列
翻译成Scala的维基百科文章伪代码,其中使用了 * 命令式可变样式 *。
这样做的目的是尽可能地用Scala来重现一对一的实现。例如,Scala假设String使用从零开始的索引,而伪代码显然使用从一开始的索引,这需要一些调整。
LC减影Fp
将
LCSubstr
重构为惯用的 *Scala函数不可变样式 *。所有的命令式和变异代码都被函数和不可变代码所取代,两个
for
循环被递归所取代。最长公用子字符串
重构
LCSubstrFp
以使用描述性名称(例如left
和right
,而不是s
和t
),并跳过在Map
中存储零长度。除了增强可读性之外,这种重构还省去了在
lengthByIndexLongerAndIndexShorter
中存储零长度值的步骤,从而大大减少了“内存扰动”的数量。同样,在函数式风格的调整中,通过将Set
Package 在Option
中,返回值也得到了增强,不再返回空的Set
。如果返回值是Some
,所包含的X1 M39 N1 X将总是包含至少一个项目。最长常用子字符串快速
重构
longestCommonSubstrings
以深度优化CPU和内存。在保持功能性和不可变性的同时,消除了每一点低效,执行速度比
longestCommonSubstrings
提高了数倍。大部分成本降低是通过将整个矩阵的Map
替换为仅跟踪当前行和先前行的一对List
来实现的。要轻松查看与
longestCommonSubstrings
、please view this visual diff的区别。最长的带索引的公共子字符串
重构
longestCommonSubstringsFast
以增强返回值,方法是将每个条目扩展为(String, (Int, Int))
的元组,其中包括找到的子字符串和每个输入String
中找到子字符串的索引。**注意:**如果同一个
String
出现多次,则会创建索引对的组合展开。在函数式风格的调整中,返回值也得到了增强,通过将
List
Package 在Option
中,不再返回空的List
。如果返回的值是Some
,则包含的List
将始终包含至少一个项。第一个最长公用子字符串
longestCommonSubstringsFast
的一个注重效率的版本,当只关心第一个LCS而想忽略相同大小的其他LCS时,它提供了提前终止的机会。奖励:
最长通用子字符串最终
重构
longestCommonSubstringsFast
以增加内部实现的可变性,同时在外部保留函数的引用透明性。在保持功能性和引用透明性的同时(利用实现本身的可变性,有些人认为这种可变性 * 不是 * 有效的功能),进一步消除了每一点低效,执行速度比
longestCommonSubstringsFast
提高了近三倍。大部分成本降低是通过用单个Array
取代一对List
实现的。要轻松查看与
longestCommonSubstringsFast
、please view this visual diff的区别。uurity8g4#
**更新:**请勿使用下面详述的方法。
我应该多关注OP的明确提供的实现。不幸的是,我被所有其他的答案分散了注意力,使用低效的面向
String
的比较,并继续提供我自己的优化版本,这些我能够使用Stream
和LazyList
的喜悦。我现在添加了一个additional answer(根据StackOverflow的策略),它涵盖了速度更快的Scala函数式解决方案。
以
Stream
为中心的解决方案可能如下所示:这里,substrings 方法返回产生原始字符串的长度递减的子字符串的流,例如,“test”产生“test”、“tes”、“est”、“te”、“es”、...
方法 longestCommonSubstring 获取从字符串 b 中包含的 a 生成的第一个子字符串
t0ybt7op5#
**更新:**在发布这个答案之后,感谢@Kolmar的反馈,我发现
Char
索引策略要快得多(至少快了一个数量级)。现在我添加了additional answer(根据StackOverflow的策略),它涵盖了快得多的Scala函数式解决方案。我本应该多关注OP专门提供的实现。不幸的是,我被所有其他答案分散了注意力,使用了低效的面向
String
的比较,并继续撕裂,以提供我自己的优化版本,这些我能够使用Stream
和LazyList
的喜悦。除了OP的要求之外,我还有几个额外的要求,即编写一个解决方案来查找两个String示例之间的最长公共子字符串(LCS)。
解决方案要求:
1.急切地查找两个
String
示例之间的 * 第一个 * LCS1.通过比较更少的
String
示例最大限度地减少CPU工作量1.通过生成更少的
String
示例来最大限度地减少GC工作量1.最大化Scala的习惯用法,包括使用Scala集合API
第一个目标是捕获一般的搜索策略,该过程从
left
String
示例开始,从最长的子字符串生成一个有序的子字符串列表(原始String
示例本身)到最短例如,如果left
String
示例包含“ABCDEF”,应生成String
示例的结果列表,并且完全按照以下顺序:接下来,开始遍历
left
子串示例的列表,一旦在right
String
示例内的任何索引处找到特定的left
子串示例,就立即停止。当找到left
子串示例时,返回该示例。否则,返回未找到匹配的指示。关于满足解决方案要求#1的“急切”方法,有两点需要特别注意:
left
子字符串示例可能出现在right
String
示例中的多个索引处。这意味着使用indexOf
从right
String
示例的开头搜索left
子字符串示例与从right
String
的结尾搜索left
子字符串示例可能会产生不同的索引值示例使用lastIndexOf
。right
String
示例中也可能出现第二个(不同的)长度相同的left
子字符串示例。本实现忽略了这种可能性。Scala 2.13/Dotty(又名3.0)的解决方案-使用LazyList:
Stream
自2.13起已弃用。解决方案Scala 2.12及之前版本-使用Stream:
备注:
String
作为输入),则Option[String]
用于函数的返回类型。String
示例中较短的一个被设置为shorter
以减少示例化和与String
的比较,String
比longer
String
示例长。shorter
子串示例产生被以相同的大小进行子批处理(通过distinct
)。然后将每个子批次添加到LazyList
这确保了仅将实际需要的shorter
子串示例的示例化提供给longer.contains
函数。ee7vknir6#
我认为带有 for 理解的代码看起来非常清晰和实用。
最后一个函数返回Seq[String],只要结果可能包含几个最大长度相同的子字符串
3ks5zfa07#
这个方法怎么样:
1.获取所有子字符串:
左.初始.平面Map(.尾部)
1.按长度倒序排序
. to列表.排序依据(.长度).反向
1.找到第一个匹配项
.find(右.包含(_)).get
全功能:
注意:get永远不会是空的,因为初始字符串排列也包含空字符串,它总是匹配一些东西。