使用scanLeft打包符号

时间:2012-12-12 16:47:08

标签: list scala traversal tail-recursion

99 scala problems有这个问题:

  
    

将列表元素的连续副本打包到子列表中。         如果列表包含重复的元素,则应将它们放在单独的子列表中。

Example:
  
scala> pack(List('a, 'a, 'a, 'a, 'b, 'c, 'c, 'a, 'a, 'd, 'e, 'e, 'e, 'e))
res0: List[List[Symbol]] = List(List('a, 'a, 'a, 'a), List('b), List('c, 'c), List('a, 'a), List('d), List('e, 'e, 'e, 'e))

我对解决上述问题的尾递归方法有所了解。我想知道是否有办法使用scanLeft完成上述操作,其中中间结果是常用元素列表?

5 个答案:

答案 0 :(得分:1)

以下是使用foldLeft的解决方案:

def pack[A](l:List[A]): List[List[A]] = l match {
  case head :: tail =>
    tail.foldLeft(List(List(head))) { (collector:List[List[A]], elem:A) =>
      if (collector.head.head == elem)
        (elem +: collector.head) +: collector.tail
      else
        List(elem) +: collector
    }.reverse
  case _ => List.empty
}

这仅适用于列表。 更好的解决方案可能会使用MultiSets,尽管很难找到Scala实现。

答案 1 :(得分:1)

简洁但未经优化的版本可能如下所示:

val l = List('a, 'a, 'a, 'a, 'b, 'c, 'c, 'a, 'a, 'd, 'e, 'e, 'e, 'e)
for (i <-l.distinct) yield l.filter(_ == i)

res0: List[List[Symbol]] = List(List('a, 'a, 'a, 'a, 'a, 'a), List('b), List('c, 'c), List('d), List('e, 'e, 'e, 'e))

答案 2 :(得分:0)

scanLeft对于计算前缀和非常有用,但问题并非如此。您可以尝试将其与其他呼叫结合使用,但我认为这不是解决问题的好方法。

foldLeft似乎对此任务更有用。

def pack[T](list: Seq[T]) = list.foldLeft(new ArrayBuffer[ArrayBuffer[T]])((ret, el) => {
    if (ret.isEmpty || ret.last.head != el)
      ret.append(new ArrayBuffer[T])
    ret.last.append(el)
    ret
  })

为好奇的读者留下了两个简单的任务:

  • 使用List prepending
  • 使其纯粹功能化
  • 制作正确的返回类型

答案 3 :(得分:0)

不,scanLeft在这种情况下不是合适的方法。 scanLeft将生成一个结果集合,其中包含列表中每个值的一个元素,以及您提供的初始值。因此,例如在scanLeft上使用List(1,2,3,4)将始终产生包含5个元素的结果

e.g。

scala> List(1,2,3,4).scanLeft(">"){case (last, x) => last + x}
res7: List[String] = List(>, >1, >12, >123, >1234)

答案 4 :(得分:0)

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

// val list = List('a, 'a, 'a, 'a, 'b, 'c, 'c, 'a, 'a, 'd, 'e, 'e, 'e, 'e)
List.unfold(list) {
  case Nil  => None
  case rest => Some(rest.span(_ == rest.head))
}
// List[List[Symbol]] = List(List('a, 'a, 'a, 'a), List('b), List('c, 'c), List('a, 'a), List('d), List('e, 'e, 'e, 'e))

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

List.unfold(list) {
  rest => Option.unless(rest.isEmpty)(rest.span(_ == rest.head))
}

详细信息:

  • “展开”使用的内部状态在我们的案例中是用list初始化的,以拆分List('a, 'a, 'a, 'a, 'b, 'c, 'c, 'a, 'a, 'd, 'e, 'e, 'e, 'e)
  • 在每次迭代中,我们span处于该内部状态,以查找包含相同符号的前缀:l.span(_ == l.head),该符号在第一次迭代l.span(_ == 'a)期间给出(List('a, 'a, 'a, 'a),List('b, 'c, 'c, 'a, 'a, 'd, 'e, 'e, 'e, 'e))
  • 正如unfold期望的那样,元组的Option每次迭代的第一部分是要添加到正在构建的列表中的新元素(此处为List('a, 'a, 'a, 'a)),第二部分是内部状态的新值(此处为List('b, 'c, 'c, 'a, 'a, 'd, 'e, 'e, 'e, 'e)),该值正好符合该要求。
  • 我们一直重复相同的步骤,直到内部列表为空,在这种情况下,我们通过返回unfold来告诉None我们已经完成。