Ghép nối danh sách Scala, ::: vs ++


362

Có sự khác biệt nào giữa :::++cho các danh sách nối trong Scala không?

scala> List(1,2,3) ++ List(4,5)
res0: List[Int] = List(1, 2, 3, 4, 5)

scala> List(1,2,3) ::: List(4,5)
res1: List[Int] = List(1, 2, 3, 4, 5)

scala> res0 == res1
res2: Boolean = true

Từ các tài liệu có vẻ như ++là tổng quát hơn trong khi đó :::là đặc Listthù. Là cái sau được cung cấp bởi vì nó được sử dụng trong các ngôn ngữ chức năng khác?


4
Cũng :::là một toán tử tiền tố giống như tất cả các phương thức bắt đầu bằng:
Ben Jackson

3
Các câu trả lời mô tả khá rõ cách thức scala được phát triển xung quanh các danh sách và tính đồng nhất của nhà điều hành trong Scala (hoặc thiếu cái sau). Có một chút đáng tiếc rằng một cái gì đó đơn giản lại có một cái đuôi rất nhỏ để gây nhầm lẫn và lãng phí thời gian của bất kỳ người học Scala nào. Tôi ước nó sẽ bị chững lại trong 2.12.
matanster

Câu trả lời:


321

Di sản. Danh sách ban đầu được định nghĩa là tìm ngôn ngữ chức năng:

1 :: 2 :: Nil // a list
list1 ::: list2  // concatenation of two lists

list match {
  case head :: tail => "non-empty"
  case Nil          => "empty"
}

Tất nhiên, Scala đã phát triển các bộ sưu tập khác, theo cách thức đặc biệt. Khi 2.8 xuất hiện, các bộ sưu tập được thiết kế lại để sử dụng lại mã tối đa và API nhất quán, để bạn có thể sử dụng ++để ghép bất kỳ hai bộ sưu tập nào - và thậm chí cả các trình vòng lặp. Danh sách, tuy nhiên, phải giữ các nhà khai thác ban đầu của nó, ngoài một hoặc hai đã bị phản đối.


19
Vì vậy, nó là thực hành tốt nhất để tránh :::ủng hộ ++bây giờ? Cũng dùng +:thay ::?
Luigi Plinge

37
::là hữu ích vì khớp mẫu (xem ví dụ thứ hai của Daniel). Bạn không thể làm điều đó với+:
nghịch lý

1
@Luigi Nếu bạn đang sử dụng Listthay vì Seq, bạn cũng có thể sử dụng các Listphương thức thành ngữ . Mặt khác, nó sẽ làm cho việc thay đổi sang loại khác trở nên khó khăn hơn , nếu bạn muốn làm như vậy.
Daniel C. Sobral

2
Tôi thấy thật tốt khi người ta có cả hai hoạt động thành ngữ List (thích :::::) và hoạt động chung hơn chung cho các bộ sưu tập khác. Tôi sẽ không bỏ hoạt động từ ngôn ngữ.
Giorgio

21
@paradigmatic Scala 2.10 có :++:trích xuất đối tượng.
0__

97

Luôn luôn sử dụng :::. Có hai lý do: hiệu quả và an toàn loại.

Hiệu quả

x ::: y ::: zlà nhanh hơn x ++ y ++ z, bởi vì :::là kết hợp đúng. x ::: y ::: zđược phân tích cú pháp dưới dạng x ::: (y ::: z), thuật toán nhanh hơn (x ::: y) ::: z(sau này yêu cầu O (| x |) nhiều bước hơn).

Loại an toàn

Với :::bạn chỉ có thể nối hai Lists. Với ++bạn có thể nối bất kỳ bộ sưu tập nào List, điều này thật tồi tệ:

scala> List(1, 2, 3) ++ "ab"
res0: List[AnyVal] = List(1, 2, 3, a, b)

++cũng dễ dàng trộn lẫn với +:

scala> List(1, 2, 3) + "ab"
res1: String = List(1, 2, 3)ab

9
Khi kết hợp chỉ 2 danh sách, không có sự khác biệt, nhưng trong trường hợp từ 3 trở lên, bạn có một điểm tốt và tôi đã xác nhận nó với điểm chuẩn nhanh. Tuy nhiên, nếu bạn lo lắng về hiệu quả, x ::: y ::: znên được thay thế bằng List(x, y, z).flatten. pastebin.com/gkx7Hpad
Luigi Plinge

3
Vui lòng giải thích, tại sao kết hợp liên kết trái đòi hỏi nhiều bước O (x) hơn. Tôi nghĩ rằng cả hai đều làm việc cho O (1).
pacman

6
@pacman Danh sách được liên kết đơn lẻ, để nối thêm danh sách này với danh sách khác, bạn cần tạo một bản sao của danh sách đầu tiên có danh sách thứ hai được đính kèm ở cuối. Do đó, sự kết hợp là O (n) đối với số lượng phần tử trong danh sách đầu tiên. Độ dài của danh sách thứ hai không ảnh hưởng đến thời gian chạy, vì vậy tốt hơn là nối một danh sách dài vào một danh sách ngắn thay vì nối thêm một danh sách ngắn vào một danh sách dài.
puhlen

1
Danh sách của @pacman Scala là bất biến . Đó là lý do tại sao chúng ta không thể thay thế liên kết cuối cùng khi thực hiện nối. Chúng ta phải tạo một danh sách mới từ đầu.
ZhekaKozlov

4
@pacman Độ phức tạp luôn luôn là tuyến tính với độ dài xy( zkhông bao giờ được lặp lại trong mọi trường hợp nên không ảnh hưởng đến thời gian chạy, đây là lý do tại sao nên nối một danh sách dài vào một danh sách ngắn, so với cách khác sự phức tạp tiệm cận không nói lên toàn bộ câu chuyện. x ::: (y ::: z)lặp đi lặp lại yvà nối thêm z, sau đó lặp lại xvà nối thêm kết quả của y ::: z. xyđược lặp lại một lần. (x ::: y) ::: zlặp đi lặp lại xvà nối thêm y, sau đó lặp lại kết quả x ::: yvà nối thêm z. yvẫn được lặp lại một lần nhưng xđược lặp lại hai lần trong trường hợp này.
puhlen

84

:::chỉ hoạt động với danh sách, trong khi ++có thể được sử dụng với bất kỳ giao dịch nào. Trong triển khai hiện tại (2.9.0), hãy ++quay lại :::nếu đối số cũng là a List.


4
Vì vậy, rất dễ sử dụng cả ::: và ++ làm việc với danh sách. Điều đó có khả năng có thể đặt một mớ hỗn độn để mã / phong cách.
vừng

24

Một điểm khác là câu đầu tiên được phân tích cú pháp là:

scala> List(1,2,3).++(List(4,5))
res0: List[Int] = List(1, 2, 3, 4, 5)

Trong khi ví dụ thứ hai được phân tích cú pháp là:

scala> List(4,5).:::(List(1,2,3))
res1: List[Int] = List(1, 2, 3, 4, 5)

Vì vậy, nếu bạn đang sử dụng macro, bạn nên cẩn thận.

Bên cạnh đó, ++đối với hai danh sách đang gọi :::nhưng có nhiều chi phí hơn vì nó yêu cầu một giá trị ngầm định để có một người xây dựng từ Danh sách đến Danh sách. Nhưng microbenchmark không chứng minh được điều gì hữu ích theo nghĩa đó, tôi đoán rằng trình biên dịch tối ưu hóa các cuộc gọi như vậy.

Điểm chuẩn vi mô sau khi khởi động.

scala>def time(a: => Unit): Long = { val t = System.currentTimeMillis; a; System.currentTimeMillis - t}
scala>def average(a: () => Long) = (for(i<-1 to 100) yield a()).sum/100

scala>average (() => time { (List[Int]() /: (1 to 1000)) { case (l, e) => l ++ List(e) } })
res1: Long = 46
scala>average (() => time { (List[Int]() /: (1 to 1000)) { case (l, e) => l ::: List(e ) } })
res2: Long = 46

Như Daniel C. Sobrai đã nói, bạn có thể nối thêm nội dung của bất kỳ bộ sưu tập nào vào danh sách bằng cách sử dụng ++, trong khi với :::bạn chỉ có thể ghép các danh sách.


20
Xin vui lòng gửi các dấu hiệu vi mô không quá đơn giản của bạn và tôi sẽ bỏ phiếu cho họ.
Mikaël Mayer
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.