跳转到内容

Haskell/库/Maps

来自维基教科书,开放世界中的开放书籍

Data.Map 模块提供了 Map 数据类型,它允许你存储与特定 关联的 。这在其他语言中被称为查找表、字典或关联数组。

通常,拥有某种数据结构来关联特定键的值或值列表非常有用。这通常被称为字典,源于现实世界中的例子:现实生活中的字典将定义(值)与每个单词(键)关联;我们说字典是 从单词到定义的映射。文件系统驱动程序可能会维护从文件名到文件信息的映射。电话簿应用程序可能会维护从联系人姓名到电话号码的映射。Maps 是一种非常通用且有用的数据类型。

为什么不直接使用 [(a, b)]

[编辑 | 编辑源代码]

你可能在其他章节中看到过,成对列表(或“查找表”)经常被用作一种映射,以及函数 lookup :: a -> [(a, b)] -> Maybe b。那么为什么不一直使用查找表呢?以下是一些原因

  • 使用 Maps 可以让你访问大量用于处理查找表的更实用的函数。
  • Maps 的实现效率远高于查找表,特别是在查找速度方面。[1]

库函数

[编辑 | 编辑源代码]

Data.Map 模块提供了大量用于处理 Maps 的函数,包括集合操作,例如并集和交集。完整列表可以在核心库文档中找到。[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
华夏公益教科书