Podziel listę na wiele list ze stałą liczbą elementów

119

Jak podzielić listę elementów na listy zawierające maksymalnie N elementów?

przykład: mając listę z 7 elementami, utwórz grupy po 4, pozostawiając ostatnią grupę możliwie z mniejszą liczbą elementów.

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

=> List(List(1,2,3,4), List(5,6,"seven"))
Johnny Everson
źródło

Odpowiedzi:

213

Myślę, że szukasz grouped. Zwraca iterator, ale możesz przekonwertować wynik na listę,

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))
Kipton Barros
źródło
25
Listy Scala mają coś do wszystkiego.
J Atkin
Mam dziwne pytanie. W tym samym przypadku, jeśli przekonwertuję dane na sekwencję, otrzymam obiekt Stream. Dlaczego?
Rakshith
3
@Rakshith To brzmi jak osobne pytanie. Scala ma tajemniczego gnoma, który wybiera strukturę danych i wybiera dla ciebie Strumień. Jeśli chcesz mieć listę, powinieneś poprosić o listę, ale możesz też po prostu zaufać ocenie gnoma.
Ion Freeman
12

O wiele łatwiej jest wykonać to zadanie metodą ślizgową. Działa to w ten sposób:

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

Powiedzmy, że chcesz podzielić listę na mniejsze listy o rozmiarze 3.

numbers.sliding(3, 3).toList

da tobie

List(List(1, 2, 3), List(4, 5, 6), List(7))
Dordże
źródło
9

Lub jeśli chcesz stworzyć własny:

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

Posługiwać się:

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

edytuj : po przejrzeniu tego 2 lata później nie polecałbym tej implementacji, ponieważ sizejest O (n), a zatem ta metoda to O (n ^ 2), co wyjaśniałoby, dlaczego wbudowana metoda staje się szybsza dla dużych list, jak zauważono w komentarzach poniżej. Możesz skutecznie wdrożyć w następujący sposób:

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

lub nawet (nieco) wydajniej używając 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)
  }
Luigi Plinge
źródło
4
xs splitAt njest alternatywą dla kombinacji xs take nixs drop n
Kipton Barros
1
to spowoduje eksplozję stosu, rozważ rekurencyjną implementację
Jed Wesley-Smith
@Kipton, prawda, ale musisz wyodrębnić wyniki do tymczasowych wartości, więc dodaje kilka wierszy do metody. Wykonałem szybki test porównawczy i wydaje się, że splitAtzamiast take/ droppoprawia wydajność średnio o około 4%; oba są o 700-1000% szybsze niż .grouped(n).toList!
Luigi Plinge
@Luigi, Wow. Jakieś myśli o tym, dlaczego grouped-toListjest tak wolny? To brzmi jak błąd.
Kipton Barros
@Jed Masz rację w skrajnych przypadkach, ale Twoja implementacja zależy od tego, do czego jej używasz. Dla przypadku użycia OP (jeśli groupednie istniał :)), prostota jest nadrzędnym czynnikiem. W standardowej bibliotece stabilność i wydajność powinny być ważniejsze od elegancji. Ale jest wiele przykładów zarówno w Programowaniu w Scali, jak iw standardowych bibliotekach wywołań normalnie rekurencyjnych (zamiast rekurencyjnych ogonowych); to standardowa i ważna broń w skrzynce z narzędziami FP.
Luigi Plinge
4

Dodam rekurencyjną wersję metody split, ponieważ była dyskusja na temat rekurencji ogonowej w porównaniu z rekurencją. Użyłem adnotacji tailrec, aby zmusić kompilator do narzekania w przypadku, gdy implementacja nie jest rzeczywiście rekuzyjna tailrec. Uważam, że rekurencja ogona zamienia się w pętlę pod maską, a zatem nie spowoduje problemów nawet w przypadku dużej listy, ponieważ stos nie będzie rosnąć w nieskończoność.

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)
}
Mikrofon
źródło
1
Tę odpowiedź można by poprawić, dodając pewne wyjaśnienie. Biorąc pod uwagę, że przyjęta odpowiedź wydaje się być kanonicznym, zamierzonym sposobem, aby to zrobić, powinieneś wyjaśnić, dlaczego ktoś wolałby tę odpowiedź.
Jeffrey Bosboom,
0

Myślę, że jest to implementacja wykorzystująca splitAt zamiast 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)
Hydrosan
źródło