Haskell `filterM`用于“Data.Map.Map”或“Data.Set.Set”等容器`

Haskell `filterM`用于“Data.Map.Map”或“Data.Set.Set”等容器`,haskell,Haskell,简言之:您将如何过滤单子上的Map或Set元素 Haskell中的谓词 我可以想出两种可能的方法: a) 往返于列表和过滤器项之间(可能效率不高): b) 如果谓词本身不是一元的,但例如是与 处于状态的状态monad;然后我们可以使用Data.Map.filter(相当特殊的 案例): filterMapM2::(Monad m,Ord k)=>(v->v->Bool)->m.Map k v->StateT v m(m.Map k v) 过滤器PM2 f m=do 这两种方法具有完全不同的语义和

简言之:您将如何过滤单子上的
Map
Set
元素 Haskell中的谓词

我可以想出两种可能的方法:

a) 往返于列表和过滤器项之间(可能效率不高):

b) 如果谓词本身不是一元的,但例如是与 处于
状态的状态
monad;然后我们可以使用
Data.Map.filter
(相当特殊的 案例):

filterMapM2::(Monad m,Ord k)=>(v->v->Bool)->m.Map k v->StateT v m(m.Map k v)
过滤器PM2 f m=do

这两种方法具有完全不同的语义和效率含义

当您往返于一个列表时,您允许过滤受到之前所有比较的影响,因此最终结果可能会受到访问元素的顺序的影响。然而,在第二种情况下,过滤是一个纯函数,因此无论比较的顺序如何,答案都是相同的

例如,回答问题的用户可能希望保持偶数和奇数的数量大致相同,因此用户是否喜欢特定的数字将取决于之前提供的所有数字

另一方面,这里是
M.filter
的代码:

-- | /O(n)/. Filter all elements that satisfy the predicate.
filter :: (a -> Bool) -> Set a -> Set a
filter _ Tip = Tip
filter p (Bin _ x l r)
    | p x       = link x (filter p l) (filter p r)
    | otherwise = merge (filter p l) (filter p r)
重要的是要注意代码的形状——生成的树结构很大程度上受原始树结构的影响。也许只需要少量的再平衡。这可能比使用
M.fromList
从零知识重建树要高效得多

结果是,在
filterM1
情况下,您应该关注进行比较的顺序。可能
M.toList
给出了一个可接受的顺序,或者您希望
相反。M.toList
,或

在第二种情况下,您无需在意,因此可以让
M.filter
完成所有工作,并利用其数据结构知识

更新:我刚刚注意到
M.toAscList
M.fromAscList
函数,所以这个版本的
filterMapM1
可能稍微高效一些:

filterMapM1 :: (Monad m, Ord k) => (v -> m Bool) -> M.Map k v -> m (M.Map k v)
filterMapM1 f m = liftM M.fromList $ filterM (f.snd) $ M.toList m
filterMapM1 f m = liftM M.fromAscList $ filterM (f.snd) $ M.toAscList m

目前我认为没有一个更通用的抽象可以直接支持
filterM
。Traversable不会,因为
traverse
无法更改结构的形状。我认为,从技术上讲,使用lens可以做到这一点,但文档表明,您确实不应该这样做,而且我认为它可以通过另一个结构进行往返

您可以使用类似的内容(不可编译,因为
filter
不是类成员):

filterM::(可遍历c,Monad m,Applicative m)=>(a->m Bool)->ca->m(ca)
filterM p=fmap snd。过滤器fst。遍历p'
其中p'x=(,x)px

这是否比通过列表往返更高效,可能取决于结构。可以说,它没有那么干净。

浏览一个列表并没有那么糟糕;在树的情况下,如果删除一些顶级节点,则很可能必须重建结构@d8d0d65b3f7cf42您能详细说明一下吗?我不知道如何使用这些来直接删除元素。脑海中浮现的唯一方法是通过
可能
的中间步骤。例如,在函数
f::a->m(可能是a)
上使用
mapM
,然后过滤掉所有
Nothing
s,然后使用
fromJust
去除
Maybe
。这就是你的想法吗?我很抱歉这不是一条直线。这是关于编写更短/更好的代码(还是关于效率)?我确信有一些构造涉及到使用
sans
来移除元素(从
Data.Map
实例中)的镜头化遍历。@d8d0d65b3f7cf42这是关于编写更短/更好的代码(还是关于效率)?我在哈斯克尔方面不是很有经验。所以,你可以说我在寻找合适的人选™ 对。但我不确定两次遍历数据(“toMaybe”、“fromJust”)是否比遍历列表的往返更有效。一个使用sans的镜头遍历例子:我不知道镜头包。但是,总的来说,这似乎是一个非常有用的工具。谢谢你指给我看。我想我找到了这个周末要做的事我做了一个基准测试(见更新),结果显示
toList
,和
toaslist
做得同样好;而且比在
Map
的内部表示上的实现要好得多。我不知道为什么。关于不同的语义。当然,谢谢你指出这一点。不过,我会说,如果顺序很重要,那么就不应该使用地图,而应该立即使用有序的东西,比如列表。我看了lense一眼,但没有找到一个很好的方法。正如你所说,这似乎不是镜头的主要设计目的。我做了一个小的基准测试(见更新),通过列表的往返过程做得很好。
-- | /O(n)/. Filter all elements that satisfy the predicate.
filter :: (a -> Bool) -> Set a -> Set a
filter _ Tip = Tip
filter p (Bin _ x l r)
    | p x       = link x (filter p l) (filter p r)
    | otherwise = merge (filter p l) (filter p r)
filterMapM1 f m = liftM M.fromAscList $ filterM (f.snd) $ M.toAscList m
filterM :: (Traversable c, Monad m, Applicative m) => (a -> m Bool) -> c a -> m (c a)
filterM p = fmap snd . filter fst . traverse p'
  where p' x = (,x) <$> p x