【问题标题】:Splitting string into groups将字符串拆分为组
【发布时间】:2011-07-12 00:12:19
【问题描述】:

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

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

我可以用命令式的方式想出几种方法(使用vars 并逐步遍历字符串以查找组),但我想知道是否有更好的功能解决方案可以 达到?我一直在浏览 Scala API,但似乎没有适合我需要的东西。

任何帮助将不胜感激

【问题讨论】:

  • 如果您能提及(并标记)您想使用的语言会很有帮助!
  • 帖子被标记了吗?可能需要一段时间才能出现在 SO 服务器或其他东西上
  • 您是否也希望匹配aaabbccddeeffffffhhhhhiiiiijjjj 等?还是只有这 7 个字符?
  • 构建器的功能对您来说是否足够?当然,它们本质上是可变的,但作为标准库的一部分应该是值得信赖的。
  • 我已经看过这个问题,仅针对 Scala,已经有两三遍了。

标签: string scala


【解决方案1】:

你可以用 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)
    }
}

【讨论】:

  • @Paul - 不,我不这么认为。比赛将占用更多空间,并且对尺寸的检查非常清楚。 @Thomas - 请参阅我对 Martin Ring 回答的评论。我喜欢这个答案,但我确实想指出这种方法的缺点。
  • @Paul 你可以像 Martin 那样实现它。我实际上是这样做的,但它的可读性并不高。
  • 感谢您的回复,我对span 方法并没有太多经验,因此阅读此类内容让我对您解决此问题的思考过程有了一些深刻的了解,所以谢谢!
  • 任何想法如何使这个尾递归?我收到大量输入的堆栈溢出错误
【解决方案2】:

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

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

在这个正则表达式中,我对捕获的字符使用 positive lookbehindnegative lookahead

【讨论】:

  • +1 - 非常好!我总是忘记正则表达式的惊人力量。
  • 还有惊人的不可读性。你真的可以说你不仔细研究就明白了。
  • 这是“功能性”吗? @Paul 这就是 cmets 的用途
  • @marcello,但我熟悉正则表达式。为什么你认为我不是?我认为这种使用算作高级正则表达式的使用(前瞻和后瞻),并将让大多数读者落后。我认为 Martin 的解决方案对更多读者来说更容易理解。
  • @Paul:我同意,但考虑一下:如果你真的使用了上面的代码,你会把它放在一个方法中,给它起一个好听的名字splitByGroup,记录和单元测试它.想要拆分字符串的人不必懂正则表达式,只要splitByGroup的实现者懂。
【解决方案3】:
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)
}

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

【讨论】:

  • @Rex Kerr 为什么是O(n^2)?我认为字符串的每个字符都有 2 个比较操作,这不是最佳的(更好的解决方案是像 Thomas 建议的那样使用 span),但这仍然是 O(n) 吗?我错过了什么吗?
  • @Rex 我会说你消耗每个字符一次(好吧实际上在这个实现中两次)。这将是 O(n)。 (是的,你可以让它尾递归。这是一个练习而不是问题。)
  • @Martin, @Thomas - 如果每个字符都不同,则生成平均长度为 n/2n 字符串,为 O(n^2) 工作总数。
  • @Rex 创建长度为k 的子字符串是O(1) 而不是O(k)。 (见here
  • @Martin - 我错了,但不是因为你说的原因;我不认为dropWhile 能够一路回到substring。但我检查了来源,看起来确实如此。所以只有堆栈溢出问题,而不是O(n^2) 问题。
【解决方案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)

【讨论】:

  • 我认为从 OP 的示例中可以清楚地看出,段的顺序很重要。
  • @Paul 无论如何我都没有提到订单我更新了代码
  • 这将导致List(bb, aaa) 用于"aabba"...不是吗?不知道这是否是 djhworld 想要的。
  • @Martin Ring - 是的,它将是 List(aaa,bb)。我猜这个问题的提出方式可以有多种解释。
  • 我有点迷茫,这个“身份”是从哪里来的?感谢您的回答,但这不是我所追求的,我对字符串首先出现的特定顺序很感兴趣
【解决方案5】:

你可以像这样使用一些辅助函数:

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)

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

【讨论】:

  • 分区没有做想要的,我想。给定 aaabbbbbaa 它将返回 aaaaa bbbbb
  • partition 将列表一分为二,因此像这样拆分 "aaaabbbbaaa".toList 将产生 "aaaa","bbbbaaaaa" 然后相同的函数以递归方式应用于列表的其余部分.我认为它确实可以满足您的需求
  • 分区不会在某一点拆分列表。它收集所有匹配或不匹配谓词scala&gt; "aaabbbbaaa" partition (_ == 'a') res0: (String, String) = (aaaaaa,bbbb) scala&gt;的元素
  • 你要找的函数是span(见Thomas的帖子)
  • 是的。你是对的,这里分区是错误的。没有仔细阅读文档。谢谢
【解决方案6】:

使用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 集合库中的所有/大多数操作都依赖于迭代器,恕我直言,这是一个本质上不起作用的概念。但至少代码看起来很实用。

【讨论】:

    【解决方案7】:

    Scala 2.13 开始,List 现在提供了unfold 构建器,可以与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.13Option#unless builder:

    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) =&gt; Option[(A, S)]): List[A]) 基于内部状态 (init),在我们的例子中初始化为 "aaaabbaaacdeeffg"
    • 对于每次迭代,我们span (def span(p: (Char) =&gt; Boolean): (String, String)) 这个内部状态是为了找到包含相同符号的前缀并产生一个包含前缀和字符串其余部分的(String, String) 元组。 span 在这种情况下非常幸运,因为它产生的正是 unfold 所期望的:一个包含列表的下一个元素和新的内部状态的元组。
    • 当内部状态为 "" 时,展开将停止,在这种情况下,我们会按照展开退出的预期生成 None

    【讨论】:

      【解决方案8】:

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

      有时,一些可变状态会有所帮助:

      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)
      

      【讨论】:

      • 如果collection.mutable.Seq 上有某种方法实际上修改了 序列,您可以在 tmp 中使用双链表并在 O(n) 时间内记忆。
      【解决方案9】:

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

      str.groupBy(c => c).values
      

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

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

      【讨论】:

        猜你喜欢
        • 2012-02-22
        • 1970-01-01
        • 1970-01-01
        • 2016-04-01
        • 2011-01-10
        • 1970-01-01
        相关资源
        最近更新 更多