Saturas
Saturas

Reputation: 625

Haskell - Adding a Tuple to a List of Tuples if not already existing else increase existing value of Tuple

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

Answers (2)

Iceland_jack
Iceland_jack

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) into mp if key does not exist in the map. If the key 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

Robin Zigmond
Robin Zigmond

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:

  1. please use understandable variable names. You'll see I've replaced (a, b, c) with (name, amount, price) because this clearly explains to a reader what the values actually represent
  2. if (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

Related Questions