Tại sao danh sách khuyết điểm liên quan đến lập trình chức năng?


22

Tôi đã nhận thấy rằng hầu hết các ngôn ngữ chức năng sử dụng danh sách liên kết đơn (danh sách "khuyết điểm") làm loại danh sách cơ bản nhất của chúng. Ví dụ bao gồm Lisp chung, Haskell và F #. Điều này khác với các ngôn ngữ chính, trong đó các loại danh sách gốc là mảng.

Tại sao vậy?

Đối với Common Lisp (được gõ động) tôi có ý tưởng rằng khuyết điểm đủ chung để cũng là cơ sở của danh sách, cây, v.v ... Đây có thể là một lý do nhỏ.

Tuy nhiên, đối với các ngôn ngữ được nhập tĩnh, tôi không thể tìm thấy lý do chính đáng, thậm chí tôi có thể tìm thấy các đối số phản biện:

  • Kiểu chức năng khuyến khích tính không thay đổi, do đó, việc dễ dàng chèn danh sách được liên kết ít có lợi thế hơn,
  • Phong cách chức năng khuyến khích sự bất biến, cũng chia sẻ dữ liệu; một mảng dễ chia sẻ "một phần" hơn danh sách được liên kết,
  • Bạn cũng có thể thực hiện khớp mẫu trên một mảng thông thường, và thậm chí tốt hơn (ví dụ bạn có thể dễ dàng gấp từ phải sang trái),
  • Trên hết, bạn có thể truy cập ngẫu nhiên miễn phí,
  • Và (một lợi thế thực tế) nếu ngôn ngữ được nhập tĩnh, bạn có thể sử dụng bố cục bộ nhớ thông thường và tăng tốc độ từ bộ đệm.

Vậy tại sao thích danh sách liên kết?


4
Xuất phát từ các ý kiến ​​về câu trả lời của @ sepp2k, tôi nghĩ an array is easier to share "partially" than a linked listcần làm rõ ý của bạn. Do tính chất đệ quy của chúng, điều ngược lại là đúng như tôi hiểu - bạn có thể chia sẻ một phần danh sách được liên kết dễ dàng hơn bằng cách chuyển qua bất kỳ nút nào trong đó, trong khi một mảng sẽ cần dành thời gian để tạo một bản sao mới. Hoặc về mặt chia sẻ dữ liệu, hai danh sách được liên kết có thể trỏ đến cùng một hậu tố, điều này chỉ đơn giản là không thể với các mảng.
Izkata

Nếu một mảng tự định nghĩa là một phần bù, độ dài, bộ đệm, thì bạn có thể chia sẻ một mảng bằng cách tạo một mảng mới với offset + 1, length-1, bộ đệm. Hoặc có một loại mảng đặc biệt là subarray.
Dobes Vandermeer

@Izkata Khi nói về mảng, chúng ta hiếm khi chỉ có nghĩa là một bộ đệm như con trỏ bắt đầu bộ nhớ liền kề trong C. Chúng ta thường có nghĩa là một loại cấu trúc lưu trữ độ dài và con trỏ đến đầu của bộ đệm được bọc. Trong một hệ thống như vậy, một thao tác cắt có thể trả về một phân đoạn con, mà con trỏ bộ đệm của nó trỏ giữa chừng vào bộ đệm (ở phần tử đầu tiên của mảng phụ) và số đếm của nó sao cho start + Count cung cấp cho bạn phần tử cuối cùng. Các hoạt động cắt lát như vậy là O (1) trong thời gian và không gian
Alexander - Tái lập Monica

Câu trả lời:


22

Yếu tố quan trọng nhất là bạn có thể thêm vào danh sách liên kết đơn bất biến trong thời gian O (1), cho phép bạn xây dựng đệ quy danh sách các yếu tố n trong thời gian O (n) như sau:

// Build a list containing the numbers 1 to n:
foo(0) = []
foo(n) = cons(n, foo(n-1))

Nếu bạn đã làm điều này bằng cách sử dụng các mảng không thay đổi, thời gian chạy sẽ là bậc hai bởi vì mỗi consthao tác sẽ cần sao chép toàn bộ mảng, dẫn đến thời gian chạy bậc hai.

Phong cách chức năng khuyến khích sự bất biến, cũng chia sẻ dữ liệu; một mảng dễ chia sẻ "một phần" hơn danh sách được liên kết

Tôi giả sử bằng cách chia sẻ "một phần", bạn có nghĩa là bạn có thể lấy một phân đoạn từ một mảng trong thời gian O (1), trong khi với các danh sách được liên kết, bạn chỉ có thể lấy đuôi trong thời gian O (1) và mọi thứ khác cần O (n). Điều đó đúng.

Tuy nhiên, lấy đuôi là đủ trong nhiều trường hợp. Và bạn phải tính đến việc có thể tạo ra các phân đoạn giá rẻ không giúp ích gì cho bạn nếu bạn không có cách nào tạo ra các mảng rẻ tiền. Và (không có tối ưu hóa trình biên dịch thông minh), không có cách nào để xây dựng một mảng giá rẻ từng bước một.


Điều đó không đúng chút nào. Bạn có thể nối vào các mảng trong khấu hao O (1).
DeadMG

10
@DeadMG Có, nhưng không phải là mảng bất biến .
sepp2k

"chia sẻ một phần" - Tôi nghĩ rằng cả hai danh sách khuyết điểm đều có thể trỏ đến cùng một danh sách hậu tố (dunno tại sao bạn muốn điều này) và bạn có thể chuyển điểm giữa thay vì bắt đầu danh sách sang chức năng khác mà không cần phải sao chép nó (tôi đã làm điều này nhiều lần)
Izkata

@Izkata OP đã nói về mảng chia sẻ một phần, chứ không phải danh sách. Ngoài ra, tôi chưa bao giờ nghe những gì bạn mô tả được gọi là chia sẻ một phần . Đó chỉ là chia sẻ.
sepp2k

1
@Izkata OP sử dụng thuật ngữ "mảng" chính xác ba lần. Một lần để nói rằng các ngôn ngữ FP sử dụng danh sách được liên kết trong đó các ngôn ngữ khác sử dụng mảng. Một lần để nói rằng các mảng tốt hơn trong việc chia sẻ một phần so với các danh sách được liên kết và một lần để nói rằng các mảng cũng có thể được khớp mẫu (như các danh sách được liên kết). Trong mọi trường hợp, anh ấy đối chiếu các mảng và danh sách được liên kết (để xác định rằng các mảng sẽ hữu ích hơn khi là cấu trúc dữ liệu chính so với danh sách được liên kết, dẫn đến câu hỏi của anh ấy tại sao danh sách được liên kết được ưa thích trong FP), vì vậy tôi không thấy cách anh ấy có thể được sử dụng các thuật ngữ thay thế cho nhau.
sepp2k

4

Tôi nghĩ rằng nó đi xuống danh sách được thực hiện dễ dàng trong mã chức năng.

Kế hoạch:

(define (cons x y)(lambda (m) (m x y)))

Haskell:

data  [a]  =  [] | a : [a]

Mảng khó hơn và gần như không đẹp để thực hiện. Nếu bạn muốn chúng cực kỳ nhanh thì chúng sẽ phải được viết ở cấp độ thấp.

Ngoài ra, đệ quy hoạt động tốt hơn nhiều trong danh sách so với mảng. Xem xét số lần bạn đã tiêu thụ đệ quy / tạo danh sách so với chỉ mục một mảng.


Tôi không nói chính xác khi gọi phiên bản chương trình của bạn là triển khai danh sách được liên kết. Bạn sẽ không thể sử dụng nó để lưu trữ bất cứ thứ gì ngoài chức năng. Ngoài ra, các mảng khó hơn (thực tế là không thể) để thực hiện bằng bất kỳ ngôn ngữ nào không có hỗ trợ tích hợp cho chúng (hoặc các khối bộ nhớ), trong khi các danh sách được liên kết chỉ yêu cầu một cái gì đó như cấu trúc, lớp, bản ghi hoặc các loại dữ liệu đại số để thực hiện. Điều đó không đặc trưng cho các ngôn ngữ lập trình chức năng.
sepp2k

@ sepp2k Ý bạn là gì "lưu trữ bất cứ thứ gì ngoài chức năng" ?
Pubby

1
Ý tôi là các danh sách được xác định theo cách đó không thể lưu trữ bất cứ thứ gì không phải là hàm. Điều đó không thực sự đúng mặc dù. Dunno, tại sao tôi lại nghĩ thế. Xin lỗi vì điều đó.
sepp2k

2

Một danh sách liên kết đơn là cấu trúc dữ liệu liên tục đơn giản nhất .

Các cấu trúc dữ liệu liên tục là rất cần thiết để thực hiện, lập trình chức năng thuần túy.


1
điều này dường như chỉ lặp lại điểm được thực hiện và giải thích trong câu trả lời hàng đầu đã được đăng hơn 4 năm trước
gnat

3
@gnat: Câu trả lời hàng đầu không đề cập đến cấu trúc dữ liệu liên tục hoặc danh sách liên kết đơn là cấu trúc dữ liệu liên tục đơn giản nhất hoặc chúng rất cần thiết cho lập trình chức năng thuần túy. Tôi không thể tìm thấy sự trùng lặp với câu trả lời hàng đầu.
Michael Shaw

2

Bạn chỉ có thể sử dụng các nút Cons một cách dễ dàng nếu bạn có ngôn ngữ được thu gom rác.

Nhược điểm phù hợp rất nhiều với phong cách lập trình chức năng của các cuộc gọi đệ quy và các giá trị bất biến. Vì vậy, nó phù hợp với mô hình lập trình tinh thần.

Và đừng quên lý do lịch sử. Tại sao họ vẫn được gọi là Cons Nodes và tệ hơn là vẫn sử dụng xe hơi và cdr làm phụ kiện? Mọi người học nó từ sách giáo khoa và các khóa học và sau đó sử dụng nó.

Bạn đã đúng, trong các mảng trong thế giới thực dễ sử dụng hơn nhiều, chỉ tiêu thụ một nửa dung lượng bộ nhớ và có hiệu suất cao hơn vì bỏ lỡ mức bộ nhớ cache. Không có lý do để sử dụng chúng với các ngôn ngữ bắt buộc.


1

Danh sách liên kết rất quan trọng vì lý do sau:

Khi bạn lấy một số như 3 và chuyển đổi nó thành chuỗi kế tiếp như succ(succ(succ(zero)))sau đó sử dụng thay thế cho nó bằng {succ=List node with some memory space}, và {zero = end of list}, bạn sẽ kết thúc với một danh sách được liên kết (có độ dài 3).

Phần quan trọng thực tế là số, thay thế, và không gian bộ nhớ và số không.

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.