跳轉到內容

Haskell/庫/對映

來自華夏公益教科書

模組 Data.Map 提供了 Map 資料型別,它允許您儲存附加到特定 。這在其他語言中被稱為查詢表、字典或關聯陣列。

通常,擁有某種資料結構來將值或值列表關聯到特定鍵將非常有用。這通常被稱為字典,源於現實世界中的例子:現實生活中的字典將定義(值)與每個單詞(鍵)關聯起來;我們說字典是 從單詞到定義的對映。檔案系統驅動程式可能會保留一個從檔名到檔案資訊的對映。電話簿應用程式可能會保留一個從聯絡人姓名到電話號碼的對映。對映是一個非常通用和有用的資料型別。

為什麼不直接使用 [(a, b)]?

[編輯 | 編輯原始碼]

您可能在其他章節中看到,成對列表(或“查詢表”)通常用作一種對映,以及函式 lookup :: a -> [(a, b)] -> Maybe b。那麼為什麼不一直使用查詢表呢?以下是一些原因

  • 使用對映可以讓您訪問大量更實用的函式來處理查詢表。
  • 對映的實現效率遠高於查詢表,尤其是在查詢速度方面。[1]

庫函式

[編輯 | 編輯原始碼]

模組 Data.Map 提供了大量用於處理對映的函式,包括集合操作,例如並集和交集。完整的列表可以在核心庫文件中找到。[2]

以下示例實現了一個密碼資料庫。假設使用者是可信的,因此沒有經過身份驗證,可以訪問檢視或更改密碼。

 {- A quick note for the over-eager refactorers out there: This is (somewhat)
    intentionally ugly. It doesn't use the State monad to hold the DB because it
    hasn't been introduced yet. Perhaps we could use this as an example of How
    Monads Improve Things? -}
 
 module PassDB where
 
 import qualified Data.Map as M
 import System.Exit
 
 type UserName = String
 type Password = String
 type PassDB   = M.Map UserName Password 
   -- PassBD is a map from usernames to passwords
 
 -- | Ask the user for a username and new password, and return the new PassDB
 changePass :: PassDB -> IO PassDB
 changePass db = do
   putStrLn "Enter a username and new password to change."
   putStr "Username: "
   un <- getLine
   putStrLn "New password: "
   pw <- getLine
   if un `M.member` db               -- if un is one of the keys of the map
     then return $ M.insert un pw db -- then update the value with the new password
     else do putStrLn $ "Can't find username '" ++ un ++ "' in the database."
             return db
 
 -- | Ask the user for a username, whose password will be displayed.
 viewPass :: PassDB -> IO ()
 viewPass db = do
   putStrLn "Enter a username, whose password will be displayed."
   putStr "Username: "
   un <- getLine
   putStrLn $ case M.lookup un db of
                Nothing -> "Can't find username '" ++ un ++ "' in the database."
                Just pw -> pw
 
 -- | The main loop for interacting with the user. 
 mainLoop :: PassDB -> IO PassDB
 mainLoop db = do
   putStr "Command [cvq]: "
   c <- getChar
   putStr "\n"
   -- See what they want us to do. If they chose a command other than 'q', then
   -- recurse (i.e. ask the user for the next command). We use the Maybe datatype
   -- to indicate whether to recurse or not: 'Just db' means do recurse, and in
   -- running the command, the old datbase changed to db. 'Nothing' means don't
   -- recurse.
   db' <- case c of
            'c' -> fmap Just $ changePass db
            'v' -> do viewPass db; return (Just db)
            'q' -> return Nothing
            _   -> do putStrLn $ "Not a recognised command, '" ++ [c] ++ "'."
                      return (Just db)
   maybe (return db) mainLoop db'
 
 -- | Parse the file we've just read in, by converting it to a list of lines,
 --   then folding down this list, starting with an empty map and adding the
 --   username and password for each line at each stage.
 parseMap :: String -> PassDB
 parseMap = foldr parseLine M.empty . lines
     where parseLine ln map = 
             let [un, pw] = words ln
             in M.insert un pw map
 
 -- | Convert our database to the format we store in the file by first converting
 --   it to a list of pairs, then mapping over this list to put a space between
 --   the username and password
 showMap :: PassDB -> String
 showMap = unlines . map (\(un, pw) -> un ++ " " ++ pw) . M.toAscList
 
 main :: IO ()
 main = do
   putStrLn $ "Welcome to PassDB. Enter a command: (c)hange a password, " ++
              "(v)iew a password or (q)uit."
   dbFile <- readFile "passdb"
   db'    <- mainLoop (parseMap dbFile)
   writeFile "passdb" (showMap db')

備註

  1. Data.Map 的特定情況下,實現基於 大小平衡二叉樹
  2. http://hackage.haskell.org/package/containers-0.6.0.1/docs/Data-Map.html
華夏公益教科書