Reputation: 625
I have a Tuple representing a Product:
type Product = (String, Int, Float)
String is the name, Int the amount and Float the price or value.
Now i want to do the following: I got a List of Products and into that List i want to add a new Product. But if the Product is already in the List i just want to increase the amount. (For now i dont care what happens if the price/value is not the same)
"If already in List then increase amount of that Product. Else add it to the List"
Example Input: ("Beer", 9, 3) [("Beer", 1, 3),("Milk", 3, 3),("Tea", 10, 3)]
Expected Output: [("Beer", 10, 3),("Milk", 3, 3),("Tea", 10, 3)]
or
Example Input: ("Apple", 2, 1) [("Beer", 1, 3),("Milk", 3, 3),("Tea", 10, 3)]
Expected Output: [("Beer", 1, 3),("Milk", 3, 3),("Tea", 10, 3),("Apple", 2, 1)]
(The List has no order so it does not matter where the new Product is inside the List)
addProduct :: Product -> [Product] -> [Product]
Sounds easy enough and actually i got a working method that does this:
-- check if a name is inside a List
nameInList :: String -> [Product] -> Bool
nameInList n [(a,b,c)] = if (a == n) then True else False
nameInList n ((a,b,c):xs) = if (a == n) then True else nameInList n xs
-- add a product to a list if its not already in that list else just increase amount of that product
addProduct :: Product -> [Product] -> [Product]
addProduct (a,b,c) xs = if nameInList a xs then newList else (a,b,c) : newList
where newList = [ (a2,b2+b,c2) | (a2,b2,c2) <- xs, a2 == a ] ++ [ (a2,b2,c2) | (a2,b2,c2) <- xs, a2 /= a]
But i was wondering if i can somehow achieve the same using pattern matching and recursion. I also feel like it should be possible without another function that does some checking.
Upvotes: 2
Views: 318
Reputation: 7014
If you represented them as a Map
you can make use of the insertWith
function
insertWith :: Ord k => (a -> a -> a) -> k -> a -> Map k a -> Map k a
Insert with a function, combining new value and old value.
insertWith f key value mp
will insert the pair(key, value)
intomp
ifkey
does not exist in the map. If thekey
does exist, the function will insert the pair(key, f new_value old_value)
.
This means you could implement it as follows, inserting what ever logic you want for the price
{-# Language BlockArguments #-}
{-# Language StandaloneKindSignatures #-}
import Data.Kind (Type)
import Data.Map (Map, insertWith)
type Product :: Type
type Product = Map String (Int, Float)
addProduct :: String -> (Int, Float) -> Product -> Product
addProduct = insertWith \(amount1, price) (amount2, _) -> (amount1 + amount2, price)
or if you're fine with biliftA2
: addProduct = insertWith (biliftA2 (+) const)
Upvotes: 3
Reputation: 18259
Provided you know your list of products will never have a duplicate name, this is indeed quite simple with pure recursion:
addProduct :: Product -> [Product] -> [Product]
addProduct p [] = [p]
addProduct product@(name, amount, price) (product'@(name', amount', price') : ps)
| name == name' = (name, amount' + amount, price') : ps
| otherwise = product' : addProduct product ps
(Notice that if the product is being added with a different price to the version currently in the list, this leaves the old price in place - I'll leave it up to your business requirements whether this is even possible and if so what should happen.)
Note the use of as patterns to allow the product
tuples to be pattern-matched against while still retaining a way to refer to the whole thing in the recursive case. This isn't essential but makes the code much more readable. And also I've used guards rather than if-then-else - this is more of a personal preference but I think Haskellers generally prefer it this way.
A couple of small notes on your own code:
(a, b, c)
with (name, amount, price)
because this clearly explains to a reader what the values actually representif (a == n) then True else False
is redundant because it's exactly the same as simply a == n
, so you should use the shorter, clearer version.Upvotes: 3