git.iko.soy/production-haskell
on master help sign in
.
commit184c660d5762fff612e0e863b83856878b2b7bb3 authoriko <mail@iko.soy> dateWed 6 Mar 2024, 21:00 (3 years ago) parent14c9909
5 files changed+226−10
seminars/1/src/Task.hs+41−6 seminars/2/package.yaml+1 seminars/2/src/Task.hs+179−1 seminars/2/task.cabal+5−3 seminars/1/shell.nix → shell.nix
seminars/1/src/Task.hs+41−6
@@ -34,6 +34,8 @@ import Data.Map (Map, empty, toList, insert, update, delete)
3434import Data.Monoid (Sum(..))
3535import qualified Data.Map
3636import Data.List (sortBy)
37+import Data.Maybe (catMaybes, listToMaybe, mapMaybe)
38+import Control.Applicative hiding (empty)
3739
3840-- _ _ _ __ __ _ _
3941-- | | (_)___| |_ | \/ | ___ _ __ ___ (_) __| |___
@@ -279,7 +281,9 @@ data DebitCard
279281-- If the balance of the debit card after deducing the price of the coffee
280282-- would become negative, return 'Nothing' from the function.
281283chargeCoffee :: Coffee -> DebitCard -> Maybe DebitCard
282−chargeCoffee = error "TODO: chargeCoffee"
284+chargeCoffee coffee card
285+ | price coffee <= balance card = Just card {balance=balance card - price coffee}
286+ | otherwise = Nothing
283287
284288-- | How was the coffee charged.
285289data PaymentMethod
@@ -327,7 +331,15 @@ data Customer
327331-- NOTE: The customer can only buy a coffee with strictly one payment method.
328332-- (Only one card or cash)
329333payForCoffee :: Customer -> Coffee -> Maybe PaymentMethod
330−payForCoffee = error "TODO: payForCoffee"
334+payForCoffee customer coffee =
335+ let checkCard = chargeCoffee coffee
336+ goodCards :: Maybe PaymentMethod
337+ goodCards = fmap Card $ listToMaybe $ mapMaybe checkCard (cards customer)
338+ cashOption = if cash customer >= price coffee then
339+ Just $ Cash (cash customer - price coffee)
340+ else
341+ Nothing
342+ in goodCards <|> cashOption
331343
332344-- | This function should apply the chosen payment method to the customer.
333345-- In other words, it needs to apply the charge to the customer himself
@@ -338,12 +350,21 @@ payForCoffee = error "TODO: payForCoffee"
338350--
339351-- NOTE: You can not change the order of the cards.
340352applyPayment :: Customer -> PaymentMethod -> Customer
341−applyPayment = error "TODO: applyPayment"
353+applyPayment customer paymentMethod =
354+ case paymentMethod of
355+ Cash change -> customer {cash = change }
356+ Card chargedCard ->
357+ let newCards = fmap (\card -> if cardId card == cardId chargedCard then chargedCard else card ) $ cards customer
358+ in customer {cards = newCards}
359+
342360
343361-- | Performs the full payment (as in 'payForCoffee') and
344362-- returns the modified customer (as in 'applyPayment').
345363buyCoffee :: Customer -> Coffee -> Maybe Customer
346−buyCoffee = error "TODO: buyCoffee"
364+buyCoffee customer coffe =
365+ let maybePaymentMethod = payForCoffee customer coffe in
366+ fmap (applyPayment customer) maybePaymentMethod
367+
347368
348369-- | You know that due to a medical condition the customer needs to watch his
349370-- sugar intake. The new privacy-invading piece of ... technology can now
@@ -357,7 +378,14 @@ buyCoffee = error "TODO: buyCoffee"
357378--
358379-- NOTE: You can not change the order of the coffee.
359380saveTheDiabetic :: [Coffee] -> (RUBAmount, [Coffee])
360−saveTheDiabetic = error "TODO: saveTheDiabetic"
381+saveTheDiabetic coffeeList =
382+ let isSugar = (`elem` [WhiteSugar, BrownSugar])
383+ sugarPrice extra = if isSugar extra then price extra else 0
384+ savedMonney = foldl (\savedMonney coffee -> savedMonney + (sum $ fmap sugarPrice $ extras coffee))
385+ 0 coffeeList
386+ coffeesWithoutSugar = fmap (\coffee -> coffee {extras = filter (not . isSugar) $ extras coffee} ) coffeeList
387+ in (savedMonney, coffeesWithoutSugar)
388+
361389
362390-- | And just to torment those who love sugar lets make a function which
363391-- calculates health hazard (sugar content) of the given orders.
@@ -367,4 +395,11 @@ saveTheDiabetic = error "TODO: saveTheDiabetic"
367395-- WhiteSugar: 2 extra danger points
368396-- BrownSugar: 1 extra danger point
369397calculateSugarDanger :: [Coffee] -> Int
370−calculateSugarDanger = error "TODO: calculateSugarDanger"
398+calculateSugarDanger coffeeList = sum $ fmap (
399+ \extra -> case extra of
400+ WhiteSugar -> 2
401+ BrownSugar -> 1
402+ _ -> 0) (coffeeList >>= extras)
403+
404+-- (>>=) :: m a -> (a -> m b) -> m b
405+-- (>>=) :: [a] -> (a -> [b]) -> [b]
seminars/2/package.yaml+1
@@ -5,6 +5,7 @@ license: Unlicense
55dependencies:
66 - base >= 4.7 && < 5
77 - mtl
8+ - QuickCheck
89
910library:
1011 source-dirs: src
seminars/2/src/Task.hs+179−1
@@ -1,5 +1,10 @@
1+{-# LANGUAGE DeriveFunctor #-}
2+{-# LANGUAGE InstanceSigs #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
4+
15module Task
26 ( calculate,
7+ Expr(..),
38 )
49where
510
@@ -7,6 +12,179 @@ import Control.Applicative
712import Control.Monad.State.Lazy
813import Data.Char
914import Data.Maybe
15+import Test.QuickCheck
16+import Text.Read
17+import Debug.Trace
18+import Control.Monad
19+
20+type ParsingResult a = Either String (a, String)
21+
22+-- Parser :: Type -> Type
23+newtype Parser a = Parser (String -> ParsingResult a)
24+ deriving Functor
25+
26+instance Applicative Parser where
27+ pure :: a -> Parser a
28+ pure x = Parser (\q -> Right (x, q))
29+
30+ (<*>) :: Parser (a -> b) -> Parser a -> Parser b
31+ (<*>)
32+ (Parser (e :: String -> ParsingResult (a -> b)))
33+ (Parser (x :: String -> ParsingResult a)) = Parser (\s ->
34+ case e s of
35+ Left err -> Left err
36+ Right (arr, sLeft) -> case x sLeft of
37+ Left err2 -> Left err2
38+ Right (resOfTypeA, sLeft2) -> Right (arr resOfTypeA, sLeft2))
39+
40+instance Monad Parser where
41+ return :: a -> Parser a
42+ return = pure
43+
44+ (>>=) :: Parser a -> (a -> Parser b) -> Parser b
45+ (>>=) (Parser x) e = Parser (\s ->
46+ case x s of
47+ Left err -> Left err
48+ Right (a, s1) -> case e a of
49+ Parser f -> f s1
50+ )
51+
52+instance MonadFail Parser where
53+ fail :: String -> Parser a
54+ fail err = Parser . const . Left $ err
55+
56+instance Alternative Parser where
57+ empty :: Parser a
58+ empty = Parser . const . Left $ "I'm empty"
59+
60+ (Parser left) <|> (Parser right) = Parser $ \s -> case (left s, right s) of
61+ (Right res, _) -> Right res
62+ (Left _, Right res) -> Right res
63+ (Left err1, Left err2) -> Left $ "Both alternatives failed: 1) " ++ err1 ++ "\n2:" ++ err2
64+
65+anyToken :: Parser Char
66+anyToken = Parser $ \s -> case s of
67+ "" -> Left "Empty String"
68+ (hd: tl) -> Right (hd, tl)
69+
70+eos :: Parser ()
71+eos = Parser $ \s -> case s of
72+ "" -> Right ((), "")
73+ s -> Left $ "unexpected trailing content: " ++ s
74+
75+token :: Char -> Parser ()
76+-- token expected = Parser $ \s -> case s of
77+-- "" -> Left "Empty String"
78+-- (hd: tl) -> if hd == expected then Right ((), tl) else Left $
79+token expected = do
80+ actual <- anyToken
81+ when (actual /= expected) $ fail $ "expected " ++ [expected] ++ ", got " ++ [actual]
82+-- when :: Applicative f => Bool -> f () -> f ()
83+
84+tokens :: String -> Parser ()
85+tokens "" = pure ()
86+tokens (c: cs) = do
87+ token c
88+ tokens cs
89+
90+-- "x"
91+-- u <- tokensWhile (== 'a')
92+
93+tokensWhile :: (Char -> Bool) -> Parser String
94+-- tokensWhile e = (do
95+-- char <- anyToken
96+-- str <- if e char then tokensWhile e else empty
97+-- pure (char:str)) <|> (pure "")
98+-- tokensWhile e = (do
99+-- char <- anyToken
100+-- str <- if e char then tokensWhile e else empty
101+-- pure (char:str)) <|> (pure "")
102+
103+tokensWhile e = tokensWhile1 e <|> pure ""
104+
105+tokensWhile1 :: (Char -> Bool) -> Parser String
106+tokensWhile1 e = do
107+ char <- anyToken
108+ -- traceShowM char
109+ str <- if e char then tokensWhile e else empty
110+ pure (char:str)
111+
112+-- isDigit :: Char -> Bool
113+
114+-- TODO: add negative numbers
115+number:: Parser Int
116+number = do
117+ chars <- tokensWhile1 isDigit
118+ case readMaybe chars of
119+ Just res -> return res
120+ Nothing -> fail $ "unexpected invalid number: '" ++ chars ++ "'"
121+
122+data Expr
123+ = Num Int
124+ | AddExpr Expr Expr
125+ | MulExpr Expr Expr
126+ | SubExpr Expr Expr
127+ | DivExpr Expr Expr
128+ deriving Show
129+
130+half :: Gen a -> Gen a
131+half = scale (`div` 2)
132+
133+instance Arbitrary Expr where
134+ arbitrary = do
135+ size <- getSize
136+ case size of
137+ 0 -> Num <$> arbitrary
138+ otherwise ->
139+ oneof [
140+ Num <$> arbitrary,
141+ AddExpgr <$> half arbitrary <*> half arbitrary,
142+ SubExpr <$> half arbitrary <*> half arbitrary,
143+ MulExpr <$> half arbitrary <*> half arbitrary,
144+ DivExpr <$> half arbitrary <*> half arbitrary
145+ ]
146+
147+parse :: Parser Expr
148+parse = Num <$> number <|> (do
149+ token '('
150+ tokensWhile isSpace
151+ expr1 <- parse
152+ tokensWhile isSpace
153+ operator <- anyToken
154+ tokensWhile isSpace
155+ expr2 <- parse
156+ tokensWhile isSpace
157+ token ')'
158+ case operator of
159+ '+' -> return $ AddExpr expr1 expr2
160+ '*' -> return $ MulExpr expr1 expr2
161+ '-' -> return $ SubExpr expr1 expr2
162+ '/' -> return $ DivExpr expr1 expr2
163+ _ -> fail "unknown operator"
164+ )
165+
166+parseFull :: Parser Expr
167+parseFull = do
168+ e <- parse
169+ eos
170+ return e
171+
172+evaluate :: Expr -> Maybe Int
173+evaluate (Num x) = Just x
174+evaluate (AddExpr left right) = (+) <$> (evaluate left) <*> (evaluate right)
175+evaluate (MulExpr left right) = (*) <$> (evaluate left) <*> (evaluate right)
176+evaluate (SubExpr left right) = (-) <$> (evaluate left) <*> (evaluate right)
177+evaluate (DivExpr _ right) | evaluate right == Just 0 = Nothing
178+evaluate (DivExpr left right) = div <$> (evaluate left) <*> (evaluate right)
179+
180+
181+runParser :: Parser x -> String -> Maybe x
182+runParser (Parser parser_fun) context =
183+ case parser_fun context of
184+ Left msg -> trace msg Nothing
185+ Right (x, _) -> Just x
10186
11187calculate :: String -> Maybe Int
12−calculate = error "TODO: calculate"
188+calculate s = do
189+ expression <- runParser parseFull s
190+ evaluate expression
seminars/2/task.cabal+5−3
@@ -4,7 +4,7 @@ cabal-version: 2.2
44--
55-- see: https://github.com/sol/hpack
66--
7−-- hash: 20a39a4ec02d6e75694b336bd8631bf5d630fcaacb76de84c7263c8abe1c45b3
7+-- hash: c2b1bcd2599c61a3fda41614499046ca5654131a2e5ac9976e0c07ec2b8887d8
88
99name: task
1010version: 0.1.0.0
@@ -21,7 +21,8 @@ library
2121 hs-source-dirs:
2222 src
2323 build-depends:
24− base >=4.7 && <5
24+ QuickCheck
25+ , base >=4.7 && <5
2526 , mtl
2627 default-language: Haskell2010
2728
@@ -36,7 +37,8 @@ test-suite task-tests
3637 hs-source-dirs:
3738 test
3839 build-depends:
39− base >=4.7 && <5
40+ QuickCheck
41+ , base >=4.7 && <5
4042 , mtl
4143 , task
4244 , tasty
seminars/1/shell.nix → shell.nixrenamed

The content is the same.