我正在尝试将字符串“分组”成段,我想这个例子会更加简洁地解释它
scala> val str: String = "aaaabbcddeeeeeeffg"
... (do something)
res0: List("aaaa","bb","c","dd","eeeee","ff","g")
我可以通过几种方式以命令式方式执行此操作(使用vars
并逐步执行字符串以查找组)但我想知道是否有更好的功能解决方案可以
获得了?我一直在查看Scala API,但似乎没有符合我需求的东西。
任何帮助将不胜感激
答案 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"
初始化的。span
(def 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