-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathHW05.hs
151 lines (124 loc) · 5.74 KB
/
HW05.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
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE OverloadedStrings, RecordWildCards #-}
module HW05 where
import Data.ByteString.Lazy (ByteString)
import Data.Map.Strict (Map)
import System.Environment (getArgs)
import qualified Data.ByteString.Lazy as BS
import qualified Data.Map.Strict as Map
import Parser
import Data.Bits
import Data.List
import Data.Ord
-- Exercise 1 -----------------------------------------
getSecret :: FilePath -> FilePath -> IO ByteString
getSecret x y = do
file1 <- BS.readFile x
file2 <- BS.readFile y
let result = BS.zipWith (xor) file1 file2
return $ BS.pack $ filter (/= 0) result
-- Exercise 2 -----------------------------------------
decryptWithKey :: ByteString -> FilePath -> IO ()
decryptWithKey key outFilePath = do
-- get the file contents
encryptedFileContents <- BS.readFile (outFilePath ++ ".enc")
-- unencrypt by xor'ing
let result = BS.pack $ BS.zipWith (xor) encryptedFileContents (BS.cycle key)
-- write it out to the current path
BS.writeFile outFilePath result
-- Exercise 3 -----------------------------------------
-- parseFile "clues/victims.json" :: IO (Maybe [TId])
-- parseFile "clues/transactions.json" :: IO (Maybe [Transaction])
parseFile :: FromJSON a => FilePath -> IO (Maybe a)
parseFile filepath = do
file <- BS.readFile filepath
return $ decode file
-- Exercise 4 -----------------------------------------
getBadTs :: FilePath -> FilePath -> IO (Maybe [Transaction])
getBadTs victims transactions = do
vs <- parseFile victims :: IO (Maybe [TId])
ts <- parseFile transactions :: IO (Maybe [Transaction])
return $ filterTransactions (vs, ts)
filterTransactions :: (Maybe [TId], Maybe [Transaction]) -> Maybe [Transaction]
filterTransactions ((Just victims), (Just transactions)) =
Just $ filter (\x -> elem (tid x) victims) transactions
filterTransactions _ = Nothing
-- Exercise 5 -----------------------------------------
getFlow :: [Transaction] -> Map String Integer
getFlow ts =
foldr (\t m-> Map.insertWith (+) (to t) (amount t) $ Map.insertWith (+) (from t) (negate $ amount t) m) (Map.empty) ts
-- Exercise 6 -----------------------------------------
getCriminal :: Map String Integer -> String
getCriminal amounts =
fst $ Map.foldrWithKey (\k e r-> if e > (snd r) then (k,e) else r) ("",0) amounts
-- Exercise 7 -----------------------------------------
-- seperate into payers and payees (people who lost and people who gained)
splitCustomers :: Map String Integer -> ((String, [(String, Integer)]),(String, [(String, Integer)]))
splitCustomers flow =
helper (Map.toList flow) (("payees", []), ("payers", [])) -- this is a fold
where
helper :: [(String, Integer)] -> ((String, [(String, Integer)]),(String, [(String, Integer)])) -> ((String, [(String, Integer)]),(String, [(String, Integer)]))
helper ((k,v):xs) (ps@(payees', payers'))
| v > 0 = helper xs (((fst payees'), ((k,v) : (snd payees'))), payers')
| v < 0 = helper xs (payees', (fst payers', ((k,v) : (snd payers'))))
| otherwise = helper xs ps
helper [] ps = ps
-- sort in descending order - payers who are owed the most, payees who gained the most
-- hint: sortBy in Data.List
sortAmounts :: ((String, [(String, Integer)]),(String, [(String, Integer)])) -> ((String, [(String, Integer)]),(String, [(String, Integer)]))
sortAmounts (payees, payers) =
(
(fst payees, sortBy (comparing snd) (snd payees)),
(fst payers, sortBy (comparing snd) (snd payers))
)
-- for each pair, have a payee pay the payer until payer loss is at 0 and payee debt is at 0
-- in the sample data, there is only one payee so, not writing extra code for multiple payees right now
payCustomers :: [TId] -> ((String, [(String, Integer)]),(String, [(String, Integer)])) -> [Transaction]
payCustomers tids list =
helper tids list []
where
helper :: [TId] -> ((String, [(String, Integer)]),(String, [(String, Integer)])) -> [Transaction] -> [Transaction]
helper [] _ transactions = transactions -- return transactions when we run out of ids
helper (t:tids') (payees@(_, ((eeName, currBalance):_)), (n2, ((erName, debt):ers))) transactions
| currBalance > debt =
helper tids'(payees, (n2, ers)) (Transaction { to=(erName), from=(eeName), amount=(currBalance - debt), tid=t} : transactions)
| currBalance == debt = -- last one, if it's below zero just negate it
Transaction { to=(erName), from=(eeName), amount=(currBalance - debt), tid=t} : transactions
helper _ _ transactions = transactions
undoTs :: Map String Integer -> [TId] -> [Transaction]
undoTs = undefined
-- Exercise 8 -----------------------------------------
writeJSON :: ToJSON a => FilePath -> a -> IO ()
writeJSON = undefined
-- Exercise 9 -----------------------------------------
doEverything :: FilePath -> FilePath -> FilePath -> FilePath -> FilePath
-> FilePath -> IO String
doEverything dog1 dog2 trans vict fids out = do
key <- getSecret dog1 dog2
decryptWithKey key vict
mts <- getBadTs vict trans
case mts of
Nothing -> error "No Transactions"
Just ts -> do
mids <- parseFile fids
case mids of
Nothing -> error "No ids"
Just ids -> do
let flow = getFlow ts
writeJSON out (undoTs flow ids)
return (getCriminal flow)
main :: IO ()
main = do
args <- getArgs
crim <-
case args of
dog1:dog2:trans:vict:ids:out:_ ->
doEverything dog1 dog2 trans vict ids out
_ -> doEverything "dog-original.jpg"
"dog.jpg"
"transactions.json"
"victims.json"
"new-ids.json"
"new-transactions.json"
putStrLn crim