mirror of
https://github.com/ikoHSE/sc-task-2.git
synced 2024-11-23 10:15:30 +03:00
Initial task
This commit is contained in:
parent
fd64f0d792
commit
7f74a909ab
17
README.md
Normal file
17
README.md
Normal file
@ -0,0 +1,17 @@
|
||||
# 🎒📓💻 Task 2
|
||||
|
||||
**NOTE: If you just fork this repository it will not be graded. Ask your supervisor for a special GitHub classroom URL.**
|
||||
|
||||
## How to do the task
|
||||
|
||||
Your task is to write a valid definition for every function declared in `src/Task.hs`. All functions have a comment describing what the function needs to do with some examples.
|
||||
|
||||
There are predefined tests for you to check your answer. You can look at the tests for some more usage examples in `test/Tests.hs`.
|
||||
|
||||
After defining all functions you then need to commit **and push** your code to your repository. An automated script will then check your solution and give you feedback as a CI result in your GitHub repository.
|
||||
|
||||
## FAQ
|
||||
|
||||
> Can I add extra functions in the `Task.hs` file?
|
||||
|
||||
Yes, you can, but you can not remove existing functions.
|
313
src/Task.hs
313
src/Task.hs
@ -1,3 +1,312 @@
|
||||
module Task
|
||||
(
|
||||
) where
|
||||
( -- * List Monoids
|
||||
constructFirstMonoid,
|
||||
destructFirstMonoid,
|
||||
FirstMonoid,
|
||||
constructLastMonoid,
|
||||
destructLastMonoid,
|
||||
LastMonoid,
|
||||
|
||||
-- * Todo List
|
||||
Priority (..),
|
||||
Completion (..),
|
||||
TaskManager (..),
|
||||
MyTaskManager,
|
||||
|
||||
-- * Coffee
|
||||
DebitCard (..),
|
||||
Coffee (..),
|
||||
CoffeeExtra (..),
|
||||
HasPrice (..),
|
||||
chargeCoffee,
|
||||
PaymentMethod (..),
|
||||
Customer (..),
|
||||
payForCoffee,
|
||||
applyPayment,
|
||||
buyCoffee,
|
||||
saveTheDiabetic,
|
||||
calculateSugarDanger,
|
||||
)
|
||||
where
|
||||
|
||||
-- _ _ _ __ __ _ _
|
||||
-- | | (_)___| |_ | \/ | ___ _ __ ___ (_) __| |___
|
||||
-- | | | / __| __| | |\/| |/ _ \| '_ \ / _ \| |/ _` / __|
|
||||
-- | |___| \__ \ |_ | | | | (_) | | | | (_) | | (_| \__ \
|
||||
-- |_____|_|___/\__| |_| |_|\___/|_| |_|\___/|_|\__,_|___/
|
||||
|
||||
-- Write a datatype and a Monoid instance for it such that:
|
||||
--
|
||||
-- (destructFirstMonoid . fold . fmap constructFirstMonoid) == safeHead
|
||||
--
|
||||
-- (constructFirstMonoid and destructFirstMonoid would wrap and
|
||||
-- unwrap the `FirstMonoid`)
|
||||
--
|
||||
-- In other words, it needs to have a monoid instance such that `fold`ing
|
||||
-- a list of `FirstMonoid`s should return the first element
|
||||
--
|
||||
-- >>> (destructFirstMonoid . fold . fmap constructFirstMonoid) [1, 2, 3]
|
||||
-- Just 3
|
||||
--
|
||||
-- >>> (destructFirstMonoid . fold . fmap constructFirstMonoid) []
|
||||
-- Nothing
|
||||
data FirstMonoid a
|
||||
|
||||
-- Warps an `a` in a `FirstMonoid`.
|
||||
constructFirstMonoid :: a -> FirstMonoid a
|
||||
constructFirstMonoid = error "TODO: constructFirstMonoid"
|
||||
|
||||
-- Unwraps the `a` from a `FirstMonoid`, if there is one.
|
||||
destructFirstMonoid :: FirstMonoid a -> Maybe a
|
||||
destructFirstMonoid = error "TODO: destructFirstMonoid"
|
||||
|
||||
-- Write a datatype and a Monoid instance for it such that:
|
||||
--
|
||||
-- (destructLastMonoid . fold . fmap constructLastMonoid) == safeLast
|
||||
--
|
||||
-- (constructLastMonoid and destructLastMonoid would wrap and
|
||||
-- unwrap the `LastMonoid`)
|
||||
--
|
||||
-- In other words, it needs to have a monoid instance such that `fold`ing
|
||||
-- a list of `LastMonoid`s should return the last element
|
||||
--
|
||||
-- >>> (destructLastMonoid . fold . fmap constructLastMonoid) [1, 2, 3]
|
||||
-- Just 1
|
||||
--
|
||||
-- >>> (destructLastMonoid . fold . fmap constructLastMonoid) []
|
||||
-- Nothing
|
||||
data LastMonoid a
|
||||
|
||||
-- Warps an `a` in a `LastMonoid`.
|
||||
constructLastMonoid :: a -> LastMonoid a
|
||||
constructLastMonoid = error "TODO: constructLastMonoid"
|
||||
|
||||
-- Unwraps the `a` from a `LastMonoid`, if there is one.
|
||||
destructLastMonoid :: LastMonoid a -> Maybe a
|
||||
destructLastMonoid = error "TODO: destructLastMonoid"
|
||||
|
||||
-- _____ _ _ _ _
|
||||
-- |_ _|__ __| | ___ | | (_)___| |_
|
||||
-- | |/ _ \ / _` |/ _ \ | | | / __| __|
|
||||
-- | | (_) | (_| | (_) | | |___| \__ \ |_
|
||||
-- |_|\___/ \__,_|\___/ |_____|_|___/\__|
|
||||
|
||||
-- Your task is to create a todo list backend, which supports adding tasks,
|
||||
-- changing the priority of tasks, completing tasks, renaming tasks, and
|
||||
-- displaying tasks as a list in order of descending priority.
|
||||
|
||||
-- | The priority of a task.
|
||||
data Priority = Low | Medium | High
|
||||
deriving (Show, Eq)
|
||||
|
||||
-- | The completion status of a task.
|
||||
data Completion = Completed | NotCompleted
|
||||
deriving (Show, Eq)
|
||||
|
||||
class TaskManager t where
|
||||
-- | Returns an empty task manager backend (it should have no tasks).
|
||||
emptyTaskManager :: t
|
||||
|
||||
-- | Creates a new task with the given name and priority.
|
||||
-- Tasks are uniquely identified by their name.
|
||||
-- When a task is newly created its status should be 'NotCompleted'.
|
||||
--
|
||||
-- If there is already a task with the given name, then the
|
||||
-- behavior is undefined. (You can do whatever you want)
|
||||
createTask :: String -> Priority -> t -> t
|
||||
|
||||
-- | Toggles a task with the given name.
|
||||
-- If the task has the status 'Completed', then it would become 'NotCompleted'.
|
||||
-- If the task has the status 'NotCompleted', then it would become 'Completed'.
|
||||
--
|
||||
-- Needless to say, toggling the same task twice should be
|
||||
-- equivalent to not doing anything.
|
||||
--
|
||||
-- If there is no tasks with the given name, then it does nothing.
|
||||
toggleTaskCompletion :: String -> t -> t
|
||||
|
||||
-- | Removes the task with the given name.
|
||||
-- If there isn't a task with the given name, then it does nothing.
|
||||
removeTask :: String -> t -> t
|
||||
|
||||
-- | Sets the priority of the task with the given name.
|
||||
-- If there isn't a task with the given name, then it does nothing.
|
||||
modifyPriority :: String -> Priority -> t -> t
|
||||
|
||||
-- | Renames the task with the given name to have a new name.
|
||||
-- The first argument is the old name, the second argument is the new name.
|
||||
-- If there isn't a task with the given name, then it does nothing.
|
||||
-- If the new name clashes with an existing task, then the
|
||||
-- behavior is undefined. (You can do whatever you want)
|
||||
renameTask :: String -> String -> t -> t
|
||||
|
||||
-- | Gets the list of all tasks, sorted by priority in the order:
|
||||
-- High, Medium, Low
|
||||
--
|
||||
-- Ordering of tasks with the same priority is not defined
|
||||
-- (You can do whatever you want)
|
||||
getPriorityList :: t -> [(Completion, Priority, String)]
|
||||
|
||||
-- The task manager you are creating.
|
||||
data MyTaskManager
|
||||
|
||||
-- TODO: You have to make your 'MyTaskManager' an instance of
|
||||
-- the 'TaskManager' typeclass.
|
||||
instance TaskManager MyTaskManager
|
||||
|
||||
-- ____ __ __
|
||||
-- / ___|___ / _|/ _| ___ ___
|
||||
-- | | / _ \| |_| |_ / _ \/ _ \
|
||||
-- | |__| (_) | _| _| __/ __/
|
||||
-- \____\___/|_| |_| \___|\___|
|
||||
--
|
||||
-- /~~~~~~~~~~~~~~~~~~~/|
|
||||
-- / /######/ / |
|
||||
-- / /______/ / |
|
||||
-- ========================= /||
|
||||
-- |_______________________|/ ||
|
||||
-- | \****/ \__,,__/ ||
|
||||
-- |===\**/ __,,__ ||
|
||||
-- |______________\====/%____||
|
||||
-- | ___ /~~~~\ % / |
|
||||
-- _| |===|=== / \%_/ |
|
||||
-- | | |###| |########| | /
|
||||
-- |____\###/______\######/__|/
|
||||
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
-- | Just a typealias to make types more readable.
|
||||
type RUBAmount = Integer
|
||||
|
||||
-- | A debit card.
|
||||
-- Note: the balance can be negative.
|
||||
data DebitCard
|
||||
= DebitCard
|
||||
{ balance :: RUBAmount,
|
||||
cardId :: Int
|
||||
}
|
||||
|
||||
-- | Represents a cup of coffee with extra ingredients added.
|
||||
data Coffee
|
||||
= Coffee
|
||||
{ -- | The price of the coffee itself
|
||||
-- (without the cost of the extra ingredients.)
|
||||
cost :: RUBAmount,
|
||||
-- | The extra ingredients added to the coffee.
|
||||
extras :: [CoffeeExtra]
|
||||
}
|
||||
|
||||
-- Extra ingredients added to the coffee.
|
||||
data CoffeeExtra
|
||||
= Cream
|
||||
| AlmondMilk
|
||||
| SoyMilk
|
||||
| OatMilk
|
||||
| Cinnamon
|
||||
| WhiteSugar
|
||||
| BrownSugar
|
||||
|
||||
-- | A typeclass representing objects, for which the price can be calculated.
|
||||
class HasPrice x where
|
||||
-- | Calculated the price of the item.
|
||||
price :: x -> RUBAmount
|
||||
|
||||
-- I have already made a 'CoffeeExtra' an instance of 'HasPrice' for you.
|
||||
-- You should use this in price calculations.
|
||||
instance HasPrice CoffeeExtra where
|
||||
price Cream = 50
|
||||
price AlmondMilk = 70
|
||||
price SoyMilk = 80
|
||||
price OatMilk = 65
|
||||
price Cinnamon = 20
|
||||
price WhiteSugar = 15
|
||||
price BrownSugar = 35
|
||||
|
||||
-- TODO: You want to make 'Coffee' an instance of 'HasPrice'.
|
||||
instance HasPrice Coffee
|
||||
|
||||
-- | Given a coffee and a debit card, deduce the total price of the coffee from
|
||||
-- the debit card. Return the given debit card with the reduced balance.
|
||||
--
|
||||
-- If the balance of the debit card after deducing the price of the coffee
|
||||
-- would become negative, return 'Nothing' from the function.
|
||||
chargeCoffee :: Coffee -> DebitCard -> Maybe DebitCard
|
||||
chargeCoffee = error "TODO: chargeCoffee"
|
||||
|
||||
-- | How was the coffee charged.
|
||||
data PaymentMethod
|
||||
= -- | The coffee was charged using a debit card.
|
||||
Card
|
||||
{ -- | The debit card with the balance deduced.
|
||||
chargedCard :: DebitCard
|
||||
}
|
||||
| -- | The coffee was charged with cash.
|
||||
Cash
|
||||
{ -- | The leftover money from the payment.
|
||||
-- (We assume that the person pays with all of the cash he has.)
|
||||
change :: RUBAmount
|
||||
}
|
||||
|
||||
-- | Represents a coffee shop customer with all of the money he has on him.
|
||||
data Customer
|
||||
= Customer
|
||||
{ -- | All of the debit hards the person has on him.
|
||||
-- Sorted in order of descending preference.
|
||||
-- (He would prefer to use the first card in the list.)
|
||||
cards :: [DebitCard],
|
||||
-- | The amount of cash the customer has on him.
|
||||
cash :: RUBAmount
|
||||
}
|
||||
|
||||
-- You have some new privacy-invading piece of ... technology, which
|
||||
-- automatically scans the customer, determines how he would prefer to pay and
|
||||
-- charges him automatically.
|
||||
|
||||
-- | This function determines how the given customer would prefer to pay for the
|
||||
-- coffee he wants.
|
||||
--
|
||||
-- The customer would always prefer to pay with a debit card if it is possible.
|
||||
-- The customer has a strict preference in the cards he carries with him:
|
||||
-- he would always like to pay with the first card in his wallet (the list). If
|
||||
-- the first card doesn't have enough money on it, then he would prefer to use
|
||||
-- the second card, and so on.
|
||||
--
|
||||
-- If non of the cards have enough balance to cover the coffee, then the
|
||||
-- customer will have to pay in cash.
|
||||
--
|
||||
-- NOTE: The customer can only buy a coffee with strictly one payment method.
|
||||
-- (Only one card or cash)
|
||||
payForCoffee :: Customer -> Coffee -> Maybe PaymentMethod
|
||||
payForCoffee = error "TODO: payForCoffee"
|
||||
|
||||
-- | This function should apply the chosen payment method to the customer.
|
||||
-- In other words, it needs to apply the charge to the customer himself
|
||||
-- (update the debit card or update the amout of cash he currently has)
|
||||
applyPayment :: Customer -> PaymentMethod -> Customer
|
||||
applyPayment = error "TODO: applyPayment"
|
||||
|
||||
-- | Performs the full payment (as in 'payForCoffee') and
|
||||
-- returns the modified customer (as in 'applyPayment').
|
||||
buyCoffee :: Customer -> Coffee -> Maybe Customer
|
||||
buyCoffee = error "TODO: buyCoffee"
|
||||
|
||||
-- | You know the due to a medical condition the customer needs to watch his
|
||||
-- sugar intake. The new privacy-invading piece of ... technology can now
|
||||
-- automatically apply filters to coffee orders.
|
||||
--
|
||||
-- You have to make a filter, which removes any occurrences of sugar
|
||||
-- (either WhiteSugar or BrownSugar) from the ordered cups of coffee and
|
||||
-- return the filtered orders and the amount of money the customer saves by
|
||||
-- being healthy (sugar costs money after all). So, you also have to count how
|
||||
-- much the total sugar would have cost.
|
||||
saveTheDiabetic :: [Coffee] -> (RUBAmount, [Coffee])
|
||||
saveTheDiabetic = error "TODO: saveTheDiabetic"
|
||||
|
||||
-- | And just to torment those who love sugar lets make a function which
|
||||
-- calculates health hazard (sugar content) of the given orders.
|
||||
--
|
||||
-- The only ingredient that elevates the danger level is, of course, sugar:
|
||||
--
|
||||
-- WhiteSugar: 2 extra danger points
|
||||
-- BrownSugar: 1 extra danger point
|
||||
calculateSugarDanger :: [Coffee] -> Int
|
||||
calculateSugarDanger = error "TODO: calculateSugarDanger"
|
||||
|
Loading…
Reference in New Issue
Block a user