2012-07-12 28 views

Trả lời

6

Nó thực sự không quá khó (và một bài tập thú vị) để cuộn hàng đợi FIFO của riêng bạn trong Haskell. Tôi có thể đánh giá cao mong muốn của bạn để sử dụng một typeclass tiêu chuẩn cho việc này, và đó là gần như chắc chắn những gì bạn nên làm. Nhưng tôi mới biết về điều này tuần trước, và tôi quá vui mừng không viết về nó. Đây là một lớp xếp hàng đơn giản, cho phép bạn kiểm tra xem hàng đợi có trống hay không, để lấy phần tử đầu tiên từ phần đầu của hàng đợi (và trả về phần còn lại của hàng đợi) và chèn một phần tử mới vào hàng đợi .

class Queue q where 
    empty :: q a -> Bool 
    get :: q a -> (a, q a) 
    ins :: a -> q a -> q a 

Cách đơn giản nhất để làm cho một hàng đợi FIFO đang sử dụng một danh sách:

instance Queue [] where 
    empty = null 

    get []  = error "Can't retrieve elements from an empty list" 
    get (x:xs) = (x, xs) 

    ins x xs = xs ++ [x] 

Tuy nhiên, đây là khủng khiếp không hiệu quả. Nếu hàng đợi hiện có các yếu tố n, thì việc chèn phần tử mới sẽ mất thời gian O (n). Nếu bạn muốn chèn các phần tử m vào hàng đợi trống, sẽ mất thời gian O (m). Chúng ta có thể tạo một hàng đợi chèn và lấy các phần tử trong thời gian O (1) (hay ít nhất là thời gian phân bổ O (1))?

Bí quyết là để lưu trữ các mặt trước và sau của hàng đợi trong danh sách riêng biệt, với sự trở lại của hàng đợi được lưu trữ trong ngược lại:

data Fifo a = F [a] [a] 

instance Queue Fifo where 

Hàng đợi rỗng nếu cả hai phía trước và phía sau là sản phẩm nào:

empty (F xs ys) = null xs && null ys 

Để chèn phần tử mới vào danh sách, chúng tôi chỉ đưa nó vào hàng đợi sau, mất khoảng thời gian O (1).

ins y (F xs ys) = F xs (y:ys) 

Bắt một yếu tố từ phía trước của hàng đợi là dễ dàng khi có những yếu tố chờ đợi ở đó (và chúng ta ném ra một lỗi nếu hàng đợi rỗng)

get (F []  []) = error "Can't retrieve elements from an empty queue" 
    get (F (x:xs) ys) = (x, F xs ys) 

Cuối cùng, nếu không có yếu tố chờ đợi ở phía trước của hàng đợi, sau đó chúng tôi đảo ngược mặt sau của hàng đợi và đặt nó ở phía trước. Mặc dù điều này mất O (n) thời gian, chúng tôi chỉ phải làm điều đó một lần cho mỗi yếu tố, vì vậy get của chúng tôi hoạt động trung bình ra O (1) thời gian:

get (F [] ys) = get (F (reverse ys) []) 

Ở đó bạn có nó - khấu hao O (1) Hàng đợi FIFO trong một ngôn ngữ chức năng.


Edit: Efie hỏi về O khấu hao (1) thực hiện trong các ý kiến. Đối số cho thời gian cố định được phân bổ là khá đơn giản.

Xem xét chuỗi các yêu cầu n chèn vào hàng đợi trống, theo sau là n truy xuất. Số lần chèn mất thời gian n. Trong lần truy xuất đầu tiên, mặt trước của hàng đợi trống, vì vậy chúng tôi phải đảo ngược mặt sau của hàng đợi, cũng mất thời gian n, cộng với 1 để truy lục phần tử. cuối cùng, n tiếp theo - 1 khả năng tìm lại mất thời gian 1 mỗi, vì vậy tổng thời gian là

n + n + 1 + n-1 = 3 n

Chúng tôi đã làm Tổng cộng 2 n cuộc gọi, do đó, thời gian khấu hao là 3 n/2 n = 3/2, là O (1). Các đối số tương tự hoạt động không có vấn đề làm thế nào các cuộc gọi đến insget được xen kẽ - trong hai cuộc gọi, mỗi yếu tố được cons'ed một lần, di chuyển một lần và de-cons'ed một lần.

+0

Cảm ơn bạn, tôi thích bài đăng của bạn. Để tránh bị lỗi, bạn phải kiểm tra xem danh sách có rỗng mỗi lần trước khi bạn 'nhận' một phần tử phải không? Điều này có tốt hơn là thay đổi kiểu nhận được :: q a -> (Có thể a, q a)? – efie

+3

Cách 'an toàn' sẽ thay đổi loại thành 'get :: qa -> Có thể (a, qa)' (với 'Có thể' gói tuple, vì nó không có ý nghĩa để trả lại phần còn lại của nếu chúng tôi không truy xuất được phần tử đầu tiên). Tôi bỏ qua điều này để giữ cho việc triển khai thực hiện đơn giản, vì vậy với mã của tôi, bạn phải kiểm tra xem danh sách có là không trước khi bạn có thể sử dụng nó một cách an toàn không. Lưu ý rằng đây không phải là công việc nữa, vì với trình bao bọc 'Maybe', chúng ta phải kiểm tra' Nothing' * sau * cuộc gọi thay thế. Tuy nhiên, với trình bao bọc, chúng tôi bị hệ thống kiểu kiểm tra buộc phải kiểm tra - không có nó, bạn có thể viết mã không an toàn. –

+0

Tôi muốn mở rộng cách trường hợp trung bình nhận được phần tử chỉ là O (1). Nếu bạn có n phần tử trong hàng đợi, có (n + 1)! cách phân bổ chúng vào hai danh sách: n! cách sắp xếp các phần tử n, (n + 1) cách chia từng sắp xếp thành hai danh sách. Có n! cách sắp xếp các phần tử trong ys nếu xs rỗng. Số lượng: hoạt động hiện nay bao gồm như sau: prob ("xsIsEmpty") * chi phí ("xsIsEmpty") + prob ("xsNotEmpty") * chi phí ("xsNotEmpty) = n!/(N + 1)! * N + ... * 0 = n/n + 1 = O (1). – efie

2

Phụ thuộc vào những hoạt động bạn muốn có. Các gói queuelikedequeue có các lớp loại cho hàng đợi.

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