読者です 読者をやめる 読者になる 読者になる

by shigemk2

当面は技術的なことしか書かない

キーから値へのマッピング

Haskell
phoneBook =
  [("betty", "555-2938"),
   ("honnie", "555-2222"),
   ("party", "333-1111"),
   ("hyper", "999-3838"),
   ("wendy", "222-1212"),
   ("penny", "888-8888")]

-- キーとリストを受け取り、キーに合致するもののみをフィルタで残して、
-- 最初のキー/値のペアを取り出し、値を返す
-- ただし、このやりかただと、キーがなかったらランタイムエラーが出るよ
findKey :: (Eq k) => k -> [(k, v)] -> v
findKey key xs = snd . head . filter (\(k, v) -> key == k) $ xs
*Main> phoneBook
[("betty","555-2938"),("honnie","555-2222"),("party","333-1111"),("hyper","999-3838"),("wendy","222-1212"),("penny","888-8888")]
*Main> findKey "wendy" phoneBook
"222-1212"
*Main> findKey "thudy" phoneBook
"*** Exception: Prelude.head: empty list
-- キーがなくても問題ないようにfindKeyを修正してみた
-- 単純に、基底部、リストのhead と tail の分割、再帰呼び出しをやっただけ
-- でもやっぱり、このやり方だと、すべての要素を走査する必要がある
findKey' :: (Eq k) => k -> [(k, v)] -> Maybe v
findKey' key [] = Nothing
findKey' key ((k,v):xs)
  | key == k = Just v
  | otherwise = findKey' key xs
*Main> findKey' "thudy" phoneBook
Nothing
*Main> findKey' "wendy" phoneBook
Just "222-1212"
-- 連想リストの代わりに、Mapを使う。
-- 連想リストをMapに変換する。
phoneBook2 :: Map.Map String String
phoneBook2 = Map.fromList $
  [("betty", "555-2938"),
   ("honnie", "555-2222"),
   ("party", "333-1111"),
   ("hyper", "999-3838"),
   ("wendy", "222-1212"),
   ("penny", "888-8888")]
# 連想リストをMapに変換する
*Main> Map.fromList [(3,"shoes"),(4,"trees"),(9,"bees")]
fromList [(3,"shoes"),(4,"trees"),(9,"bees")]
*Main> Map.fromList [("kima","greggs"),("jimmy","mcnulty"),("jay","landsman")]
fromList [("jay","landsman"),("jimmy","mcnulty"),("kima","greggs")]
# fromListだと、キーが重複するなら最後のだけを残す
*Main> Map.fromList [("MS",1),("MS",2),("MS",3)]
fromList [("MS",3)]
*Main> :t Map.fromList
Map.fromList :: Ord k => [(k, a)] -> Map.Map k a
# 連想リストからMapに変換すると、findKeyと同様の機能がlookupで使える
*Main> :t Map.lookup
Map.lookup :: Ord k => k -> Map.Map k a -> Maybe a
*Main> Map.lookup "wendy" phoneBook2
Just "222-1212"
*Main> Map.lookup "thudy" phoneBook2
Nothing
# 挿入
*Main> :t Map.insert
Map.insert :: Ord k => k -> a -> Map.Map k a -> Map.Map k a
*Main> let newBook = Map.insert "thudy" "888-9999" phoneBook2
*Main> Map.lookup "thudy" newBook
Just "888-9999"
*Main> newBook
fromList [("betty","555-2938"),("honnie","555-2222"),("hyper","999-3838"),("party","333-1111"),("penny","888-8888"),("thudy","888-9999"),("wendy","222-1212")]
-- 上記のMapでは、電話番号を文字列で表現しているので、
-- Intのリストで表現してみる
string2digits :: String -> [Int]
string2digits = map digitToInt . filter isDigit
*Main> string2digits "888-8888"
[8,8,8,8,8,8,8]
*Main> let intBook = Map.map string2digits phoneBook'
*Main> let intBook = Map.map string2digits phoneBook2
let intBook = Map.map string2digits phoneBook2
*Main> Map.lookup "wendy" intBook
Just [2,2,2,1,2,1,2]
-- 一人が複数の番号を持っているように電話帳を拡張する。
-- でもこれをformListすると番号がいくつか失われる
phoneBook3 =
  [("betty", "555-2938"),
   ("betty", "555-9999"),
   ("honnie", "555-2222"),
   ("party", "333-1111"),
   ("party", "333-2222"),
   ("hyper", "999-3838"),
   ("wendy", "222-1212"),
   ("wendy", "888-8888"),
   ("penny", "888-9999")]

-- 重複削除対策で、fromListWithをつかう。(キーの重複を削除しない)
phoneBookToMap :: (Ord k) => [(k, String)] -> Map.Map k String
phoneBookToMap xs = Map.fromListWith add xs
  where add number1 number2 = number1 ++ ", " ++ number2

-- 予め連想リストの値を単一要素のリストにすれば、電話番号を連結するのに++を使う
phoneBookToMap' :: (Ord k) => [(k,a)] -> Map.Map k [a]
phoneBookToMap' xs = Map.fromListWith (++) $ map (\(k, v) -> (k, [v])) xs
*Main> Map.lookup "wendy" $ phoneBookToMap phoneBook3
Just "888-8888, 222-1212"
*Main> Map.lookup "wendy" $ phoneBookToMap' phoneBook3
Just ["888-8888","222-1212"]