2009-12-01 29 views
5

Tôi hiểu rằng:ngắn mạch loại

head (map (2**) [1..999999]) 

sẽ chỉ thực sự đánh giá 2 ** 1, và không ai trong số các phần còn lại, nhưng cuốn sách tôi đang đọc nói rằng:

head (sort somelist) 

chỉ Will cần phải tìm mục nhỏ nhất trong danh sách, bởi vì đó là tất cả những gì được sử dụng. Cái này hoạt động ra sao? Theo như tôi có thể nói, điều này sẽ là không thể với các thuật toán sắp xếp mà tôi biết (như phân loại bong bóng).

Cách duy nhất tôi có thể nghĩ rằng điều này có hiệu quả là nếu thuật toán sắp xếp đi qua toàn bộ danh sách tìm kiếm mục nhỏ nhất và sau đó kiểm tra lại trên danh sách mà không có mục đó. Đối với tôi, điều này nghe có vẻ rất chậm.

Đây có phải là cách chức năng sắp xếp hoạt động hoặc có thuật toán sắp xếp khác mà tôi không biết, điều đó có thể cho phép đoản mạch ngắn như không?

Trả lời

10

này:

Sẽ chỉ cần phải tìm ra mục nhỏ nhất trong danh sách, bởi vì đó là tất cả những gì được sử dụng.

... thực sự nên nói rằng chức năng chỉ cần làm số tiền tối thiểu của công việc rằng các thuật toán sắp xếp đòi hỏi để tìm phần tử nhỏ nhất.

Ví dụ, nếu chúng ta đang sử dụng quicksort như thuật toán cơ bản sắp xếp của chúng tôi, sau đó head . quicksort tương đương với tối ưu (!) Thuật toán lựa chọn được gọi là 'quickselect', đó là trường hợp xấu nhất tuyến tính. Hơn nữa, chúng tôi có thể triển khai k -quickschọn chỉ bằng take k . quicksort.

Wikipedia ghi nhận trong bài viết của mình trên các thuật toán lựa chọn đó (tôi nhấn mạnh):

Bởi vì hỗ trợ ngôn ngữ để phân loại là phổ biến hơn, cách tiếp cận đơn giản phân loại tiếp theo chỉ mục được ưa thích trong nhiều môi trường mặc dù nhược điểm của nó trong tốc độ. Thật vậy, đối với ngôn ngữ lười, cách tiếp cận đơn giản này thậm chí có thể giúp bạn có được độ phức tạp tốt nhất có thể cho k nhỏ nhất/lớn nhất được sắp xếp (với tối đa/tối thiểu là trường hợp đặc biệt) nếu sắp xếp của bạn đủ lười.

Sắp xếp nhanh hoạt động tốt trong tình huống này, trong khi sắp xếp mặc định trong Haskell (merge sort) không soạn khá là tốt, vì nó làm việc chặt chẽ hơn cần thiết để trở lại mỗi phần tử của danh sách được sắp xếp. Như this post on the Haskell mailing list ghi chú:

quicksort lười biếng có thể sản xuất các lô hàng của k yếu tố nhỏ nhất đầu tiên trong

O (n + k log k) tổng thời gian [1]

khi lười biếng mergesort cần

O (n + k log n) tổng thời gian [2]

để biết thêm bạn có thể muốn đọc this blog post.

2

Thuật toán bạn vừa mô tả có tên cụ thể: "sắp xếp lựa chọn". Đó là O (n) vì vậy nó không phải là điều nhanh nhất bạn có thể làm. Tuy nhiên, nếu bạn muốn các phần tử "k" đầu tiên trong mảng được sắp xếp, độ phức tạp sẽ là O (kn) rất tốt nếu "k" đủ nhỏ (như ví dụ của bạn).

Lưu ý rằng bạn đang sử dụng hàm thuần túy bằng ngôn ngữ chức năng. Trình biên dịch có khả năng có thể tạo mã được tối ưu hóa cho sort trong cả hai trường hợp bằng cách xem xét các hàm được tạo. Có thể dễ dàng phỏng đoán rằng bạn muốn phần tử tối thiểu khi bạn soạn headsort.

+0

Phần cuối cùng này không chính xác; trình biên dịch không thể suy ra ý định! – porges

+0

Porges: Trong khi trình biên dịch có thể được hardwired để phân tích ý định trong trường hợp cụ thể, bạn không ** cần ** suy luận * ý định *. Bạn cần sử dụng một định lý cơ học để chứng minh rằng phiên bản được tối ưu hóa của mã là toán học tương đương với phiên bản gốc. Ngôn ngữ chức năng làm cho định lý này chứng minh dễ dàng hơn bằng cách không cho phép tác dụng phụ. –

+0

Có thể, nhưng tôi không biết của bất kỳ trình biên dịch Haskell hơn bao gồm provers định lý tự động như là một phần của vượt qua tối ưu hóa của họ. Lý do thành phần các chức năng này hoạt động chỉ vì tính chất mặc định lười của Haskell. – porges

6

Nếu bạn tạo một hàm so sánh rằng dấu vết đối số của nó, như thế này trong dòng lệnh GHCi của:

> :module + Data.List Debug.Trace 
> let myCompare x y = trace ("\tCmp " ++ show x ++ " " ++ show y) $ compare x y 

sau đó bạn sẽ nhìn thấy hành vi của mình:

> sortBy myCompare "foobar" 

"  Cmp 'f' 'o' 
     Cmp 'o' 'b' 
     Cmp 'f' 'b' 
     Cmp 'a' 'r' 
     Cmp 'b' 'a' 
a  Cmp 'b' 'r' 
b  Cmp 'f' 'o' 
     Cmp 'f' 'r' 
f  Cmp 'o' 'o' 
     Cmp 'o' 'r' 
o  Cmp 'o' 'r' 
or" 

Haskell đang đánh giá chuỗi uể oải , một nhân vật tại một thời điểm. Cột bên tay trái đang được in dưới dạng mỗi ký tự được tìm thấy, với cột bên tay phải ghi các so sánh cần thiết, như được in bằng "dấu vết".

Lưu ý rằng nếu bạn biên dịch điều này, đặc biệt là khi tối ưu hóa, bạn có thể nhận được kết quả khác. Trình tối ưu hóa chạy một máy phân tích nghiêm ngặt có thể sẽ nhận thấy rằng toàn bộ chuỗi được in, vì vậy sẽ hiệu quả hơn khi đánh giá nó một cách háo hức.

Sau đó thử

> head $ sortBy myCompare "foobar" 

     Cmp 'f' 'o' 
     Cmp 'o' 'b' 
     Cmp 'f' 'b' 
     Cmp 'a' 'r' 
     Cmp 'b' 'a' 
'a' 

Nếu bạn muốn hiểu cách làm việc này, tìm kiếm các mã nguồn cho các chức năng sắp xếp và đánh giá 'sắp xếp 'foobar'' bằng tay trên giấy.

qsort [] = [] 
qsort (x:xs) = qsort less ++ [x] ++ qsort greater 
    where (less, greater) = partition (< x) xs 

Vì vậy

qsort ('f':"oobar") 
= qsort ('b':"a") ++ "f" ++ qsort ('o':"or") 
= ("a" ++ "b") ++ "f" ++ qsort ('o':"or") 

Và bây giờ chúng ta đã làm đủ để thấy rằng 'a' là mục đầu tiên trong kết quả mà không cần phải đánh giá các cuộc gọi khác đến "qsort". Tôi đã bỏ qua sự so sánh thực tế bởi vì nó ẩn bên trong cuộc gọi đến "phân vùng". Trên thực tế "phân vùng" cũng là lười biếng, do đó, trong thực tế, các đối số khác "qsort" đã không được đánh giá như xa như tôi đã hiển thị nó.

Các vấn đề liên quan