加快比较字符串的功能

时间:2016-09-21 10:12:52

标签: string performance scala

我有一个带两个字符串的函数,s和obj。它通过删除1个字符来检查是否可以从字符串s形成字符串obj。实现工作正常但如果字符串变大则变得非常慢。我试图找到一种方法来使这段代码更快地工作。有人能想出一个实现吗?

  def check_extra_char(s: String, obj: String): Boolean = {
    if(s.length != obj.length+1) return false                         // Automatically false if obj is not one char smaller than s
    for (i <- 0 until s.length)
      if (s.take(i) + s.substring(1+i) == obj) return true
    return false  
  }

4 个答案:

答案 0 :(得分:2)

在发生不匹配时,一个接一个地比较两个字符串的字符跳过不匹配字符并保持不匹配的计数。如果发生多于1个不匹配,则检查返回false。在最坏的情况下,检查的时间复杂度是O(n)。

  def check(a: String, b: String): Boolean = {
   val smallerStr = if (a.length < b.length) a else b
   val largerStr = if (a.length > b.length) a else b

  if (largerStr.length - smallerStr.length > 1) false
  else {
    def countMismatches(aIndex: Int, bIndex: Int, mismatchCount: Int): Int = {

      if (bIndex < largerStr.length && aIndex < smallerStr.length) {

        if (smallerStr(aIndex) != largerStr(bIndex)) {
          if (mismatchCount > 1) mismatchCount
          else countMismatches(aIndex, bIndex + 1, mismatchCount + 1)
        }
        else countMismatches(aIndex + 1, bIndex + 1, mismatchCount)

      } else mismatchCount
    }

    countMismatches(0, 0, 0) <= 1
  }
}

<强> REPL

res12: Boolean = true
@ check("zapple", "apple")
res13: Boolean = true
@ check("apple", "apzple")
res14: Boolean = true
@ check("apple", "apzzple")
res15: Boolean = false
@ check("apple", "applez")
res16: Boolean = true
@ check("apple", "applzz")
res17: Boolean = false

答案 1 :(得分:1)

您可以通过删除额外的s.take(i) + s.substring(i+1)来加快速度。您可以浏览s并将索引与obj中的对应项进行比较。当您发现差异时,请使用s.take(i) + s.substring(i+1)

  def check_extra_char(s: String, obj: String): Boolean = {
    if(s.length != obj.length+1) return false                         // Automatically false if obj is not one char smaller than s
    if(s.dropRight(1) == obj) return true    // so we don't go outOfIndex later
    for (i <- 0 until s.length)
      if (s(i) != obj(i)){
        if (s.take(i) + s.substring(1+i) == obj) return true else return false
    return false  
  }

答案 2 :(得分:1)

您的代码问题是s.take(i) + s.substring(1+i) == obj部分。 String.take(i: Int)String.substring(start: Int, end: Int)都有O(n)时间复杂度。

有很多方法可以避免这种情况,我提供其中一个用尾随递归的惯用scala,

import scala.annotation.tailrec

def checkExtraChar(source: String, target: String): Boolean = {
  val sourceLength = source.length
  val targetLength = target.length

  // Assumption :: source.length == target.length + 1
  @tailrec
  def _check(srcIndex: Int, tgtIndex: Int, mismatchFound: Boolean): Boolean = srcIndex match {
    case index if index == (sourceLength - 1) && !mismatchFound => true
    case index if index == (sourceLength - 1) => source(srcIndex) == target(tgtIndex)
    case _ => (source(srcIndex) == target(tgtIndex), mismatchFound) match {
      case (true, _) => _check(srcIndex + 1, tgtIndex + 1, mismatchFound)
      case (false, false) => _check(srcIndex + 1, tgtIndex, true)
      case (false, true) => false
    }
  }

  (sourceLength == targetLength + 1) match {
    case false => false
    case true => _check(0, 0, false)
  }

}

checkExtraChar("qwerty", "werty") // true
checkExtraChar("wqerty", "werty") // true

checkExtraChar("qqwerty", "werty") // false

答案 3 :(得分:0)

得到的东西应该会产生一些改进,而且更具惯用性。

  def check(s: String, obj: String): Boolean = {
    if(s.length == obj.length + 1)
      streamFromString(s, 0).exists { case(substring) => substring == obj }
    else false
  }

  def streamFromString(s: String, withoutIndex: Int): Stream[String] = {
    lazy val next: Stream[String] = if(withoutIndex > s.length - 1) Stream.empty else streamFromString(s, withoutIndex + 1)
    s.patch(withoutIndex, Nil, 1) #:: next
  }

首先,您可以使用patch创建没有给定元素的子集合;它是在Seq上定义的,所以请随意阅读。

其次,我没有盲目地循环并重新发明轮子,而是决定创建一个Stream[String]创建的补丁,但由于Stream是一个懒惰的集合,我们将只在我们进行流的过程中创建后续补丁

您可以将exists来电替换为collectFirst,以PartialFunction取得对身体的更多控制权,但在这种情况下,这将是一种矫枉过正的行为,imo。

  streamFromString(s, 0)
    .collectFirst { case (substring) if substring == obj => "" } //could be anything here really, it's all about getting Option.Some
    .fold(false)(_ => true)