将列表分为具有固定数量元素的多个列表


119

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

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

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

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

Answers:


213

我认为您正在寻找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))

25
Scala列表可以满足所有需求。
J Atkin

我有一个奇怪的问题。对于相同的情况,如果我将数据转换为序列,则会得到一个流对象。这是为什么?
Rakshith

2
@Rakshith听起来像一个单独的问题。Scala有一个神秘的侏儒,可以选择数据结构,并为您选择了Stream。如果您想要一个列表,则应该请求一个列表,但是您也可以相信侏儒的判断。
离子弗里曼

12

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

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))

9

或者,如果您想自己制作:

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))

编辑:在两年后的回顾中,由于sizeO(n),因此我不推荐这种实现,因此该方法为O(n ^ 2),这将解释为什么内置方法对于大型列表变得更快,如以下评论中所述。您可以按以下方式有效实施:

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)
  }

4
xs splitAt n是该组合的替代方案,xs take n以及xs drop n
Kipton Barros 2011年

1
这将使堆栈爆炸,请考虑递归实现
Jed Wesley-Smith

@Kipton,是的,但是您需要将结果提取到临时val中,因此它会向方法添加几行。我做了一个快速的基准测试,似乎使用splitAt代替take/将drop性能平均提高了大约4%;两者都比700-1000%快.grouped(n).toList
Luigi Plinge 2011年

@路易吉,哇。关于为什么grouped-toList这么慢有什么想法吗?听起来像个虫子。
基普顿·巴罗斯

@Jed在极端情况下您是对的,但是您的实现取决于您使用它的目的。对于OP的用例(如果grouped不存在:)),简单性是首要因素。对于标准库,稳定性和性能应胜过优雅。但是在Scala编程和普通递归(而不是尾递归)调用的标准库中都有很多示例。它是FP工具箱中的标准且重要的武器。
Luigi Plinge 2011年

4

由于添加了尾递归与递归的讨论,因此我添加了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)
}

1
可以通过添加一些解释来改善此答案。鉴于已接受的答案似乎是规范的预期方法,因此您应该解释为什么有人会喜欢此答案。
Jeffrey Bosboom '16

0

我认为这是使用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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.