Khi nào tôi nên chọn Vector trong Scala?


200

Có vẻ như Vectorđã muộn cho bữa tiệc của bộ sưu tập Scala và tất cả các bài đăng trên blog có ảnh hưởng đã rời đi.

Trong Java ArrayListlà bộ sưu tập mặc định - tôi có thể sử dụng LinkedListnhưng chỉ khi tôi nghĩ qua thuật toán và quan tâm đủ để tối ưu hóa. Trong Scala tôi nên sử dụng Vectorlàm mặc định của mình Seqhay cố gắng giải quyết khi nào Listthực sự phù hợp hơn?


1
Tôi đoán điều tôi muốn nói ở đây là trong Java tôi sẽ tạo List<String> l = new ArrayList<String>()các blog Scala viết, bạn có tin rằng mọi người đều sử dụng Danh sách để có được sự tốt lành của bộ sưu tập không - nhưng liệu mục đích chung của Vector có đủ để chúng ta nên sử dụng nó ở vị trí của List không?
Duncan McGregor

9
@Debilski: Tôi đang tự hỏi ý của bạn là gì Tôi nhận được một Listkhi tôi gõ Seq()tại REPL.
missingfaktor

1
Hmm, tốt, nó nói như vậy trong các tài liệu. Có lẽ điều này chỉ đúng với IndexedSeq.
Debilski

1
Nhận xét về loại bê tông mặc định Seqlà hơn ba năm tuổi. Kể từ Scala 2.11.4 (và trước đó), loại bê tông mặc định SeqList.
Đánh dấu Canlas

3
Đối với truy cập ngẫu nhiên, vector là tốt hơn. Đối với đầu, đuôi truy cập, danh sách là tốt hơn. Đối với hoạt động hàng loạt, chẳng hạn như ánh xạ, bộ lọc, vectơ được ưa thích vì vectơ được tổ chức với 32 phần tử dưới dạng khối trong khi danh sách được sắp xếp các phần tử có con trỏ với nhau, không có gì đảm bảo các phần tử này gần nhau.
johnsam

Câu trả lời:


280

Như một quy tắc chung, mặc định để sử dụng Vector. Đó là nhanh hơn so Listvới hầu hết tất cả mọi thứ và bộ nhớ hiệu quả hơn cho các chuỗi có kích thước lớn hơn tầm thường. Xem tài liệu này về hiệu suất tương đối của Vector so với các bộ sưu tập khác. Có một số nhược điểm đi cùng Vector. Đặc biệt:

  • Cập nhật ở phần đầu chậm hơn List(mặc dù không nhiều như bạn nghĩ)

Một nhược điểm khác trước Scala 2.10 là hỗ trợ khớp mẫu tốt hơn List, nhưng điều này đã được khắc phục trong 2.10 với các trình trích xuất +:và tổng quát :+.

Ngoài ra còn có một cách trừu tượng hơn, đại số hơn để tiếp cận câu hỏi này: về mặt khái niệm bạn có loại trình tự nào ? Ngoài ra, anh đang khái niệm làm với nó? Nếu tôi thấy một hàm trả về một Option[A], tôi biết rằng hàm đó có một số lỗ hổng trong miền của nó (và do đó là một phần). Chúng ta có thể áp dụng logic tương tự cho các bộ sưu tập.

Nếu tôi có một chuỗi các loại List[A], tôi thực sự khẳng định hai điều. Đầu tiên, thuật toán của tôi (và dữ liệu) hoàn toàn có cấu trúc ngăn xếp. Thứ hai, tôi khẳng định rằng những điều duy nhất tôi sẽ làm với bộ sưu tập này là đầy đủ, O (n) đi qua. Hai người này thực sự đi đôi với nhau. Ngược lại, nếu tôi có thứ gì đó thuộc loại Vector[A], điều duy nhất tôi khẳng định là dữ liệu của tôi có thứ tự được xác định rõ và độ dài hữu hạn. Do đó, các xác nhận là yếu hơn với Vector, và điều này dẫn đến tính linh hoạt cao hơn của nó.


2
2.10 đã ra mắt được một thời gian, liệu mô hình Danh sách phù hợp vẫn tốt hơn Vector?
Tim Gautier

3
Khớp mẫu danh sách không còn tốt hơn nữa. Trên thực tế, nó hoàn toàn ngược lại. Ví dụ, để có được đầu và đuôi người ta có thể làm case head +: tailhoặc case tail :+ head. Để phù hợp với trống, bạn có thể làm case Seq()và vv. Tất cả những gì bạn cần là có trong API, đó là linh hoạt hơn List's
Kai Sellgren

Listđược thực hiện với một danh sách liên kết đơn. Vectorđược triển khai một cái gì đó giống như Java ArrayList.
Josiah Yoder

6
@JosiahYoder Nó được triển khai không có gì giống như ArrayList. ArrayList kết thúc một mảng mà nó tự động thay đổi kích thước. Vector là một trie , trong đó các khóa là chỉ mục của các giá trị.
John Colanduoni

1
Tôi xin lỗi. Tôi đang truy cập một nguồn web mơ hồ về các chi tiết. Tôi có nên sửa câu nói trước đây của mình không? Hay đó là hình thức xấu?
Josiah Yoder

93

Chà, Listcó thể cực kỳ nhanh nếu thuật toán có thể được thực hiện chỉ với ::, headtail. Gần đây tôi đã có một bài học về đối tượng, khi tôi đánh bại Java splitbằng cách tạo ra một Listthay vì Arrayvà không thể đánh bại điều đó bằng bất cứ thứ gì khác.

Tuy nhiên, Listcó một vấn đề cơ bản: nó không hoạt động với các thuật toán song song. Tôi không thể chia Listthành nhiều phân đoạn hoặc nối lại nó một cách hiệu quả.

Có những loại bộ sưu tập khác có thể xử lý song song tốt hơn nhiều - và Vectorlà một trong số đó. Vectorcũng có địa phương tuyệt vời - Listkhông - có thể là điểm cộng thực sự cho một số thuật toán.

Vì vậy, tất cả những điều được xem xét, Vectorlà lựa chọn tốt nhất trừ khi bạn có những cân nhắc cụ thể làm cho một trong các bộ sưu tập khác trở nên thích hợp hơn - ví dụ: bạn có thể chọn Streamnếu bạn muốn đánh giá lười biếng và lưu vào bộ đệm ( Iteratornhanh hơn nhưng không lưu vào bộ đệm) hoặc Listnếu thuật toán được thực hiện một cách tự nhiên với các hoạt động tôi đã đề cập.

Bằng cách này, nó là thích hợp hơn để sử dụng Seqhoặc IndexedSeqtrừ khi bạn muốn có một phần cụ thể của API (chẳng hạn như List's ::), hoặc thậm chí GenSeqhoặc GenIndexedSeqnếu thuật toán của bạn có thể chạy song song.


3
Cảm ơn câu trả lời. Bạn có ý nghĩa gì bởi "có địa phương tuyệt vời"?
Ngọc Đào

10
@ngocdaothanh Điều đó có nghĩa là dữ liệu được nhóm lại gần nhau trong bộ nhớ, cải thiện khả năng dữ liệu sẽ nằm trong bộ đệm khi bạn cần.
Daniel C. Sobral

1
@ user247077 Có, Danh sách có thể đánh bại các vectơ trong hiệu suất do các chi tiết tôi đã đề cập. Và không phải tất cả các hành động của vectơ đều được khấu hao O (1). Trong thực tế, trên các cấu trúc dữ liệu bất biến (đó là trường hợp), các thao tác chèn / xóa thay thế ở hai đầu sẽ không được khấu hao chút nào. Trong trường hợp đó, bộ đệm là vô dụng vì bạn luôn sao chép vectơ.
Daniel C. Sobral

1
@ user247077 Có lẽ bạn không biết đó Vectorlà cấu trúc dữ liệu bất biến trong Scala?
Daniel C. Sobral

1
@ user247077 Nó phức tạp hơn thế, bao gồm một số nội dung có thể thay đổi bên trong để làm cho giá rẻ hơn, nhưng khi bạn sử dụng nó như một ngăn xếp, đó là kịch bản tối ưu danh sách bất biến, cuối cùng bạn vẫn có các đặc điểm bộ nhớ tương tự của danh sách được liên kết, nhưng với một hồ sơ phân bổ bộ nhớ lớn hơn nhiều.
Daniel C. Sobral

29

Một số câu lệnh ở đây gây nhầm lẫn hoặc thậm chí sai, đặc biệt là ý tưởng rằng bất biến.Vector trong Scala là bất cứ thứ gì giống như một ArrayList. Danh sách và Vector đều là cấu trúc dữ liệu bất biến, liên tục (nghĩa là "giá rẻ để có bản sao sửa đổi"). Không có lựa chọn mặc định hợp lý vì chúng có thể dành cho các cấu trúc dữ liệu có thể thay đổi, nhưng nó phụ thuộc vào thuật toán của bạn đang làm gì. Danh sách là một danh sách liên kết đơn, trong khi Vector là bộ ba số nguyên cơ bản 32, tức là nó là một loại cây tìm kiếm với các nút cấp 32. Sử dụng cấu trúc này, Vector có thể cung cấp hầu hết các hoạt động phổ biến một cách hợp lý nhanh chóng, tức là trong O (log_32 ( n)). Nó hoạt động để thêm vào, nối thêm, cập nhật, truy cập ngẫu nhiên, phân tách ở đầu / đuôi. Lặp lại theo thứ tự tuần tự là tuyến tính. Mặt khác, danh sách chỉ cung cấp phép lặp tuyến tính và trả trước thời gian liên tục, phân tách ở đầu / đuôi.

Điều này có thể trông giống như Vector là một sự thay thế tốt cho Danh sách trong hầu hết các trường hợp, nhưng trả trước, phân tách và lặp lại thường là các thao tác quan trọng trên các chuỗi trong một chương trình chức năng và các hằng số của các hoạt động này cao hơn nhiều so với vector do cấu trúc phức tạp hơn của nó. Tôi đã thực hiện một vài phép đo, vì vậy, việc lặp lại nhanh hơn khoảng hai lần cho danh sách, việc trả trước nhanh hơn khoảng 100 lần trong danh sách, sự phân rã ở đầu / đuôi nhanh hơn khoảng 10 lần trong danh sách và việc tạo ra từ một giao dịch nhanh hơn khoảng 2 lần cho các vectơ. (Điều này có thể là do Vector có thể phân bổ các mảng gồm 32 phần tử cùng một lúc khi bạn xây dựng nó bằng cách sử dụng trình xây dựng thay vì thêm trước hoặc nối thêm các phần tử một).

Vậy chúng ta nên sử dụng cấu trúc dữ liệu nào? Về cơ bản, có bốn trường hợp phổ biến:

  • Chúng ta chỉ cần chuyển đổi các chuỗi bằng các hoạt động như bản đồ, bộ lọc, gấp vv: về cơ bản không thành vấn đề, chúng ta nên lập trình thuật toán của mình một cách tổng quát và thậm chí có thể hưởng lợi từ việc chấp nhận các chuỗi song song. Đối với các hoạt động tuần tự Danh sách có thể nhanh hơn một chút. Nhưng bạn nên điểm chuẩn nó nếu bạn phải tối ưu hóa.
  • Chúng tôi cần rất nhiều quyền truy cập ngẫu nhiên và các bản cập nhật khác nhau, vì vậy chúng tôi nên sử dụng véc tơ, danh sách sẽ bị cấm rất chậm.
  • Chúng tôi hoạt động trên các danh sách theo cách chức năng cổ điển, xây dựng chúng bằng cách thêm trước và lặp lại bằng cách phân tách đệ quy: danh sách sử dụng, vectơ sẽ chậm hơn theo hệ số 10-100 trở lên.
  • Chúng tôi có một thuật toán quan trọng về hiệu năng về cơ bản là bắt buộc và có rất nhiều quyền truy cập ngẫu nhiên trong danh sách, giống như sắp xếp nhanh: sử dụng cấu trúc dữ liệu bắt buộc, ví dụ ArrayBuffer, cục bộ và sao chép dữ liệu của bạn từ đó sang nó.

24

Đối với các bộ sưu tập bất biến, nếu bạn muốn một chuỗi, quyết định chính của bạn là sử dụng một IndexedSeqhoặc một LinearSeq, điều này mang lại sự đảm bảo khác nhau cho hiệu suất. IndexedSeq cung cấp khả năng truy cập ngẫu nhiên nhanh các yếu tố và thao tác có độ dài nhanh. Một linearSeq chỉ cung cấp truy cập nhanh đến phần tử đầu tiên thông qua head, nhưng cũng có một tailhoạt động nhanh . (Lấy từ tài liệu Seq.)

Đối với một IndexedSeqbạn thường sẽ chọn một Vector. Ranges và WrappedStrings cũng được IndexedSeqs.

Đối với một LinearSeqbạn thường sẽ chọn một Listhoặc tương đương lười biếng của nó Stream. Các ví dụ khác là Queues và Stacks.

Vì vậy, theo thuật ngữ Java, ArrayListđược sử dụng tương tự như Scala VectorLinkedListtương tự như Scala List. Nhưng trong Scala tôi có xu hướng sử dụng Danh sách thường xuyên hơn Vector, vì Scala hỗ trợ tốt hơn nhiều cho các chức năng bao gồm truyền tải chuỗi, như ánh xạ, gấp, lặp, v.v. Bạn sẽ có xu hướng sử dụng các hàm này để thao tác danh sách như một toàn bộ, thay vì truy cập ngẫu nhiên các yếu tố cá nhân.


Nhưng nếu phép lặp của Vector nhanh hơn Danh sách và tôi cũng có thể lập bản đồ gấp, v.v., ngoài một số trường hợp chuyên biệt (về cơ bản là tất cả các thuật toán FP chuyên biệt cho Danh sách) thì có vẻ như Danh sách về cơ bản là di sản.
Duncan McGregor

@Duncan bạn đã nghe nói rằng vòng lặp của Vector nhanh hơn ở đâu? Để bắt đầu, bạn cần theo dõi và cập nhật chỉ mục hiện tại mà bạn không cần với danh sách được liên kết. Tôi sẽ không gọi các chức năng danh sách là "trường hợp chuyên biệt" - chúng là bánh mì và bơ của lập trình chức năng. Không sử dụng chúng sẽ giống như cố gắng lập trình Java mà không cần vòng lặp for hoặc vòng lặp.
Luigi Plinge

2
Tôi khá chắc chắn Vectorlà lặp đi lặp lại nhanh hơn, nhưng nhu cầu của một người nào đó để chuẩn nó để đảm bảo.
Daniel Spiewak

Tôi nghĩ các phần tử (?) VectorVật lý tồn tại cùng nhau trên RAM trong các nhóm 32, phù hợp hoàn toàn hơn với bộ đệm CPU ... vì vậy sẽ có ít bộ nhớ cache hơn
giàu có vào

2

Trong các tình huống liên quan đến nhiều truy cập ngẫu nhiên và đột biến ngẫu nhiên, một Vector(hoặc - như các tài liệu nói - a Seq) dường như là một sự thỏa hiệp tốt. Đây cũng là những gì đặc điểm hiệu suất đề xuất.

Ngoài ra, Vectorlớp dường như chơi độc đáo trong môi trường phân tán mà không cần sao chép dữ liệu nhiều vì không cần phải sao chép trên ghi cho đối tượng hoàn chỉnh. (Xem: http://akka.io/docs/akka/1.1.3/scala/stm.html#persistent-datastructures )


1
Quá nhiều thứ để tìm hiểu ... Vector là Seq mặc định nghĩa là gì? Nếu tôi viết Seq (1, 2, 3) tôi nhận được Danh sách [Int] chứ không phải Vector [Int].
Duncan McGregor

2
Nếu bạn có quyền truy cập ngẫu nhiên, sử dụng một IndexedSeq. Đó cũng Vectorlà một vấn đề.
Daniel C. Sobral

@DuncanMcGregor: Vector là mặc định IndexedSeqthực hiện Seq. Seq(1, 2, 3)là một LinearSeqtrong đó được thực hiện bằng cách sử dụng List.
pathikrit

0

Nếu bạn đang lập trình một cách bất biến và cần truy cập ngẫu nhiên, Seq là cách để đi (trừ khi bạn muốn có một Bộ, điều mà bạn thường làm). Mặt khác, Danh sách hoạt động tốt, ngoại trừ các hoạt động của nó không thể được song song.

Nếu bạn không cần cấu trúc dữ liệu bất biến, hãy gắn bó với ArrayBuffer vì đó là Scala tương đương với ArrayList.


Tôi đang gắn bó với vương quốc của những bộ sưu tập bất biến, bền bỉ. Quan điểm của tôi là, ngay cả khi tôi không cần truy cập ngẫu nhiên, Vector có thay thế Danh sách một cách hiệu quả không?
Duncan McGregor

2
Phụ thuộc một chút vào trường hợp sử dụng. Các vectơ cân bằng hơn. Lặp lại nhanh hơn danh sách và truy cập ngẫu nhiên nhanh hơn nhiều. Các bản cập nhật chậm hơn vì nó không chỉ là một bản cập nhật danh sách, trừ khi đó là bản cập nhật hàng loạt từ một bản gấp có thể được thực hiện với một người xây dựng. Điều đó nói rằng, tôi nghĩ Vector là sự lựa chọn mặc định tốt nhất vì nó rất linh hoạt.
Joshua Hartman

Điều mà tôi nghĩ là đi vào trung tâm câu hỏi của tôi - Các vectơ tốt đến mức chúng ta cũng có thể sử dụng chúng trong đó các ví dụ thường hiển thị Danh sách.
Duncan McGregor
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.