2013-04-09 36 views
5

Để lưu quyền người dùng tài khoản bên ngoài (ví dụ: trong DB), tôi muốn trình bày một danh sách các thành phần của một liệt kê có một trường hợp Enum có nguồn gốc là Int.
Mỗi bit của số được xem là cờ (hoặc Boolean) biểu thị nếu phần tử thứ i có trong danh sách.
Đặt nó theo các từ khác nhau - mỗi lũy thừa của 2 đại diện cho một phần tử và tổng các quyền hạn đó là danh sách các phần tử độc đáo.Đại diện cho danh sách Enums bitwise dưới dạng Int

Ví dụ:

data Permissions = IsAllowedToLogin -- 1 
       | IsModerator  -- 2 
       | IsAdmin   -- 4 
       deriving (Bounded, Enum, Eq, Show) 

enumsToInt [IsAllowedToLogin, IsAdmin] == 1 + 4 == 5 

intToEnums 3 == intToEnums (1 + 2) == [IsAllowedToLogin, IsModerator] 

Chức năng chuyển đổi một danh sách như vậy vào một Int là khá dễ dàng để viết:

enumsToInt :: (Enum a, Eq a) => [a] -> Int 
enumsToInt = foldr (\p acc -> acc + 2^fromEnum p) 0 . nub 

Lưu ý rằng câu trả lời chấp nhận chứa nhiều hiệu quả hơn thực hiện .

Điều thực sự làm phiền tôi là chức năng đảo chiều. Tôi có thể tưởng tượng rằng nó phải có loại này:

intToEnums :: (Bounded a, Enum a) => Int -> [a] 
intToEnums = undefined    -- What I'm asking about 

Tôi nên tiếp cận vấn đề này như thế nào?

+2

Đối với người mới bắt đầu, có bạn nhìn vào [ 'cái Data.Bits' mô-đun] (http: //hackage.haskell .org/packages/archive/base/mới nhất/doc/html/Data-Bits.html)? –

+0

@C. A. McCann Không, tôi không có! Bạn có nghĩ rằng nó sẽ hữu ích? – Jakub

+0

Tôi không nghĩ rằng nó có bất cứ điều gì làm chính xác những gì bạn muốn (mặc dù nó có vẻ giống như một cái gì đó nên có) nhưng nó có một loạt các hoạt động bitwise mà sẽ làm cho mọi thứ dễ dàng hơn cho bạn. –

Trả lời

10

Sau đây là giải pháp hoàn chỉnh. Nó sẽ thực hiện tốt hơn vì nó thực hiện dựa trên bitwise chứ không phải là phép tính số học, mà là một cách tiếp cận hiệu quả hơn nhiều. Giải pháp cũng làm hết sức mình để khái quát mọi thứ.

{-# LANGUAGE DefaultSignatures #-} 
import Data.Bits 
import Control.Monad 

data Permission = IsAllowedToLogin -- 1 
       | IsModerator  -- 2 
       | IsAdmin   -- 4 
       deriving (Bounded, Enum, Eq, Show) 

class ToBitMask a where 
    toBitMask :: a -> Int 
    -- | Using a DefaultSignatures extension to declare a default signature with 
    -- an `Enum` constraint without affecting the constraints of the class itself. 
    default toBitMask :: Enum a => a -> Int 
    toBitMask = shiftL 1 . fromEnum 

instance ToBitMask Permission 

instance (ToBitMask a) => ToBitMask [a] where 
    toBitMask = foldr (.|.) 0 . map toBitMask 

-- | Not making this a typeclass, since it already generalizes over all 
-- imaginable instances with help of `MonadPlus`. 
fromBitMask :: 
    (MonadPlus m, Enum a, Bounded a, ToBitMask a) => 
    Int -> m a 
fromBitMask bm = msum $ map asInBM $ enumFrom minBound where 
    asInBM a = if isInBitMask bm a then return a else mzero 

isInBitMask :: (ToBitMask a) => Int -> a -> Bool 
isInBitMask bm a = let aBM = toBitMask a in aBM == aBM .&. bm 

Chạy nó với

main = do 
    print (fromBitMask 0 :: [Permission]) 
    print (fromBitMask 1 :: [Permission]) 
    print (fromBitMask 2 :: [Permission]) 
    print (fromBitMask 3 :: [Permission]) 
    print (fromBitMask 4 :: [Permission]) 
    print (fromBitMask 5 :: [Permission]) 
    print (fromBitMask 6 :: [Permission]) 
    print (fromBitMask 7 :: [Permission]) 

    print (fromBitMask 0 :: Maybe Permission) 
    print (fromBitMask 1 :: Maybe Permission) 
    print (fromBitMask 2 :: Maybe Permission) 
    print (fromBitMask 4 :: Maybe Permission) 

kết quả sau

[] 
[IsAllowedToLogin] 
[IsModerator] 
[IsAllowedToLogin,IsModerator] 
[IsAdmin] 
[IsAllowedToLogin,IsAdmin] 
[IsModerator,IsAdmin] 
[IsAllowedToLogin,IsModerator,IsAdmin] 
Nothing 
Just IsAllowedToLogin 
Just IsModerator 
Just IsAdmin 
+0

Cảm ơn rất nhiều! Có thể thêm các định nghĩa mặc định vào các lớp, vì vậy tôi có thể chỉ cần viết 'instance ToBitMask Permission' ??? Tất nhiên giả định rằng các quyền 'Permission' là instance của' Enum' và 'Bounded'. – Jakub

+0

@Jakub Vui lòng xem các cập nhật. –

+0

@Jakub Thực hiện cập nhật để khái quát hơn nữa, trong trường hợp bạn quan tâm. –

2

EnumSet có lẽ là chính xác những gì bạn muốn. Nó thậm chí có chức năng intToEnums (mặc dù nó dường như chỉ hoạt động liên tục với T Integer a của các loại tôi đã thử - cụ thể là T Int Char cho kết quả không mong muốn) và sẽ không được tạo lại các mục trùng lặp sau khi tuần tự hóa/deserializing (), trong khi danh sách có thể mang đến kỳ vọng đó.

+0

Vâng, bạn sẽ cần một 'Int' lớn hơn cho điều đó. Trừ khi máy của bạn sử dụng các từ 128 bit, bạn thậm chí sẽ không phù hợp với các ký tự ASCII cơ bản vào 'EnumSet'. Hãy thử 'Ordering' hoặc một cái gì đó như' (Bool, Bool) 'nếu bạn muốn sử dụng' Int 's. –

4

Tôi chắc chắn có điều gì đó về hackage đã thực hiện điều này, nhưng nó đủ đơn giản để cuộn tay của riêng bạn bằng cách sử dụng the Data.Bits module.

Bạn có thể đơn giản hóa enumsToInt thành một cái gì đó như foldl' (.|.) . map (bit . fromEnum), tức là, chuyển đổi thành chỉ số nguyên và sau đó thành từng bit, sau đó gấp với bitwise HOẶC. Nếu không có gì khác, điều này giúp bạn không phải lo lắng về việc xóa các bản sao.

Đối với intToEnums không có gì vô cùng tiện lợi, nhưng để có giải pháp nhanh chóng, bạn có thể làm điều gì đó như filter (testBit foo . fromEnum) [minBound .. maxBound]. Điều này tất nhiên chỉ hoạt động cho các loại Bounded và giả định rằng enum không có nhiều giá trị hơn loại bên ngoài có bit và fromEnum sử dụng các số nguyên liên tiếp bắt đầu từ 0, nhưng có vẻ như bạn đang bắt đầu với tất cả dù sao.

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