Danh sách nhạc rock
Cho đến nay, cấu trúc dữ liệu thân thiện nhất cho dữ liệu tuần tự trong Haskell là Danh sách
data [a] = a:[a] | []
Danh sách cung cấp cho bạn ϴ (1) khuyết điểm và khớp mẫu. Các thư viện chuẩn, và cho rằng quan trọng là khúc dạo đầu, có đầy đủ các chức năng danh sách hữu ích mà nên xả rác mã của bạn ( foldr
, map
, filter
). Danh sách là persistant , aka hoàn toàn chức năng, mà là rất tốt đẹp. Danh sách Haskell không thực sự là "danh sách" vì chúng có tính cưỡng chế (các ngôn ngữ khác gọi các luồng này) nên những thứ như
ones :: [Integer]
ones = 1:ones
twos = map (+1) ones
tenTwos = take 10 twos
làm việc tuyệt vời Cấu trúc dữ liệu vô hạn đá.
Danh sách trong Haskell cung cấp một giao diện giống như các trình lặp trong các ngôn ngữ bắt buộc (vì sự lười biếng). Vì vậy, nó có ý nghĩa rằng chúng được sử dụng rộng rãi.
Mặt khác
Vấn đề đầu tiên với các danh sách là để lập chỉ mục vào chúng (!!)
mất (k), điều này gây khó chịu. Ngoài ra, các phụ lục có thể chậm ++
, nhưng mô hình đánh giá lười biếng của Haskell có nghĩa là những điều này có thể được coi là khấu hao hoàn toàn, nếu chúng xảy ra.
Vấn đề thứ hai với danh sách là họ có địa phương dữ liệu kém. Bộ xử lý thực sự phải chịu các hằng số cao khi các đối tượng trong bộ nhớ không được đặt cạnh nhau. Vì vậy, trong C ++ std::vector
có "snoc" nhanh hơn (đặt các đối tượng ở cuối) so với bất kỳ cấu trúc dữ liệu danh sách liên kết thuần túy nào tôi biết, mặc dù đây không phải là cấu trúc dữ liệu bền vững nên ít thân thiện hơn danh sách của Haskell.
Vấn đề thứ ba với danh sách là chúng có hiệu quả không gian kém. Bunches của con trỏ thêm đẩy lưu trữ của bạn (bởi một yếu tố không đổi).
Trình tự là chức năng
Data.Sequence
là nội bộ dựa trên cây ngón tay (tôi biết, bạn không muốn biết điều này) có nghĩa là chúng có một số đặc tính tốt
- Hoàn toàn chức năng.
Data.Sequence
là một cấu trúc dữ liệu hoàn toàn bền bỉ.
- Darn nhanh chóng truy cập vào đầu và cuối của cây. (1) (khấu hao) để lấy phần tử đầu tiên hoặc cuối cùng hoặc nối thêm cây. Tại các danh sách điều là nhanh nhất,
Data.Sequence
nhiều nhất là chậm liên tục.
- (Log n) truy cập vào giữa chuỗi. Điều này bao gồm chèn các giá trị để tạo chuỗi mới
- API chất lượng cao
Mặt khác, Data.Sequence
không làm được gì nhiều cho vấn đề cục bộ dữ liệu và chỉ hoạt động cho các bộ sưu tập hữu hạn (nó ít lười hơn danh sách)
Mảng không dành cho người yếu tim
Mảng là một trong những cấu trúc dữ liệu quan trọng nhất trong CS, nhưng chúng không phù hợp lắm với thế giới chức năng thuần túy lười biếng. Mảng cung cấp quyền truy cập (1) vào giữa bộ sưu tập và các yếu tố địa phương / hằng số dữ liệu đặc biệt tốt. Nhưng, vì chúng không phù hợp lắm với Haskell, nên chúng rất khó sử dụng. Thực tế có vô số kiểu mảng khác nhau trong thư viện chuẩn hiện tại. Chúng bao gồm các mảng hoàn toàn bền bỉ, mảng có thể thay đổi cho đơn vị IO, mảng có thể thay đổi cho đơn vị ST và các phiên bản không được đóng hộp ở trên. Để biết thêm hãy xem wiki haskell
Vector là một mảng "tốt hơn"
Các Data.Vector
gói cung cấp tất cả sự tốt lành mảng, trong một mức độ và sạch API cao hơn. Trừ khi bạn thực sự biết những gì bạn đang làm, bạn nên sử dụng những thứ này nếu bạn cần mảng như hiệu suất. Tất nhiên, một số cảnh báo vẫn được áp dụng - mảng có thể thay đổi như cấu trúc dữ liệu chỉ không chơi tốt trong các ngôn ngữ lười biếng thuần túy. Tuy nhiên, đôi khi bạn muốn hiệu suất O (1) đó và Data.Vector
cung cấp cho bạn trong một gói có thể sử dụng được.
Bạn có những lựa chọn khác
Nếu bạn chỉ muốn danh sách có khả năng chèn hiệu quả vào cuối, bạn có thể sử dụng danh sách khác biệt . Ví dụ tốt nhất về danh sách làm tăng hiệu suất có xu hướng đến từ [Char]
đó khúc dạo đầu có bí danh là String
. Char
danh sách là thuận tiện, nhưng có xu hướng chạy theo thứ tự chậm hơn 20 lần so với chuỗi C, vì vậy hãy sử dụng Data.Text
hoặc rất nhanh Data.ByteString
. Tôi chắc chắn có những thư viện theo định hướng trình tự khác mà tôi không nghĩ đến ngay bây giờ.
Phần kết luận
90 +% thời gian tôi cần một bộ sưu tập tuần tự trong danh sách Haskell là cấu trúc dữ liệu phù hợp. Danh sách giống như các trình vòng lặp, các hàm tiêu thụ danh sách có thể dễ dàng được sử dụng với bất kỳ cấu trúc dữ liệu nào khác bằng cách sử dụng các toList
hàm mà chúng đi kèm. Trong một thế giới tốt hơn, khúc dạo đầu sẽ hoàn toàn tham số về loại thùng chứa mà nó sử dụng, nhưng hiện đang []
nằm trong thư viện chuẩn. Vì vậy, sử dụng danh sách (hầu hết) mọi nơi chắc chắn là ổn.
Bạn có thể nhận được các phiên bản tham số đầy đủ của hầu hết các hàm danh sách (và rất cao để sử dụng chúng)
Prelude.map ---> Prelude.fmap (works for every Functor)
Prelude.foldr/foldl/etc ---> Data.Foldable.foldr/foldl/etc
Prelude.sequence ---> Data.Traversable.sequence
etc
Trong thực tế, Data.Traversable
định nghĩa một API phổ biến hơn hoặc ít hơn trên bất kỳ thứ gì "liệt kê như".
Tuy nhiên, mặc dù bạn có thể giỏi và chỉ viết mã tham số đầy đủ, hầu hết chúng ta đều không và sử dụng danh sách ở mọi nơi. Nếu bạn đang học, tôi thực sự khuyên bạn nên làm quá.
EDIT: Căn cứ vào ý kiến tôi nhận ra tôi không bao giờ giải thích khi sử dụng Data.Vector
vs Data.Sequence
. Mảng và vectơ cung cấp các hoạt động lập chỉ mục và cắt cực nhanh, nhưng về cơ bản là cấu trúc dữ liệu (bắt buộc). Các cấu trúc dữ liệu chức năng thuần túy thích Data.Sequence
và []
cho phép tạo ra các giá trị mới từ các giá trị cũ một cách hiệu quả như thể bạn đã sửa đổi các giá trị cũ.
newList oldList = 7 : drop 5 oldList
không sửa đổi danh sách cũ và nó không phải sao chép nó. Vì vậy, ngay cả khi oldList
cực kỳ dài, "sửa đổi" này sẽ rất nhanh. Tương tự
newSequence newValue oldSequence = Sequence.update 3000 newValue oldSequence
sẽ tạo ra một chuỗi mới với một newValue
for ở vị trí của phần tử 3000 của nó. Một lần nữa, nó không phá hủy chuỗi cũ, nó chỉ tạo ra một chuỗi mới. Nhưng, nó thực hiện điều này rất hiệu quả, lấy O (log (min (k, kn)) trong đó n là độ dài của chuỗi và k là chỉ số bạn sửa đổi.
Bạn không thể dễ dàng làm điều này với Vectors
và Arrays
. Chúng có thể được sửa đổi nhưng đó là sửa đổi bắt buộc thực sự, và vì vậy không thể được thực hiện bằng mã Haskell thông thường. Điều đó có nghĩa là các hoạt động trong Vector
gói thực hiện sửa đổi như thế nào snoc
và cons
phải sao chép toàn bộ vectơ để mất O(n)
thời gian. Ngoại lệ duy nhất này là bạn có thể sử dụng phiên bản có thể thay đổi ( Vector.Mutable
) bên trong ST
đơn nguyên (hoặc IO
) và thực hiện tất cả các sửa đổi của mình giống như bạn làm trong một ngôn ngữ bắt buộc. Khi bạn đã hoàn tất, bạn "đóng băng" vector của mình để chuyển sang cấu trúc bất biến mà bạn muốn sử dụng với mã thuần.
Cảm giác của tôi là bạn nên mặc định sử dụng Data.Sequence
nếu một danh sách không phù hợp. Data.Vector
Chỉ sử dụng nếu kiểu sử dụng của bạn không liên quan đến việc thực hiện nhiều sửa đổi hoặc nếu bạn cần hiệu suất cực cao trong các đơn vị ST / IO.
Nếu tất cả các cuộc nói chuyện này của các ST
đơn vị đang khiến bạn bối rối: tất cả lý do nhiều hơn để gắn bó với tinh khiết nhanh và đẹp Data.Sequence
.