Assume a list of “cash records”. The cash records have a currency and an amount field.
Now I want to write a function add that takes a cash record, and a list of cash records and does the following:
if there is an entry with the same currency, update the existing list by adding the amounts
if there is no entry with the same currency, append it to the list.
I’ve written the following code, but I’m unsure about the elegance of replace
case find (\cash -> cash.currency == newCash.currency) cashList of
Some a ->
replace
[a]
[newCash]
cashList
None -> cashList <> [newCash]
Your solution works just fine, but worst case does two passes through the list. One to find the element and another to replace it.
In general, if you have a list where you want a key, in this case currency to be unique, I’d recommend using a map. TextMap would work really well for this.
If you want to stick with a list, I’d recommend one of three approaches for a functions like this: using the break function, using recursion, or using a fold:
data Cash = Cash with
currency : Text
amount : Decimal
deriving (Eq, Show)
-- Using `break`
addCash(newCash: Cash, cashList: [Cash]): [Cash] =
let
(h, t) = break (\cash -> cash.currency == newCash.currency) cashList
newt = case t of
c::t' -> (c with amount = c.amount + newCash.amount)::t'
[] -> [newCash]
in h ++ newt
-- Using recursion
addCash'(newCash: Cash, cashList: [Cash]): [Cash] =
case cashList of
[] -> [newCash]
c::t -> if c.currency == newCash.currency
then (c with amount = c.amount + newCash.amount)::t
else c :: addCash' (newCash, t)
-- using a fold
addCash''(newCash: Cash, cashList: [Cash]): [Cash] =
let
workfn c (processed, t) = if c.currency == newCash.currency
then (True, (c with amount = c.amount + newCash.amount)::t)
else (processed, c::t)
(processed, t) = foldr workfn (False, []) cashList
in if processed then t else newCash::t
test_addCash = script do
let
cashList =
[ Cash with currency = "A"; amount = 100.0
, Cash with currency = "B"; amount = 100.0
, Cash with currency = "C"; amount = 100.0]
newCash = Cash with currency = "B"; amount = 100.0
newCash' = Cash with currency = "D"; amount = 100.0
cashList' = [ Cash with currency = "A"; amount = 100.0
, Cash with currency = "B"; amount = 200.0
, Cash with currency = "C"; amount = 100.0]
cashList'' =
[ newCash'
, Cash with currency = "A"; amount = 100.0
, Cash with currency = "B"; amount = 100.0
, Cash with currency = "C"; amount = 100.0]
cashList''' =
[ Cash with currency = "A"; amount = 100.0
, Cash with currency = "B"; amount = 100.0
, Cash with currency = "C"; amount = 100.0
, newCash']
addCash (newCash, cashList) === cashList'
addCash (newCash', cashList) === cashList'''
addCash' (newCash, cashList) === cashList'
addCash' (newCash', cashList) === cashList'''
addCash'' (newCash, cashList) === cashList'
addCash'' (newCash', cashList) === cashList''
For completeness, here is a version using TextMap. As a bonus, you get commutativity on addition and equality on stashes of cash independent of currency-insertion order.
module Main where
import Daml.Script
import qualified DA.TextMap as TextMap
import DA.TextMap (TextMap)
data Cash = Cash (TextMap Decimal)
deriving (Eq, Show)
(===) : Eq a => a -> a -> Script ()
(===) a b = assert (a == b)
cash : Text -> Decimal -> Cash
cash cur amount =
Cash $ TextMap.fromList [(cur, amount)]
addCash : Cash -> Cash -> Cash
addCash (Cash cash1) (Cash cash2) =
Cash $ TextMap.merge
(\cur amount -> Some amount) -- don't change curs only in cash1
(\cur amount -> Some amount) -- don't change curs only in cash2
(\cur amount1 amount2 -> Some (amount1 + amount2)) -- add together amounts for curs in both
cash1
cash2
test_addCash = script do
let
stash = Cash $ TextMap.fromList [("A", 100.0), ("B", 100.0), ("C", 100.0)]
b = cash "B" 100.0
d = cash "D" 100.0
stashWithB = Cash $ TextMap.fromList [("A", 100.0), ("B", 200.0), ("C", 100.0)]
stashWithD = Cash $ TextMap.fromList [("A", 100.0), ("B", 100.0), ("C", 100.0), ("D", 100.0)]
stashWithBoth = Cash $ TextMap.fromList [("A", 100.0), ("B", 200.0), ("C", 100.0), ("D", 100.0)]
addCash (cash "A" 100.0) (addCash (cash "B" 100.0) (cash "C" 100.0)) === stash
addCash stash b === stashWithB
addCash b stash === stashWithB
addCash d stash === stashWithD
addCash b (addCash stash d) === stashWithBoth
addCash d (addCash stash b) === stashWithBoth
addCash stash (addCash b d) === stashWithBoth
addCash (addCash d b) stash === stashWithBoth