Chia danh sách thành nhiều danh sách với số phần tử cố định


119

Làm thế nào để tách Danh sách các phần tử thành danh sách có nhiều nhất N mục?

ví dụ: Cho một danh sách có 7 phần tử, tạo nhóm 4 phần tử, để lại nhóm cuối cùng có thể có ít phần tử hơn.

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

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

Câu trả lời:


213

Tôi nghĩ bạn đang tìm kiếm grouped. Nó trả về một trình lặp, nhưng bạn có thể chuyển đổi kết quả thành một danh sách,

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
Danh sách Scala có một cái gì đó cho mọi thứ.
J Atkin

Tôi có một câu hỏi kỳ lạ. Đối với trường hợp tương tự nếu tôi chuyển đổi dữ liệu thành một chuỗi, tôi nhận được một Đối tượng Luồng. Tại sao vậy?
Rakshith

2
@Rakshith Điều đó nghe giống như một câu hỏi riêng biệt. Scala có một gnome bí ẩn chọn cấu trúc dữ liệu và nó đã chọn Luồng cho bạn. Nếu bạn muốn một Danh sách, bạn nên yêu cầu một Danh sách, nhưng bạn cũng có thể chỉ cần tin tưởng vào phán đoán của gnome.
Ion Freeman

12

Có nhiều cách dễ dàng hơn để thực hiện tác vụ bằng phương pháp trượt. Nó hoạt động theo cách này:

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

Giả sử bạn muốn chia danh sách thành các danh sách nhỏ hơn có kích thước 3.

numbers.sliding(3, 3).toList

sẽ cho bạn

List(List(1, 2, 3), List(4, 5, 6), List(7))

9

Hoặc nếu bạn muốn tự làm:

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

Sử dụng:

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

chỉnh sửa : khi xem xét điều này 2 năm sau, tôi sẽ không khuyến nghị triển khai này vì sizelà O (n), và do đó phương pháp này là O (n ^ 2), điều này sẽ giải thích tại sao phương pháp tích hợp trở nên nhanh hơn đối với các danh sách lớn, như đã lưu ý trong bình luận bên dưới. Bạn có thể triển khai hiệu quả như sau:

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

hoặc thậm chí (một chút) hiệu quả hơn bằng cách sử dụng 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 nlà một sự thay thế cho sự kết hợp xs take nxs drop n
Kipton Barros.

1
này sẽ nổ chồng, hãy xem xét việc thực hiện đệ quy
Jed Wesley-Smith

@Kipton, true, nhưng bạn cần trích xuất kết quả vào vals tạm thời để nó thêm một vài dòng vào một phương thức. Tôi đã thực hiện một điểm chuẩn nhanh và có vẻ như sử dụng splitAtthay vì take/ dropcải thiện hiệu suất trung bình khoảng 4%; cả hai đều nhanh hơn 700-1000% .grouped(n).toList!
Luigi Plinge

@Luigi, Chà. Bất kỳ suy nghĩ về lý do tại sao grouped-toListquá chậm? Điều đó nghe giống như một lỗi.
Kipton Barros

@Jed Bạn đúng trong những trường hợp cực đoan, nhưng việc triển khai của bạn phụ thuộc vào việc bạn đang sử dụng nó để làm gì. Đối với trường hợp sử dụng của OP (nếu groupedkhông tồn tại :)), sự đơn giản là yếu tố quan trọng. Đối với thư viện tiêu chuẩn, sự ổn định và hiệu suất sẽ vượt trội hơn sự sang trọng. Nhưng có rất nhiều ví dụ cả về Lập trình trong Scala và các thư viện chuẩn của các lệnh gọi đệ quy thông thường (chứ không phải đệ quy đuôi); nó là một vũ khí tiêu chuẩn và quan trọng trong hộp công cụ FP.
Luigi Plinge

4

Tôi đang thêm một phiên bản đệ quy đuôi của phương pháp tách vì đã có một số cuộc thảo luận về đệ quy đuôi so với đệ quy. Tôi đã sử dụng chú thích tailrec để buộc trình biên dịch phải phàn nàn trong trường hợp việc triển khai thực sự không đúng đuôi. Tôi tin rằng đệ quy đuôi biến thành một vòng lặp dưới mui xe và do đó sẽ không gây ra vấn đề ngay cả đối với một danh sách lớn vì ngăn xếp sẽ không phát triển vô thời hạn.

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
Câu trả lời này có thể được cải thiện bằng cách thêm một số giải thích. Cho rằng câu trả lời được chấp nhận dường như là phương pháp chuẩn, dành cho việc này, bạn nên giải thích lý do tại sao ai đó lại thích câu trả lời này.
Jeffrey Bosboom

0

Tôi nghĩ đây là cách triển khai sử dụng splitAt thay vì 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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.