将字符串拆分为组

时间:2011-03-09 15:32:04

标签: string scala

我正在尝试将字符串“分组”成段,我想这个例子会更加简洁地解释它

scala> val str: String = "aaaabbcddeeeeeeffg"
... (do something)
res0: List("aaaa","bb","c","dd","eeeee","ff","g")

我可以通过几种方式以命令式方式执行此操作(使用vars并逐步执行字符串以查找组)但我想知道是否有更好的功能解决方案可以  获得了?我一直在查看Scala API,但似乎没有符合我需求的东西。

任何帮助将不胜感激

9 个答案:

答案 0 :(得分:21)

您可以使用span:

递归地分割字符串
def s(x : String) : List[String] = if(x.size == 0) Nil else {
    val (l,r) = x.span(_ == x(0))
    l :: s(r) 
}

尾递归:

@annotation.tailrec def s(x : String, y : List[String] = Nil) : List[String] = {
    if(x.size == 0) y.reverse 
    else {
        val (l,r) = x.span(_ == x(0))
        s(r, l :: y)
    }
}

答案 1 :(得分:16)

似乎所有其他答案都非常集中在收集操作上。但纯字符串+正则表达式解决方案更简单:

str split """(?<=(\w))(?!\1)""" toList

在这个正则表达式中,我使用正向后视负向前瞻作为捕获的字符

答案 2 :(得分:12)

def group(s: String): List[String] = s match {
  case "" => Nil
  case s  => s.takeWhile(_==s.head) :: group(s.dropWhile(_==s.head))
}

编辑:尾递归版:

def group(s: String, result: List[String] = Nil): List[String] = s match {
  case "" => result reverse
  case s  => group(s.dropWhile(_==s.head), s.takeWhile(_==s.head) :: result)
}

可以像其他参数一样使用,因为第二个参数具有默认值,因此不必提供。

答案 3 :(得分:4)

让它成为单行:

scala>  val str = "aaaabbcddddeeeeefff"
str: java.lang.String = aaaabbcddddeeeeefff

scala> str.groupBy(identity).map(_._2)
res: scala.collection.immutable.Iterable[String] = List(eeeee, fff, aaaa, bb, c, dddd)

<强>更新

正如@Paul所提到的,这里的订单是更新版本:

scala> str.groupBy(identity).toList.sortBy(_._1).map(_._2)
res: List[String] = List(aaaa, bb, c, dddd, eeeee, fff)

答案 4 :(得分:1)

您可以使用这样的辅助函数:

val str = "aaaabbcddddeeeeefff"

def zame(chars:List[Char]) = chars.partition(_==chars.head)

def q(chars:List[Char]):List[List[Char]] = chars match {
    case Nil => Nil
    case rest =>
        val (thesame,others) = zame(rest)
        thesame :: q(others)
}

q(str.toList) map (_.mkString)

这应该可以解决问题吧?毫无疑问,它可以进一步清理成单行

答案 5 :(得分:1)

使用fold的功能*解决方案:

def group(s : String) : Seq[String] = {
  s.tail.foldLeft(Seq(s.head.toString)) { case (carry, elem) =>
    if ( carry.last(0) == elem ) {
      carry.init :+ (carry.last + elem)
    }
    else {
      carry :+ elem.toString
    }
  }
}

对字符串执行的所有序列操作(通过隐式转换)隐藏了大量成本。我猜真正的复杂性在很大程度上取决于转换为Seq字符串的类型。

(*)Afaik集合库中的所有/大多数操作都依赖于迭代器,这是一个非常不起作用的概念。但代码看起来很有用,至少。

答案 6 :(得分:0)

修改:必须仔细阅读。下面没有功能代码。

有时,一点点可变状态有助于:

def group(s : String) = {
  var tmp = ""
  val b = Seq.newBuilder[String]

  s.foreach { c =>
    if ( tmp != "" && tmp.head != c ) {
      b += tmp
      tmp = ""
    }

    tmp += c
  }
  b += tmp

  b.result
}

运行时O(n)(如果段具有最多恒定长度)并且tmp.+=可能产生最大的开销。使用字符串构建器代替O(n)中的严格运行时。

group("aaaabbcddeeeeeeffg")
> Seq[String] = List(aaaa, bb, c, dd, eeeeee, ff, g)

答案 7 :(得分:0)

unfold构建器现在提供了从Scala 2.13开始的List,该构建器可以与String::span结合使用:

List.unfold("aaaabbaaacdeeffg") {
  case ""   => None
  case rest => Some(rest.span(_ == rest.head))
}
// List[String] = List("aaaa", "bb", "aaa", "c", "d", "ee", "ff", "g")

,或者与Scala 2.13的{​​{3}}构建器一起使用:

List.unfold("aaaabbaaacdeeffg") {
  rest => Option.unless(rest.isEmpty)(rest.span(_ == rest.head))
}
// List[String] = List("aaaa", "bb", "aaa", "c", "d", "ee", "ff", "g")

详细信息:

  • 展开(def unfold[A, S](init: S)(f: (S) ⇒ Option[(A, S)]): List[A])基于内部状态(init),在我们的示例中,内部状态是用"aaaabbaaacdeeffg"初始化的。
  • 对于每次迭代,我们spandef span(p: (Char) ⇒ Boolean): (String, String))处于此内部状态,以便找到包含相同符号的前缀,并生成一个(String, String)元组,其中包含该前缀和其余的字符串。 span在这种情况下非常幸运,因为它产生的正是unfold所期望的:一个包含列表的下一个元素和新内部状态的元组。
  • 当内部状态为""时,展开就会停止,在这种情况下,我们会按照展开状态退出而产生None

答案 8 :(得分:0)

如果您想使用scala API,则可以使用内置函数:

str.groupBy(c => c).values

或者,如果您介意将其排序并在列表中:

str.groupBy(c => c).values.toList.sorted