Розділити список на кілька списків із фіксованою кількістю елементів


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. Чому так?
Ракшит

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

редагувати : переглядаючи це 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, правда, але вам потрібно витягнути результати до тимчасових валей, тому він додає методу пару рядків. Я зробив швидкий показник, і, здається, використання splitAtзамість take/ dropпокращує ефективність в середньому близько 4%; обидва на 700-1000% швидше, ніж .grouped(n).toList!
Луїджі Плінге

@Luigi, Вау. Будь-які думки про те, чому grouped-toListтак повільно? Це звучить як помилка.
Кіптон Баррос

@Jed Ви маєте рацію в крайніх випадках, але ваша реалізація залежить від того, для чого ви її використовуєте. У випадку використання ОП (якщо 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.