【问题标题】:Split list into multiple lists with fixed number of elements将列表拆分为具有固定数量元素的多个列表
【发布时间】:2011-11-19 12:36:35
【问题描述】:

如何将元素列表拆分为最多包含 N 个项目的列表?

例如:给定一个包含 7 个元素的列表,创建 4 个组,最后一个组可能包含更少的元素。

split(List(1,2,3,4,5,6,"seven"),4)

=> List(List(1,2,3,4), List(5,6,"seven"))

【问题讨论】:

    标签: list scala split


    【解决方案1】:

    使用滑动方法可以更轻松地完成任务。 它是这样工作的:

    val numbers = List(1, 2, 3, 4, 5, 6 ,7)
    

    假设您想将列表分成大小为 3 的较小列表。

    numbers.sliding(3, 3).toList
    

    会给你

    List(List(1, 2, 3), List(4, 5, 6), List(7))
    

    【讨论】:

      【解决方案2】:

      我正在添加 split 方法的尾递归版本,因为有一些关于尾递归与递归的讨论。我使用了 tailrec 注释来强制编译器抱怨,以防实现确实不是尾递归的。我相信尾递归会变成一个循环,因此即使对于一个大列表也不会造成问题,因为堆栈不会无限增长。

      import scala.annotation.tailrec
      
      
      object ListSplitter {
      
        def split[A](xs: List[A], n: Int): List[List[A]] = {
          @tailrec
          def splitInner[A](res: List[List[A]], lst: List[A], n: Int) : List[List[A]] = {
            if(lst.isEmpty) res
            else {
              val headList: List[A] = lst.take(n)
              val tailList : List[A]= lst.drop(n)
              splitInner(headList :: res, tailList, n)
            }
          }
      
          splitInner(Nil, xs, n).reverse
        }
      
      }
      
      object ListSplitterTest extends App {
        val res = ListSplitter.split(List(1,2,3,4,5,6,7), 2)
        println(res)
      }
      

      【讨论】:

      • 这个答案可以通过添加一些解释来改进。鉴于接受的答案似乎是规范的、有意的方法,您应该解释为什么有人更喜欢这个答案。
      【解决方案3】:

      或者如果你想自己制作:

      def split[A](xs: List[A], n: Int): List[List[A]] = {
        if (xs.size <= n) xs :: Nil
        else (xs take n) :: split(xs drop n, n)
      }
      

      用途:

      scala> split(List(1,2,3,4,5,6,"seven"), 4)
      res15: List[List[Any]] = List(List(1, 2, 3, 4), List(5, 6, seven))
      

      编辑:两年后回顾这个,我不推荐这个实现,因为size 是 O(n),因此这个方法是 O(n^2),这将解释为什么内置方法对于大型列表变得更快,如下面的 cmets 所述。您可以按如下方式高效实施:

      def split[A](xs: List[A], n: Int): List[List[A]] =
        if (xs.isEmpty) Nil 
        else (xs take n) :: split(xs drop n, n)
      

      甚至(稍微)更有效地使用splitAt

      def split[A](xs: List[A], n: Int): List[List[A]] =
        if (xs.isEmpty) Nil 
        else {
          val (ys, zs) = xs.splitAt(n)   
          ys :: split(zs, n)
        }
      

      【讨论】:

      • xs splitAt nxs take nxs drop n 组合的替代方案
      • 这会爆炸堆栈,考虑递归实现
      • @Kipton,是的,但是您需要将结果提取到临时 val,因此它会在方法中添加几行。我做了一个快速的基准测试,似乎使用splitAt 而不是take/drop平均提高了大约 4% 的性能;两者都比 .grouped(n).toList 快 700-1000%!
      • @Luigi,哇。关于为什么grouped-toList 这么慢的任何想法?这听起来像一个错误。
      • @Jed 你在极端情况下是对的,但你的实现取决于你使用它的目的。对于 OP 的用例(如果 grouped 不存在:)),简单是最重要的因素。对于标准库,稳定性和性能应该胜过优雅。但是在 Scala 编程 和标准递归(而不是尾递归)调用的标准库中都有很多示例;它是 FP 工具箱中的标准且重要的武器。
      【解决方案4】:

      我认为这是使用 splitAt 而不是 take/drop 的实现

      def split [X] (n:Int, xs:List[X]) : List[List[X]] =
          if (xs.size <= n) xs :: Nil
          else   (xs.splitAt(n)._1) :: split(n,xs.splitAt(n)._2)
      

      【讨论】:

        【解决方案5】:

        我认为您正在寻找grouped。它返回一个迭代器,但您可以将结果转换为列表,

        scala> List(1,2,3,4,5,6,"seven").grouped(4).toList
        res0: List[List[Any]] = List(List(1, 2, 3, 4), List(5, 6, seven))
        

        【讨论】:

        • Scala 列表可以满足所有需求。
        • 我有一个奇怪的问题。对于同样的情况,如果我将数据转换为序列,我会得到一个流对象。这是为什么呢?
        • @Rakshith 这听起来像是一个单独的问题。 Scala 有一个神秘的 gnome 可以选择数据结构,它为您选择了 Stream。如果你想要一个 List,你应该请求一个 List,但你也可以相信 gnome 的判断。
        猜你喜欢
        • 2015-03-28
        • 2022-01-04
        • 1970-01-01
        • 2021-08-10
        • 1970-01-01
        • 2018-03-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多