Pisahkan daftar menjadi beberapa daftar dengan jumlah elemen tetap

119

Bagaimana cara membagi Daftar elemen menjadi daftar dengan paling banyak N item?

mis .: Diberikan daftar dengan 7 elemen, buat grup yang terdiri dari 4 elemen, biarkan grup terakhir mungkin memiliki lebih sedikit elemen.

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

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

Jawaban:

213

Saya pikir Anda sedang mencari grouped. Ini mengembalikan iterator, tetapi Anda dapat mengonversi hasilnya menjadi daftar,

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
sumber
25
Daftar Scala memiliki sesuatu untuk segalanya.
J Atkin
Saya punya pertanyaan aneh. Untuk kasus yang sama jika saya mengonversi data menjadi urutan, saya mendapatkan Objek Stream. Mengapa demikian?
Rakshith
3
@Rakshith Kedengarannya seperti pertanyaan terpisah. Scala memiliki gnome misterius yang memilih struktur data, dan memilih Aliran untuk Anda. Jika Anda menginginkan Daftar, Anda harus meminta Daftar, tetapi Anda juga dapat mempercayai penilaian gnome.
Ion Freeman
12

Ada cara yang jauh lebih mudah untuk melakukan tugas tersebut menggunakan metode geser. Ini bekerja seperti ini:

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

Katakanlah Anda ingin memecah daftar menjadi daftar ukuran 3 yang lebih kecil.

numbers.sliding(3, 3).toList

akan memberimu

List(List(1, 2, 3), List(4, 5, 6), List(7))
Dorjee
sumber
9

Atau jika Anda ingin membuatnya sendiri:

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

Menggunakan:

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

sunting : setelah meninjau ini 2 tahun kemudian, saya tidak akan merekomendasikan implementasi ini karena sizeadalah O (n), dan karenanya metode ini adalah O (n ^ 2), yang akan menjelaskan mengapa metode built-in menjadi lebih cepat untuk daftar besar, seperti yang tercantum dalam komentar di bawah. Anda dapat mengimplementasikan secara efisien sebagai berikut:

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

atau bahkan (sedikit) lebih efisien menggunakan 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
sumber
4
xs splitAt nadalah alternatif untuk kombinasi xs take ndanxs drop n
Kipton Barros
1
ini akan meledakkan tumpukan, pertimbangkan penerapan rekursif
Jed Wesley-Smith
@Kipton, benar, tetapi Anda perlu mengekstrak hasil ke nilai sementara sehingga menambahkan beberapa baris ke metode. Saya melakukan benchmark cepat dan tampaknya menggunakan splitAtbukannya take/ dropmeningkatkan kinerja rata-rata sekitar 4%; keduanya 700-1000% lebih cepat dari .grouped(n).toList!
Luigi Plinge
@Luigi, Wow. Ada pemikiran tentang mengapa grouped-toListbegitu lambat? Kedengarannya seperti bug.
Kipton Barros
@Jed Anda benar dalam kasus ekstrim, tetapi penerapan Anda bergantung pada tujuan Anda menggunakannya. Untuk kasus penggunaan OP (jika groupedtidak ada :)), kesederhanaan adalah faktor utama. Untuk pustaka standar, stabilitas dan kinerja harus mengalahkan keanggunan. Tapi ada banyak contoh baik dalam Pemrograman di Scala dan pustaka standar panggilan rekursif normal (bukan rekursif ekor); itu adalah senjata standar dan penting dalam kotak peralatan FP.
Luigi Plinge
4

Saya menambahkan versi rekursif ekor dari metode split karena ada beberapa diskusi tentang rekursi ekor versus rekursi. Saya telah menggunakan anotasi tailrec untuk memaksa compiler untuk mengeluh jika implementasinya tidak benar-benar bersifat tail-recusive. Saya yakin rekursi ekor berubah menjadi loop di bawah kap dan dengan demikian tidak akan menimbulkan masalah bahkan untuk daftar besar karena tumpukan tidak akan tumbuh tanpa batas.

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)
}
Mike
sumber
1
Jawaban ini bisa diperbaiki dengan menambahkan beberapa penjelasan. Mengingat bahwa jawaban yang diterima tampaknya kanonik, cara yang dimaksudkan untuk melakukan ini, Anda harus menjelaskan mengapa seseorang lebih suka jawaban ini.
Jeffrey Bosboom
0

Saya pikir ini adalah implementasi menggunakan splitAt, bukan 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
sumber