【问题标题】:Haskell - Adding a Tuple to a List of Tuples if not already existing else increase existing value of TupleHaskell - 如果不存在,则将元组添加到元组列表中,否则会增加元组的现有值
【发布时间】:2021-07-07 16:47:04
【问题描述】:

我有一个表示产品的元组:

type Product = (String, Int, Float)

String 是名称,Int 是数量,Float 是价格或价值。

现在我想做以下事情: 我有一个产品列表,我想在该列表中添加一个新产品。 但如果产品已经在列表中,我只想增加数量。 (现在我不在乎如果价格/价值不一样会发生什么)

“如果已经在列表中,则增加该产品的数量。否则将其添加到列表中”

示例输入:("Beer", 9, 3) [("Beer", 1, 3),("Milk", 3, 3),("Tea", 10, 3)]
预期输出:[("Beer", 10, 3),("Milk", 3, 3),("Tea", 10, 3)]

示例输入: ("Apple", 2, 1) [("Beer", 1, 3),("Milk", 3, 3),("Tea", 10, 3)]
预期输出:[("Beer", 1, 3),("Milk", 3, 3),("Tea", 10, 3),("Apple", 2, 1)]
(列表没有顺序,因此新产品在列表中的位置无关紧要)

addProduct :: Product -> [Product] -> [Product]

听起来很简单,实际上我有一个工作方法可以做到这一点:

-- 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]

但我想知道是否可以使用模式匹配和递归以某种方式实现相同的目标。我也觉得没有其他功能进行检查应该是可能的。

【问题讨论】:

    标签: haskell


    【解决方案1】:

    如果您知道您的产品列表永远不会有重复的名称,这确实非常简单,纯递归:

    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
    

    (请注意,如果产品以与列表中当前版本不同的价格添加,这将保留旧价格 - 我将根据您的业务需求来决定这是否可能,如果可能的话应该发生什么。)

    注意使用as patterns 允许product 元组进行模式匹配,同时仍然保留在递归情况下引用整个事物的方法。这不是必需的,但会使代码更具可读性。而且我使用了守卫而不是 if-then-else - 这更多是个人喜好,但我认为 Haskellers 通常更喜欢这种方式。

    关于您自己的代码的一些小注释:

    1. 请使用易于理解的变量名。您会看到我已将 (a, b, c) 替换为 (name, amount, price),因为这清楚地向读者解释了这些值实际代表什么
    2. if (a == n) then True else False 是多余的,因为它与简单的a == n 完全相同,因此您应该使用更短、更清晰的版本。

    【讨论】:

    • 谢谢,这正是我想要的。不知道为什么我想不出那个似乎并不复杂的解决方案。
    【解决方案2】:

    如果您将它们表示为Map,您可以使用insertWith 函数

    insertWith :: Ord k => (a -> a -> a) -> k -> a -> Map k a -> Map k a 
    

    用函数插入,结合新值和旧值。如果 key 在地图中不存在,insertWith f key value mp 将把 (key, value) 对插入到 mp 中。如果key 确实存在,该函数将插入(key, f new_value old_value) 对。

    这意味着您可以按如下方式实现它,插入您想要的价格逻辑

    {-# 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)
    

    或者如果您对biliftA2 满意:addProduct = insertWith (biliftA2 (+) const)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-10-25
      • 1970-01-01
      • 2021-12-27
      • 1970-01-01
      • 2012-04-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多