Разделить список на несколько списков с фиксированным количеством элементов


119

Как разбить список элементов на списки, содержащие не более N элементов?

Пример: учитывая список из 7 элементов, создайте группы из 4, оставив последнюю группу, возможно, с меньшим количеством элементов.

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

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

Ответы:


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

У меня странный вопрос. В том же случае, если я конвертирую данные в последовательность, я получаю объект Stream. Это почему?
— Rakshith

3
@Rakshith Звучит как отдельный вопрос. В Scala есть загадочный гном, который выбирает структуру данных, и он выбрал для вас поток. Если вам нужен список, вы должны запросить список, но вы также можете просто доверять мнению гнома.
— Ион Фриман

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

edit : просмотрев это 2 года спустя, я бы не рекомендовал эту реализацию, поскольку sizeэто O (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
— Киптон Баррос

1
это взорвет стек, рассмотрим рекурсивную реализацию
— Джед Уэсли-Смит

@Kipton, правда, но вам нужно извлечь результаты во временные vals, чтобы он добавил пару строк в метод. Я провел быстрый тест, и похоже, что он использует splitAtвместо take/ dropповышает производительность в среднем на 4%; оба на 700-1000% быстрее .grouped(n).toList!
— Луиджи Плиндж

@ Луиджи, Вау. Есть мысли о том, почему grouped-toListтак медленно? Звучит как ошибка.
— Киптон Баррос,

@Jed В крайних случаях вы правы, но ваша реализация зависит от того, для чего вы ее используете. Для варианта использования OP (если groupedне существует :)) простота является решающим фактором. Для стандартной библиотеки стабильность и производительность должны быть важнее элегантности. Но есть множество примеров как в Программе на Scala, так и в стандартных библиотеках нормально-рекурсивных (а не хвостово-рекурсивных) вызовов; это стандартное и важное оружие в наборе инструментов FP.
— Луиджи Плиндж

4

Я добавляю хвостовую рекурсивную версию метода разделения, поскольку было некоторое обсуждение хвостовой рекурсии по сравнению с рекурсией. Я использовал аннотацию 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
Этот ответ можно улучшить, добавив некоторые пояснения. Учитывая, что принятый ответ кажется каноническим, предполагаемым способом сделать это, вы должны объяснить, почему кто-то предпочел бы этот ответ.
— Джеффри Босбум,

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)
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.