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

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

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

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

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

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)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.