【发布时间】:2010-09-03 04:10:27
【问题描述】:
我正在尝试编写一个函数,该函数接受一个谓词 f 和一个列表,并返回一个列表,该列表包含满足 f 且具有保留顺序的所有项目。诀窍是只使用高阶函数 (HoF),没有递归,没有推导,当然也没有过滤器。
【问题讨论】:
我正在尝试编写一个函数,该函数接受一个谓词 f 和一个列表,并返回一个列表,该列表包含满足 f 且具有保留顺序的所有项目。诀窍是只使用高阶函数 (HoF),没有递归,没有推导,当然也没有过滤器。
【问题讨论】:
你可以用foldr表达filter:
filter p = foldr (\x xs-> if p x then x:xs else xs) []
【讨论】:
我认为你可以这样使用map:
filter' :: (a -> Bool) -> [a] -> [a]
filter' p xs = concat (map (\x -> if (p x) then [x] else []) xs)
你看到了吗?将列表转换为列表列表,如果你想要的元素没有通过p,它会变成一个空列表
filter' (> 1) [1 , 2, 3 ] 将是:concat [ [], [2], [3]] = [2,3]
在prelude 中有 concatMap 使代码更简单:P
代码应如下所示:
filter' :: (a -> Bool) -> [a] -> [a]
filter' p xs = concatMap (\x -> if (p x) then [x] else []) xs
按照 sclv 的建议,使用 foldr 可以通过以下方式完成:
filter'' :: (a -> Bool) -> [a] -> [a]
filter'' p xs = foldr (\x y -> if p x then (x:y) else y) [] xs
【讨论】:
concat,不如使用catMaybes :: [Maybe a] -> [a]。
你这样做显然是为了学习,所以让我向你展示一些很酷的东西。首先,让我们重新思考一下,过滤器的类型是:
filter :: (a -> Bool) -> [a] -> [a]
有趣的是最后一位[a] -> [a]。它分解一个列表并建立一个新列表。
递归模式在 Haskell(和其他函数式语言)中非常普遍,以至于人们已经为其中一些模式想出了名称。最简单的是变形,它是双重变形。最后我会告诉你这与你的直接问题有什么关系。
必备知识FTW!
Nothing 的类型是什么?启动 GHCI,它说Nothing :: Maybe a,我不会不同意。 Just Nothing 呢?再次使用 GHCI,它会显示 Just Nothing :: Maybe (Maybe a),这也是完全有效的,但是这个 Nothing 嵌入在任意数量甚至无限数量的 Justs 中的值呢?即,这个值的类型是什么:
foo = Just foo
Haskell 实际上不允许这样的定义,但稍作调整我们就可以做出这样的类型:
data Fix a = In { out :: a (Fix a) }
just :: Fix Maybe -> Fix Maybe
just = In . Just
nothing :: Fix Maybe
nothing = In Nothing
foo :: Fix Maybe
foo = just foo
哇,够近了!使用相同的类型,我们可以创建任意嵌套的nothings:
bar :: Fix Maybe
bar = just (just (just (just nothing)))
旁白:Peano 算术有人吗?
fromInt :: Int -> Fix Maybe
fromInt 0 = nothing
fromInt n = just $ fromInt (n - 1)
toInt :: Fix Maybe -> Int
toInt (In Nothing) = 0
toInt (In (Just x)) = 1 + toInt x
这个Fix Maybe 类型有点无聊。这是一个定点为列表的类型:
data L a r = Nil | Cons a r
type List a = Fix (L a)
这种数据类型将有助于演示一些递归模式。
有用的事实:Cons a r 中的 r 称为递归站点
变质是一种破坏结构的操作。列表的 catamorphism 更为人所知的是折叠。现在变质的类型可以这样表示:
cata :: (T a -> a) -> Fix T -> a
可以等价写成:
cata :: (T a -> a) -> (Fix T -> a)
或者用英文写成:
您给我一个将数据类型化简为值的函数,我将给您一个将其定点化简为值的函数。
其实我撒谎了,类型真的是:
cata :: Functor T => (T a -> a) -> Fix T -> a
但是原理是一样的。请注意,T 仅根据递归站点的类型进行参数化,因此 Functor 部分实际上是在说“给我一种操作所有递归站点的方法”。
那么cata可以定义为:
cata f = f . fmap (cata f) . out
这是相当密集的,让我详细说明。这是一个三步过程:
Fix t,这是一个很难玩的类型,我们可以通过应用out(来自Fix的定义)给我们一个t (Fix t)来使它更容易。
t (Fix t) 转换为t a,我们可以通过一厢情愿的方式使用fmap (cata f);我们假设我们将能够构造cata。t a,我们想要一个a,所以我们只使用f。之前我说过列表的变态称为折叠,但cata 目前看起来不像折叠。让我们根据cata 定义一个折叠函数。
回顾一下,列表类型是:
data L a r = Nil | Cons a r
type List a = Fix (L a)
这需要是一个有用的函子,这是直截了当的:
instance Functor (L a) where
fmap _ Nil = Nil
fmap f (Cons a r) = Cons a (f r)
所以专门cata 我们得到:
cata :: (L x a -> a) -> List x -> a
我们几乎就在那里:
construct :: (a -> b -> b) -> b -> L a b -> b
construct _ x (In Nil) = x
construct f _ (In (Cons e n)) = f e n
fold :: (a -> b -> b) -> b -> List a -> b
fold f m = cata (construct f m)
好的,变质一次将数据结构分解为一层。
列表上的变形是展开。展开比折叠对偶少为人知,它们的类型如下:
unfoldr :: (b -> Maybe (a, b)) -> b -> [a]
正如您所见,变形构建了数据结构。这是更通用的类型:
ana :: Functor a => (a -> t a) -> a -> Fix t
这应该立即看起来很熟悉。这个定义也让人联想到变质。
ana f = In . fmap (ana f) . f
这只是同样的事情颠倒过来。从ana 构造unfold 甚至比从cata 构造fold 还要简单。注意Maybe (a, b) 和L a b 之间的结构相似性。
convert :: Maybe (a, b) -> L a b
convert Nothing = Nil
convert (Just (a, b)) = Cons a b
unfold :: (b -> Maybe (a, b)) -> b -> List a
unfold f = ana (convert . f)
filter 是一个有趣的函数,因为它可以从变形或变形构造。这个问题的其他答案(迄今为止)也使用了变态,但我会用两种方式定义它:
filter p = foldr (\x xs -> if p x then x:xs else xs) []
filter p =
unfoldr (f p)
where
f _ [] =
Nothing
f p (x:xs) =
if p x then
Just (x, xs)
else
f p xs
是的,是的,我知道我在展开版本中使用了递归定义,但请原谅我,我教了你很多理论,而且无论如何 filter 不是递归的。
【讨论】:
我建议你看看foldr。
【讨论】:
那么,是否允许 if 和空列表?
filter = (\f -> (>>= (\x -> if (f x) then return x else [])))
【讨论】:
对于整数列表
filter2::(Int->Bool)->[Int]->[Int]
filter2 f []=[]
filter2 f (hd:tl) = if f hd then hd:filter2 f tl
else filter2 f tl
【讨论】:
我忍不住用另一种方式回答这个问题,这次完全没有递归。
-- This is a type hack to allow the y combinator to be represented
newtype Mu a = Roll { unroll :: Mu a -> a }
-- This is the y combinator
fix f = (\x -> f ((unroll x) x))(Roll (\x -> f ((unroll x) x)))
filter :: (a -> Bool) -> [a] -> [a]
filter =
fix filter'
where
-- This is essentially a recursive definition of filter
-- except instead of calling itself, it calls f, a function that's passed in
filter' _ _ [] = []
filter' f p (x:xs) =
if p x then
(x:f p xs)
else
f p xs
【讨论】: