【发布时间】:2011-11-12 12:37:13
【问题描述】:
如何在Scala中找到两个字符串的最长公共前缀?
我可能可以编写一个“命令式”解决方案(索引i 在字符串上运行,而s(i) == t(i))但我正在寻找一个“功能式”解决方案(例如,无需显式更新索引变量)。
【问题讨论】:
标签: string scala list-comprehension
如何在Scala中找到两个字符串的最长公共前缀?
我可能可以编写一个“命令式”解决方案(索引i 在字符串上运行,而s(i) == t(i))但我正在寻找一个“功能式”解决方案(例如,无需显式更新索引变量)。
【问题讨论】:
标签: string scala list-comprehension
scala> "helloworld".zip("hellohell").takeWhile(Function.tupled(_ == _)).map(_._1).mkString
res130: String = hello
【讨论】:
"helloworld".zip("hellohell") 更改为("helloworld", "hellohell").zipped,我认为可以解决低效率问题。
"helloworld".view zip "hellohell".
takeWhile { case (a, b) => a == b } 而不是takeWhile(Function.tupled(_ == _))。
另一个递归版本。
def pref(s: String, t: String, out: String = ""): String = {
if (s == "" || t == "" || s(0) != t(0)) out
else pref(s.substring(1), t.substring(1), out + s(0))
}
它比 sjj 快 10 倍以上,比 missingfaktor 快两倍以上。 Java 的 substring 很快,因为 String 是不可变的。
【讨论】:
递归版本:
def findCommonPrefix(s1 : String, s2 : String) : String = {
def findCommonPrefixR(l1: List[Char], l2 : List[Char]) : List[Char] = {
l1 match {
case Nil => Nil
case x::xs => if (l2 != Nil && l2.head == x) x :: findCommonPrefixR(xs, l2.tail) else Nil
}
}
findCommonPrefixR(s1.toList, s2.toList).mkString
}
【讨论】:
命令式版本可以简化为
def longestCommonPrefix(s1:String, s2:String):String = {
val maxSize = scala.math.min(s1.length, s2.length)
var i:Int = 0;
while ( i < maxSize && s1(i)== s2(i)) i += 1;
s1.take(i);
}
【讨论】:
如果速度很重要,那就势在必行。
scala> def longestCommonPrefix(a: String, b: String): String = {
| var same = true
| val sb = new StringBuilder
| var i = 0
| while(same && i < math.min(a.length, b.length)) {
| if(a.charAt(i) != b.charAt(i)) {
| same = false
| } else {
| sb += a.charAt(i)
| i += 1
| }
| }
| sb.result
| }
longestCommonPrefix: (a: String, b: String)String
scala> longestCommonPrefix("", "")
res50: String = ""
scala> longestCommonPrefix("helloworld", "hellohell")
res51: String = hello
编辑:
按照路易吉的建议:
scala> def longestCommonPrefix(a: String, b: String): String = {
| var same = true
| var i = 0
| while(same && i < math.min(a.length, b.length)) {
| if(a.charAt(i) != b.charAt(i)) {
| same = false
| } else {
| i += 1
| }
| }
| a.substring(0, i)
| }
longestCommonPrefix: (a: String, b: String)String
scala> longestCommonPrefix("helloworld", "hellohell")
res68: String = hello
【讨论】:
a.substring(0, i)。这要快得多,因为您根本不必构建新字符串。
获取任意数量字符串的公共前缀:
def commonPrefix (strs: Seq[String]): String = {
var i = 0;
strs(0).takeWhile { ch => strs.forall(_(i) == ch) && {i += 1; true}} mkString
}
【讨论】:
def commonPrefix(strings: String*): String = {
val refString = strings.map(s => (s, s.length())).minBy { case (string, length) => length }._1
var i = 0
while (i < refString.length) {
if (!strings.forall(_(i) == refString(i))) return refString.substring(0, i)
i += 1
}
return refString
}
【讨论】:
根据 OP 请求,下面是一个简单的最佳快速函数式实现。
这种实现与任何手写的可变命令式风格一样快(因为它是尾递归的)。
def takeCommonPrefix(string1: String, string2: String): String = {
if (string1.nonEmpty && string2.nonEmpty) {
val (shorter, longer) =
if (string1.length < string2.length) (string1, string2)
else (string2, string1)
@scala.annotation.tailrec
def recursive(shorterIndex: Int = 0): String =
if (shorterIndex == shorter.length)
shorter
else
if (shorter(shorterIndex) != longer(shorterIndex))
shorter.take(shorterIndex)
else
recursive(shorterIndex + 1)
recursive()
}
else
""
}
作为奖励,如果您想要通用前缀,那么很有可能需要通用后缀。
def takeCommonSuffix(string1: String, string2: String): String = {
if (string1.nonEmpty && string2.nonEmpty) {
val (shorter, longer) =
if (string1.length < string2.length) (string1, string2)
else (string2, string1)
val longerOffset =
longer.length - shorter.length
@scala.annotation.tailrec
def recursive(shorterIndex: Int = shorter.length - 1): String =
if (shorterIndex == -1)
shorter
else
if (shorter(shorterIndex) != longer(shorterIndex + longerOffset))
shorter.takeRight(shorter.length - shorterIndex - 1)
else
recursive(shorterIndex - 1)
recursive()
}
else
""
}
【讨论】: