Хаскелл Преобразования 1-800 Чисел


Это маленькое приложение преобразует 1-800 номера (с буквами) в реальные цифры. Я очень расстроен функцией insertDashes как очень грубую силу. Я уверен, что есть гораздо более элегантное решение. Я бы признательны за любую обратную связь на хоть что-нибудь еще.

module Dial
    ( enter,
    stripDashes
    ) where

import Data.Char (ord, toLower, isNumber, intToDigit, isPunctuation)
import System.IO
import Data.List


enter = do
    putStrLn "Enter a phone number"
    num <- getLine
    let converted = insertDashes $ convertLetters $ stripDashes num
    putStrLn $ "You may dial " ++ converted

stripDashes :: String -> String
stripDashes xs = [ x | x <- xs, not (elem x  "-") ]

insertDashes :: String -> String
insertDashes xs = take 1 xs ++ ['-'] ++ take 3 (drop 1 xs) ++ ['-'] ++ take 3 (drop 4 xs) ++ ['-'] ++ take 4 (drop 7 xs) 

getDigit :: Char -> Int
getDigit letter
    | (ord letter >= 97) && (ord letter <= 99) = 2
    | ord letter <= 102 = 3
    | ord letter <= 105 = 4
    | ord letter <= 108 = 5
    | ord letter <= 111 = 6
    | ord letter <= 115 = 7
    | ord letter <= 118 = 8
    | ord letter <= 122 = 9
    | otherwise = 0

convertLetters :: String -> String
convertLetters xs = map (\x -> if (isNumber x) then x else (intToDigit $ getDigit $ toLower x)) xs


137
6
задан 24 марта 2018 в 04:03 Источник Поделиться
Комментарии
1 ответ

Тип подписи

Хорошая работа по обеспечению всех подписей типа. К сожалению, вы забыли одну: enter. Всегда включайте подписями типа на топ-уровне функции.

Удалить неиспользуемые импорты

Вы не используете Data.Listни System.IO.

Предпочитаю символ сравнения вместо ord x > n

В getDigitты сравниваешь персонажей по их ord значения. Это не легко на глаз. Это намного легче читать, если вы просто сравните буквы с персонажами:

getDigit :: Char -> Int
getDigit letter
| letter >= 'a' && letter <= 'c' = 2
| letter <= 'f' = 3
| letter <= 'i' = 4
| letter <= 'l' = 5
| letter <= 'o' = 6
| letter <= 's' = 7
| letter <= 'v' = 8
| letter <= 'z' = 9
| otherwise = 0

Есть и другие способы пишите getDigit, но они отличаются от личных предпочтений только.

Использование стандартной библиотеки

stripDashes это filter (/= '-') в маскировке:

stripDashes xs = [ x | x <- xs, not (elem x  "-")]
= [ x | x <- xs, x /= '-']
= filter (/= '-') xs

Поэтому использовать filter вместо списка осмысления:

stripDashes :: String -> String
stripDashes = filter (/= '-')

Кстати not (elem x y) это notElem x y.

Предпочитаю местных Привязок вместо длинных линий

Это личные предпочтения, но я думаю, что

convertLetters :: String -> String
convertLetters xs = map toDigit xs
where
toDigit x
| isNumber x = x
| otherwise = intToDigit $ getDigit $ toLower x

легче читать.

Использовать hlint чтобы избавиться от избыточных скобок

У вас есть много лишних скобок, которые могут сделать код трудно читать. hlint можете сообщить их вам. Он также сообщает not (elem x y) == notElem x y намек, кстати.

Предпочитаю чистые функции, если это возможно

У вас есть enter для полной трансформации. Было бы здорово, если бы была не-ИО вариант тоже, например

convert :: String -> String
convert = insertDashes . convertLetters . stripDashes

Таким образом, вы можете экспортировать enter, conver и stripDashes.

Использовать splitAt вместо take/drop комбинации

Наконец, мы посмотрим insertDashes. Прежде всего, давайте напишем привязки:

insertDashes :: String -> String
insertDashes xs = group1 ++ ['-'] ++ group2 ++ ['-'] ++ group3 ++ ['-'] ++ group4
where
group1 = take 1 xs
group2 = take 3 (drop 1 xs)
group3 = take 3 (drop 4 xs)
group4 = take 4 (drop 7 xs)

Это выглядит как идеальное место работы для splitAt:

-- do not include this function in your code, it's part of the Prelude
splitAt n xs = (take n xs, drop n xs)

Однако, если мы должны были переписать insertDashes С splitAtэто не вам лучше сразу:

import Data.List (intercalate)

insertDashes :: String -> String
insertDashes xs = group1 ++ ['-'] ++ group2 ++ ['-'] ++ group3 ++ ['-'] ++ group4
where
(group1,ys1) = splitAt 1 xs
(group2,ys2) = splitAt 3 ys1
(group3,ys3) = splitAt 3 ys2
(group4,_ ) = splitAt 4 ys3

Тьфу. На самом деле не лучше. Так что вместо этого напишем еще одну функцию:

splitAtNs :: [Int] -> [a] -> [[a]]
splitAtNs [] xs = [xs]
splitAtNs (n:ns) xs = as : splitAtNs ns bs
where (as, bs) = splitAt n xs

Сейчас insertDashes это

insertDashes :: String -> String
insertDashes xs = group1 ++ ['-'] ++ group2 ++ ['-'] ++ group3 ++ ['-'] ++ group4
where
(group1:group2:group3:group4:_) = splitAtNs [1,3,3,4] xs

Который выглядит почти нормально. Хотя, вот еще одно улучшение. Функция intercalate от Data.List приносит ее вниз на одну строку:

insertDashes :: String -> String
insertDashes = intercalate "-" . take 4 . splitAtNs [1,3,3,4]

5
ответ дан 24 марта 2018 в 08:03 Источник Поделиться