更好的压缩字符串的方法

时间:2015-11-30 15:01:41

标签: scala

我正在尝试问题所在的Hackerrank问题

问题陈述

Input: abcaaabbb
output : abca3b3

我的解决方案看起来像

import scala.io.StdIn.readLine

object Solution {

    def main(args: Array[String]) {
        val input = readLine()
        require(input.length > 0, "input string must not be empty.")    
        println(compress(input.tail, input.head, 1, ""))
    }

    def compress(in: String, currentChar: Char, currentCharCount: Int, out: String):String = in.isEmpty match {
        case true => getNewOutput(out, currentChar, currentCharCount)
        case false => in.head match {
            case `currentChar` => compress(in.tail, currentChar, currentCharCount + 1, out)
            case _ => compress(in.tail, in.head, 1, getNewOutput(out, currentChar, currentCharCount))
        }
    }

    def getNewOutput(out:String, currentChar: Char, currentCharCount:Int):String = 
        out + currentChar + (if(currentCharCount == 1) "" else currentCharCount.toString)
}

这很好用,但两个测试用例超时。我想知道是什么花了很多时间?

match吗?还是tail

什么是更好的写作方式?

P.S。我正在学习Scala,所以现在要忍受我的语法和知识

2 个答案:

答案 0 :(得分:3)

由于问题被标记为功能编程/ 递归 ,因此可以通过尾递归解决问题。在这种序列遍历中用于tailrec的常用方法是创建额外的累加器参数,该参数在最后处理:

def compress(str: Seq[Char], acc: List[(Char, Int)]): String = str match {
    case first +: rest => compress(rest, acc match {
      case (`first`, n) :: tail => (first, n + 1) :: tail
      case _ => (first, 1) :: acc
    })
    case _ => acc.reverse.view.map {
      case (c, 1) => c.toString
      case (c, n) => s"$c$n"
    }.mkString
  }

虽然这样的尾递归函数很少被认为是好的风格FP,因为它们的组合性差。 Usualy更好的解决方案使用折叠而不是tailrec:

def compress(str: Seq[Char]): String =
  str.foldLeft(List.empty[(Char, Int)]) { (acc, char) =>
    acc match {
      case (`char`, n) :: tail => (char, n + 1) :: tail
      case _ => (char, 1) :: acc
    }
  }.reverse.view.map {
    case (c, 1) => c.toString
    case (c, n) => s"$c$n"
  }.mkString

我确实费心去验证两个解决方案都是接受对应的Hackerrank问题代码

object Solution {
  def compress(...
  def main(args: Array[String]) {
    println(compress(io.StdIn.readLine()))
  }
}

第二个foldLeft实施有点快。

代码中最麻烦的是String串联。由于问题是在某些地方等待100K字符的字符串结果,所以

out + currentChar + ... 

多次将小部件附加到很长的前缀上。由于scala中的String是普通的java.lang.String,而不是像Vector那样可以在小型连接中有效地重复使用大部分内容,而是将其击败为TL。

答案 1 :(得分:1)

基于span的递归解决方案证明O(n)并且实现起来很简单,如下所示,

def compress(s: String): String = {
  if (s.isEmpty) s
  else {
    val (l,r) = s.span(_ == s.head)
    (if (l.size == 1) l else l.head + l.size.toString) + compress(r)
  }
}

在span的左侧,我们将相同的字符收集到当前头部,然后将其作为单个字符或具有连续重复次数的字符添加到最终结果中;在span的右边,我们有剩余的要压缩的字符串,直到我们消耗整个字符串。

更新:如下所示,并在原始查询之后,请注意此初始递归提案的尾递归版本,

def compress(s: String): String = {

  def f(s2: String, res: String): String = {
    if (s2.isEmpty) res
    else {
      val (l,r) = s2.span(_ == s2.head)
      f(r, res + (if (l.size == 1) l else l.head + l.size.toString))
    }
  }

  f(s, "")
}