【问题标题】:Split up a list at each element satisfying a predicate (Scala)在满足谓词的每个元素处拆分一个列表(Scala)
【发布时间】:2011-11-09 18:04:15
【问题描述】:

在一个文本文件中,我有以下形式的数据:

1)
text
text
2)
more text
3)
even more text
more even text
even more text
...

我使用以下内容将其作为字符串列表读取:

val input = io.Source.fromFile("filename.txt").getLines().toList

我想将列表分解为以1)2) 等开头的子列表。

我想出了:

val subLists =
  input.foldRight( List(List[String]()) ) {
    (x, acc) =>
      if (x.matches("""[0-9]+\)""")) List() :: (x :: acc.head) :: acc.tail
      else (x :: acc.head) :: acc.tail
  }.tail

这可以更简单地实现吗?如果有一个内置方法可以在满足谓词的每个元素上拆分集合(提示、提示、库设计者:)),那将是非常好的。

【问题讨论】:

  • 看看这个问题和接受的答案:stackoverflow.com/questions/6800737/…
  • 在那个答案中可以使用迭代器,但这种情况更复杂,因为每个标题都不同,所以你需要第二个迭代器/列表作为标题,它不再是优雅的。递归似乎更简洁。

标签: list scala collections


【解决方案1】:

foldRight 带有一个复杂的参数通常表明您最好使用递归来编写它,并在您使用它时将其分解为自己的方法。这就是我想出的。首先,让我们概括一下 到一个通用方法,groupPrefix:

 /** Returns shortest possible list of lists xss such that
  *   - xss.flatten == xs
  *   - No sublist in xss contains an element matching p in its tail
  */
 def groupPrefix[T](xs: List[T])(p: T => Boolean): List[List[T]] = xs match {
   case List() => List()
   case x :: xs1 => 
     val (ys, zs) = xs1 span (!p(_))
     (x :: ys) :: groupPrefix(zs)(p)  
 }

现在您只需调用即可获得结果

 groupPrefix(input)(_ matches """\d+\)""")

【讨论】:

  • 一个问题:这不适用于大量组(堆栈溢出)
  • 包含 476k 元素和 10000 个分隔符的列表会炸毁堆栈
  • 它不是尾递归的,因为函数调用在返回其值之前依赖于 :: 操作(因此在每次迭代时使用更多的堆栈空间)。然而,通过向函数添加一个额外的参数(通常称为累加器)使其尾递归相对简单。
【解决方案2】:

我有幸在伟大的@MartinOdersky 旁边添加答案!

从 Scala 2.13 开始,我们可以使用 List.unfold:

List.unfold(input) {
  case Nil =>
    None
  case x :: as =>
    as.span(!_.matches("""\d+\)""")) match {
      case (prefix, Nil) =>
        Some(x :: prefix, List.empty)
      case (prefix, suffix) =>
        Some(x :: prefix, suffix)
    }
}

代码在Scastie 运行。

【讨论】:

    猜你喜欢
    • 2011-01-11
    • 2022-01-10
    • 2020-05-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-11
    • 1970-01-01
    相关资源
    最近更新 更多