content: import Data.Char (toUpper)
  import Data.Function (on)
  import Data.List (groupBy, sortBy)
  import Data.Maybe (fromMaybe, isJust, isNothing)
  
  toKey :: Char -> Maybe Char
  toKey ch
    | ch < 'A' = Nothing
    | ch < 'D' = Just '2'
    | ch < 'G' = Just '3'
    | ch < 'J' = Just '4'
    | ch < 'M' = Just '5'
    | ch < 'P' = Just '6'
    | ch < 'T' = Just '7'
    | ch < 'W' = Just '8'
    | ch <= 'Z' = Just '9'
    | otherwise = Nothing
  
  toKeyString :: String -> Maybe String
  toKeyString st
    | any isNothing mch = Nothing
    | otherwise = Just $ map (fromMaybe '!') mch
    where
      mch = map (toKey . toUpper) st
  
  showTextonym :: [(String, String)] -> String
  showTextonym ts =
    fst (head ts)
      ++ " => "
      ++ concat
        [ w ++ " "
          | (_, w) <- ts
        ]
  
  main :: IO ()
  main = do
    let src = "unixdict.txt"
    contents <- readFile src
    let wordList = lines contents
        keyedList =
          [ (key, word)
            | (Just key, word) <-
                filter (isJust . fst) $
                  zip (map toKeyString wordList) wordList
          ]
        groupedList =
          groupBy ((==) `on` fst) $
            sortBy (compare `on` fst) keyedList
        textonymList = filter ((> 1) . length) groupedList
    mapM_ putStrLn $
      [ "There are "
          ++ show (length keyedList)
          ++ " words in "
          ++ src
          ++ " which can be represented by the digit key mapping.",
        "They require "
          ++ show (length groupedList)
          ++ " digit combinations to represent them.",
        show (length textonymList) ++ " digit combinations represent Textonyms.",
        "",
        "Top 5 in ambiguity:"
      ]
        ++ fmap
          showTextonym
          ( take 5 $
              sortBy (flip compare `on` length) textonymList
          )
        ++ ["", "Top 5 in length:"]
        ++ fmap
          showTextonym
          (take 5 $ sortBy (flip compare `on` (length . fst . head)) textonymList)
  