Here is my solution for an exercise, which requires translation of strings to key presses on a phone keypad (for example, to get 'b' the digit '2' needs to be pressed twice).
It's taken from the book Haskell from first principles, and originally appeared in 1HaskellADay. I'd love to get feedback.
module Phone where
import Data.Char
import Data.String.Utils
import Data.List
data DaPhone = DaPhone [String]
convo :: [String]
convo =
["Wanna play 20 questions",
"Ya",
"U 1st haha",
"Lol ok. Have u ever tasted alcohol lol",
"Lol ya",
"Wow ur cool haha. Ur turn",
"Ok. Do u think I am pretty Lol",
"Lol ya",
"Haha thanks just making sure rofl ur turn"]
-- validButtons = "1234567890*#"
type Digit = Char
-- Valid presses: 1 and up
type Presses = Int
reverseTaps :: DaPhone -> Char -> [(Digit, Presses)]
reverseTaps (DaPhone keys) c = if isUpper c then ('*', 1) : looks else looks
where looks = look keys (toLower c)
-- assuming the default phone definition
-- 'a' -> [('2', 1)]
-- 'A' -> [('*', 1), ('2', 1)]
look :: [String] -> Char -> [(Digit, Presses)]
look keys c = look' keys c 0
look' :: [String] -> Char -> Int -> [(Digit, Presses)]
look' [] _ _ = []
look' (x:xs) c n = if ind /= (-1) then [(head $ show n, ind + 1)] else look' xs c (n + 1)
where ind = maybe (-1) id $ elemIndex c x
cellPhonesDead :: DaPhone -> String -> [(Digit, Presses)]
cellPhonesDead = (concat .) . map . reverseTaps
-- count total presses
fingerTaps :: [(Digit, Presses)] -> Presses
fingerTaps = sum . map snd
mostPopularLetter :: String -> Char
mostPopularLetter = head . longest . group . sort
coolestLtr :: [String] -> Char
coolestLtr = mostPopularLetter . filter isAlpha . concat
coolestWord :: [String] -> String
coolestWord = head . longest . group . sort . words . join " "
longest :: [[a]] -> [a]
longest = maximumBy (\x y -> compare (length x) (length y))
phone :: DaPhone
phone = DaPhone keymap
keymap :: [String]
keymap =
[
" 0",
"1",
"abc2",
"def3",
"ghi4",
"jkl5",
"mno6",
"pqrs7",
"tuv8",
"wxyz9"
]
2 Answers 2
In real code, I would consider this to be too much naming for this little code. Most of this would probably be used only once and could thus be inlined. Since the exercise required implementations for all these, it's okay.
(concat .) . map
is foldMap
.
\x y -> compare (length x) (length y)
is comparing length
.
Did you try compiling this? ind /= Nothing
means that ind + 1
shouldn't work, and elemIndex takes the Char before the String.
look keys c = case asum $ zipWith (fmap . (,)) [0..] $ map (elemIndex c) keys of
Nothing -> []
Just (n, ind) -> [(head $ show n, ind + 1)]
-
\$\begingroup\$ Thanks, fixed
look
. I originally used my own function for index, which returned -1, thats why there was a confusion. \$\endgroup\$dimid– dimid2016年09月05日 09:33:44 +00:00Commented Sep 5, 2016 at 9:33
I am wondering whether DaPhone could have a better data structure, because I think your passing number around makes code complex. Such as:
data DaPhone = DaPhone [(Char, String)]
reverseTaps :: DaPhone -> Char -> [(Digit, Presses)]
reverseTaps phone c =
if isUpper c
then [('*', 1), look phone (toLower c)]
else [look phone c]
look :: DaPhone -> Char -> (Digit, Presses)
look (DaPhone ((digit, press):tl)) c =
case elemIndex c press of
Just idx -> (digit, idx + 1)
Nothing -> look (DaPhone tl) c
cellPhonesDead :: DaPhone -> String -> [(Digit, Presses)]
cellPhonesDead phone = foldMap (reverseTaps phone)
What I assume:
- look will not fail.
- DaPhone will store like ('2', 'abc2') instead of ('2', 'abc').
-
\$\begingroup\$ That's not valid Haskell code, since either
data
ornewtype
is missing. \$\endgroup\$Zeta– Zeta2016年09月14日 06:18:45 +00:00Commented Sep 14, 2016 at 6:18 -
\$\begingroup\$ sure. I just modify the core functions. you can find the data and newtype in original code. Because those definitions are fixed in this problem. @Zeta \$\endgroup\$Ningning– Ningning2016年09月14日 06:36:06 +00:00Commented Sep 14, 2016 at 6:36