我熟悉对于包含数字、字符或字符串的列表使用nub
函数,但是有人能解释一下如何在一组键值对的列表中使用来自Data.List
的nub
函数吗?
例如:
[('a', 3),( 'b', 2),('a', 1),('b', 4)]
to
[('a', 3),('b', 2)]
正如您所看到的,我想删除所有键已经存在于列表中的键值对(key, value)。
Prelude Data.List> nubBy (\(x,_) (x', _) -> x == x') [('a',1),('b',2),('b',3)]
[('a',1),('b',2)]
removeDuplicate :: (Eq a) => [(a, b)] -> [(a, b)]
removeDuplicate lst = go lst []
where go [] seen = seen
go (x:xs) seen
| any (\(a, _) -> a == fst x) seen = go xs seen
| otherwise = go xs (seen ++ [x])
*Main> removeDuplicate [('a', 3),( 'b', 2),('a', 1),('b', 4)]
[('a',3),('b',2)]
foldl
来写成:removeDuplicate' = foldl (\seen x -> if any (\(a, _) -> a == fst x) seen
then seen
else seen ++ [x]) []
Data.List
的 sortBy
函数按每个元组中第一个元素进行排序,然后使用 groupBy
函数对它们进行分组。然后使用 map()
函数从每个组中取出第一个元组,如下所示:import Data.List
import Data.Function
removeDuplicate'' :: (Ord a) => [(a, b)] -> [(a, b)]
removeDuplicate'' xs = map head $ groupBy ((==) `on` fst) $ sortBy (compare `on` fst) xs
nubBy
的答案是最简单的方法,我只想提供其他实现方式。Data.Function
的on
,使得分组和排序更加容易。与RoadRunner的答案类似,您可以将那个seen
实现为一个Set
,甚至可以将其包装在State
单子中。
module Main where
-- mtl
import Control.Monad.State (State, get, put, evalState)
-- containers
import Data.Set (Set, empty, insert, member)
removeDuplicates :: Ord a => [(a, b)] -> [(a, b)]
removeDuplicates xs = evalState (go xs) (empty, [])
where
go [] = do
(_, ys) <- get
return $ reverse ys
go (x:xs) = do
(s, ys) <- get
case member (fst x) s of
True -> go xs
False -> do
put $ (insert (fst x) s, x:ys)
go xs
main :: IO ()
main = do
let testData = [('a', 3),( 'b', 2),('a', 1),('b', 4)]
print $ removeDuplicates testData
就像RoadRunner的答案一样,再次强调--使用nubBy
来完成这个任务。这种方法只适合作为一次练习。
λ:> import Data.List (nubBy)
λ:> import Data.Function (on)
λ:> nubBy ((==) `on` snd) [('a',1),('b',2),('b',3)]
[('a',1),('b',2),('b',3)]
(==) `on` fst
,其中的on
是来自于Data.Function
。 - 4castlenubBy
的lambda类型类似于a -> a -> Boolean
。这意味着它是一个具有两个相同类型参数并返回True或False的函数。我选择表示两个元组,形式为(x, _)
,表示一个带有x
和“任何东西”的元组。在Haskell中,x'
或“x prime”只是一种方便的写法 - 我们可以用y
代替x'
,它只是表示不同的变量。 - גלעד ברקן