repl.it
@essic/

Kata (Haskell) - FooBarQix

Haskell

No description

fork
loading
Files
  • main.hs
main.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
-- http://codingdojo.org/kata/FooBarQix/
{-# LANGUAGE OverloadedStrings #-}

import Data.Text as T
import qualified Data.Map.Strict as M
import qualified Data.Char as C
import qualified Data.List as L
import Data.Maybe (catMaybes, fromMaybe)

newtype Digit = D Text
  deriving (Show,Eq)

newtype Number = N [Digit]
  deriving (Show, Eq)

toNumber :: Text -> Maybe Number
toNumber = 
  fmap (N . fmap D . T.chunksOf 1 ) . isNumeric
  where 
    isNumeric :: Text -> Maybe Text
    isNumeric t = 
      (const t) <$> (textToInt t)
    textToInt :: Text -> Maybe Int
    textToInt t =
      case reads . unpack $ t of
        [(s,_)] -> Just s
        _ -> Nothing


digits :: Number -> [Digit]
digits (N ds) = ds
  
digitToText :: Digit -> Text
digitToText (D t) = t

isDivisibleBy :: Int -> Number -> Maybe (Text -> Text)
isDivisibleBy i number = do
  n <- numberToInt number
  case n % i of
    0 -> Just id
    x -> Nothing
  where
    (%) :: Int -> Int -> Int
    (%) = mod

foo :: Number -> Maybe Text
foo = fmap ($ "Foo") . isDivisibleBy 3

bar :: Number -> Maybe Text
bar = fmap ($ "Bar") . isDivisibleBy 5

qix :: Number -> Maybe Text
qix = fmap ($ "Qix") . isDivisibleBy 7

replaceWithZero :: Text -> Text
replaceWithZero t =
  T.concat $ T.replace "0" "*" <$> T.chunksOf 1 t

is :: Text -> Digit -> Maybe Bool
is t (D d) 
  | t == d = Just True
  | otherwise = Nothing

isFoo :: Digit -> Maybe Text
isFoo = fmap (const "Foo") . is "3" 

isBar :: Digit -> Maybe Text
isBar = fmap (const "Bar") . is "5" 

isQix :: Digit -> Maybe Text
isQix = fmap (const "Qix") . is "7" 

isZero :: Digit -> Maybe Text
isZero = fmap (const "*") . is "0"

numberToInt :: Number -> Maybe Int
numberToInt (N digits) =
  case reads . unpack $ T.concat ( digitToText <$> digits) of
    [(s,_)] -> Just s
    _ -> Nothing


replaceNumberIfAnyTrue :: [Number -> Maybe Text] -> Number -> Maybe Text
replaceNumberIfAnyTrue fs number = 
  case catMaybes $ ($ number) <$> fs of
    [] -> Nothing
    r -> Just $ T.concat r

replaceDigit :: [Digit -> Maybe Text] -> Digit -> Maybe Text
replaceDigit [] _ = Nothing
replaceDigit (f:fs) d = 
  case f d of
    Nothing -> replaceDigit fs d
    r -> r

exampleStep2 :: M.Map Text Text
exampleStep2 =
  let results =
                [
                    ("101","1*1")
                  , ("303","FooFoo*Foo")
                  , ("105","FooBarQix*Bar")
                  , ("10101","FooQix**")
                ]
    in
      M.fromList results

exampleStep1 :: M.Map Text Text
exampleStep1 =
  let results = 
                [
                      ("1","1")
                    , ("2","2")
                    , ("3","FooFoo")
                    , ("4","4")
                    , ("5","BarBar")
                    , ("6","Foo")
                    , ("7","QixQix")
                    , ("8","8")
                    , ("9","Foo")
                    , ("10","Bar")
                    , ("13","Foo")
                    , ("15","FooBarBar")
                    , ("21","FooQix")
                    , ("33","FooFooFoo")
                    , ("51","FooBar")
                    , ("53" ,"BarFoo") 
                ]
    in 
      M.fromList results

-- We made it (Maybe Text) instead of Text as specified in the Kata, for convinience, it cost nothing to be safe in Haskell :p
computeStep1 :: Text -> Maybe Text
computeStep1 input = do
  number <- toNumber input
  case (replaceNumberIfAnyTrue [foo,bar,qix] number,T.concat . catMaybes $ (replaceDigit [isFoo,isBar,isQix]) <$> (digits number)) of
    (Nothing,"") -> pure input
    (Nothing,x) -> pure x
    (Just fbrResults,"") -> pure fbrResults
    (Just fbrResults,x) -> pure $ T.concat [fbrResults,x]

computeStep2 :: Text -> Maybe Text
computeStep2 input = do
  number <-  toNumber input
  case (replaceNumberIfAnyTrue [foo,bar,qix] number,T.concat . catMaybes $ (replaceDigit [isFoo,isBar,isQix,isZero]) <$> (digits number)) of
    (Nothing, "") -> pure input
    (Nothing, x ) -> pure $ if T.all (\c -> '*' == c) x then replaceWithZero input else x
    (Just fbrResults , "") -> pure fbrResults
    (Just fbrResults ,  x) -> pure $ T.concat [fbrResults,x]

runExampleStep1 :: M.Map Text Text
runExampleStep1 =
  M.fromList $
    (\t ->
      case computeStep1 t of
        Just r ->  (t,r)
        Nothing -> (t,"ERROR, Can't compute!")
    ) <$> M.keys exampleStep1

runExampleStep2 :: M.Map Text Text
runExampleStep2 =
  M.fromList $
    (\t ->
      case computeStep2 t of
        Just r ->  (t,r)
        Nothing -> (t,"ERROR, Can't compute!")
    ) <$> M.keys exampleStep2

main :: IO ()
main = do
    print . toNumber . replaceWithZero $ "101"
    computedStep1 <- pure runExampleStep1
    computedStep2 <- pure runExampleStep2
    print computedStep1
    (print $ computedStep1 == exampleStep1)
    print computedStep2
    (print $ computedStep2 == exampleStep2) 
GHCi, version 8.6.5
?