【问题标题】:Scala List function for grouping consecutive identical elements用于对连续相同元素进行分组的 Scala List 函数
【发布时间】:2011-06-13 06:44:36
【问题描述】:

例如:

List(5, 2, 3, 3, 3, 5, 5, 3, 3, 2, 2, 2)

我想去:

List(List(5), List(2), List(3, 3, 3), List(5, 5), List(3, 3), List(2, 2, 2))

我假设有一个简单的 List 函数可以做到这一点,但我找不到它。

【问题讨论】:

    标签: scala collections


    【解决方案1】:

    这是我通常使用的技巧:

    def split[T](list: List[T]) : List[List[T]] = list match {
      case Nil => Nil
      case h::t => val segment = list takeWhile {h ==}
        segment :: split(list drop segment.length)
    }
    

    其实...不是,我通常对集合类型进行抽象并使用尾递归进行优化,但希望答案保持简单。

    【讨论】:

    • 不错。但这不是足够普遍以保证它自己的库功能吗?
    • @KevinWright "也使用尾递归进行优化" -> 你会怎么做?
    • 没关系 - 我刚刚了解了 tailrec。另外,如果你有一个 Iterable 而不是列表,如何通过惰性评估来做到这一点?
    • @vishvAs vAsuki:发布了一个tailrec解决方案below
    • 您能否解释一下您的解决方案?具体来说,t 来自哪里?
    【解决方案2】:
    val xs = List(5, 2, 3, 3, 3, 5, 5, 3, 3, 2, 2, 2)
    

    这是另一种方式。

    (List(xs.take(1)) /: xs.tail)((l,r) =>
      if (l.head.head==r) (r :: l.head) :: l.tail else List(r) :: l
    ).reverseMap(_.reverse)
    

    【讨论】:

      【解决方案3】:
      list.foldRight(List[List[Int]]()){
        (e, l) => l match {
          case (`e` :: xs) :: fs => (e :: e :: xs) :: fs
          case _ => List(e) :: l
        }
      }
      

      或者

      list.zip(false :: list.sliding(2).collect{case List(a,b) => a == b}.toList)
       .foldLeft(List[List[Int]]())((l,e) => if(e._2) (e._1 :: l.head) :: l.tail 
                                             else List(e._1) :: l ).reverse
      

      [编辑]

      //find the hidden way 
      //the beauty must be somewhere
      //when we talk scala
      
      def split(l: List[Int]): List[List[Int]] = 
        l.headOption.map{x => val (h,t)=l.span{x==}; h::split(t)}.getOrElse(Nil)
      

      【讨论】:

      • 任何方法都适用于 NaN 值吗?我试图弄清楚我是否在 Vector 中看到 n 个连续的 Double.NaN 值。看看这里发布的解决方案,这似乎不是我自己想出来的。
      • NaN 值确实需要特殊处理。也许您可以将Ints 包装在Option[Int] 中,并将NaNs 转换为Nones。然后通常基于平等的解决方案将起作用。或者您可以编写自己的相等函数。
      【解决方案4】:

      该死的 Rex Kerr,写下我想要的答案。由于存在细微的风格差异,这是我的看法:

      list.tail.foldLeft(List(list take 1)) { 
          case (acc @ (lst @ hd :: _) :: tl, el) => 
              if (el == hd) (el :: lst) :: tl 
              else (el :: Nil) :: acc 
      }
      

      由于元素相同,我没有费心反转子列表。

      【讨论】:

      • 你能解释一下(或指向一些链接)案例吗(acc @ (lst @ hd :: _)。我以前从未见过这样的语法。提前致谢。
      • @gashu 在模式匹配中,x @ y 表示x 将被分配给y匹配的任何内容。因此,例如,x @ _ :: _ 将分配给 x 一个非空列表(即,与 _ :: _ 匹配的具有头部和尾部的列表)。所以上面的acc 是整个列表,lst 是列表的头部。
      • @daniel-c-sobral 所以这样的表达式只用于模式匹配,或者在其他上下文中,它们确实意味着不同的东西?
      • @gashu @ 也用于注解,与 Java 非常相似。它看起来一点也不像上面,但它也是 at 符号的使用。
      【解决方案5】:

      我在处理集合方法时有这些实现。最后,我签入了更简单的 inits 和 tails 实现,并省略了集群。每一种新方法,无论多么简单,最终都会征收一笔从外部很难看到的巨额税款。但这是我没有使用的实现。

      import generic._
      import scala.reflect.ClassManifest
      import mutable.ListBuffer
      import annotation.tailrec
      import annotation.unchecked.{ uncheckedVariance => uV }
      
      def inits: List[Repr] = repSequence(x => (x, x.init), Nil)
      def tails: List[Repr] = repSequence(x => (x, x.tail), Nil)
      def cluster[A1 >: A : Equiv]: List[Repr] =
        repSequence(x => x.span(y => implicitly[Equiv[A1]].equiv(y, x.head)))
      
      private def repSequence(
        f: Traversable[A @uV] => (Traversable[A @uV], Traversable[A @uV]),
        extras: Traversable[A @uV]*): List[Repr] = {
      
        def mkRepr(xs: Traversable[A @uV]): Repr = newBuilder ++= xs result
        val bb = new ListBuffer[Repr]
      
        @tailrec def loop(xs: Repr): List[Repr] = {
          val seq = toCollection(xs)
          if (seq.isEmpty)
            return (bb ++= (extras map mkRepr)).result
      
          val (hd, tl) = f(seq)
          bb += mkRepr(hd)
          loop(mkRepr(tl))
        }
      
        loop(self.repr)
      }
      

      [编辑:我忘记其他人不会知道内部情况。这段代码是从 TraversableLike 内部编写的,所以它不会开箱即用。]

      【讨论】:

        【解决方案6】:

        这可能更简单:

        val input = List(5, 2, 3, 3, 3, 5, 5, 3, 3, 2, 2, 2)
        input groupBy identity values
        

        【讨论】:

        • 这将分组所有相同的元素,而不仅仅是顺序。
        【解决方案7】:

        这是一个受@Kevin Wright 和@Landei 启发的尾递归解决方案:

        @tailrec
        def sliceEqual[A](s: Seq[A], acc: Seq[Seq[A]] = Seq()): Seq[Seq[A]] = {
          s match {
            case fst :: rest =>
              val (l, r) = s.span(fst==)
              sliceEqual(r, acc :+ l)
            case Nil => acc
          }
        }
        

        【讨论】:

          【解决方案8】:

          这是一个稍微干净一点的:

          def groupConsequtive[A](list: List[A]): List[List[A]] = list match {
            case head :: tail =>
              val (t1, t2) = tail.span(_ == head)
              (head :: t1) :: groupConsequtive(t2)
            case _ => Nil  
          }
          

          尾递归版本

          @tailrec
          def groupConsequtive[A](list: List[A], acc: List[List[A]] = Nil): List[List[A]] = list match {
            case head :: tail =>
              val (t1, t2) = tail.span(_ == head)
              groupConsequtive(t2, acc :+ (head :: t1))
            case _ => acc
          }
          

          【讨论】:

          • 据我了解 - 尾递归解决方案的缺点是前置操作的时间效率较低,不是吗?
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-05-05
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多