以下函数不在标准的Haskell库中,所以我写了它:
paginate _ [] = []
paginate n xs = let (h, t) = splitAt n xs in h : paginate n t
然后我想在没有显式递归的情况下重写它,但仍然很容易理解。
我尝试了fix
版本,但结果并不令人满意:
paginate = fix go
where
go _ _ [] = []
go v n xs = let (h, t) = splitAt n xs in h : v n t
是否有更简单的解决方案使用Prelude功能?
答案 0 :(得分:14)
当我看到这种问题时,我喜欢考虑你想要的功能的“形状”。在这种情况下,您将从列表开始并生成列表列表。这是一个特殊情况,从单个值开始并生成一个列表,对应于展开。因此,如果您不介意导入Data.List
,则可以使用unfoldr
整齐地编写函数。
让我们看一下如何逐步推导出来。首先,如上所述,您只需了解unfoldr
并了解它可以应用。这是一些经验(比如阅读这样的答案:))。
接下来,我们来看一下unfoldr
的类型:
Prelude Data.List> :t unfoldr
unfoldr :: (b -> Maybe (a, b)) -> b -> [a]
我们的想法是,我们从单个“内核”值(b
)开始,并重复应用步进函数(b -> Maybe (a, b)
),直到我们点击Nothing
。我们的起始值是我们要分成块的列表,因此我们不需要在那里进行任何更多的处理。这意味着我们的最后一步是实现步骤功能。
由于我们从一系列值开始,因此我们可以将b
替换为[n]
;此外,由于我们希望在最后生成列表列表,因此我们可以将[a]
替换为[[n]]
,因此a
替换为[n]
。这为我们提供了我们必须为步骤函数实现的最终类型:
[n] -> Maybe ([n], [n])
嘿,这看起来与splitAt
的类型非常相似!
splitAt :: Int -> a -> ([a], [a])
事实上,我们可以将splitAt
的结果包装在Just
中并满足我们的类型。但是这留下了一个非常重要的部分:最终Nothing
告诉unfoldr
停止!所以我们的辅助函数需要[]
的额外基本情况才能返回正确的结果。
将所有内容放在一起为我们提供了最终功能:
import Data.List (unfoldr)
paginate n = unfoldr go
where go [] = Nothing -- base case
go ls = Just (splitAt n ls)
我认为最终结果相当不错,但我不确定它是否比递归版本更具可读性。
如果您不介意启用扩展程序(LambdaCase
),则可以通过避免命名go
来更轻松地重写它:
paginate n = unfoldr $ \case
[] -> Nothing
ls -> Just (splitAt n ls)
答案 1 :(得分:2)
仅供参考,继Tikhon Jelvis的回答之后,我最终得到了:
boolMaybe p f x = if p x then Just (f x) else Nothing
groupWith = unfoldr . boolMaybe null
paginate = groupWith . splitAt
对我来说有点太混淆了。回到递归版本。
答案 2 :(得分:2)
以下是Tikhon Jelvis'代码:
import Data.Maybe
import Data.List
import Control.Applicative
paginate n = unfoldr $ \xs -> listToMaybe xs *> Just (splitAt n xs)
或点免费:
paginate n = unfoldr $ (*>) <$> listToMaybe <*> Just . splitAt n
答案 3 :(得分:0)
import Data.List
chunks n = takeWhile (not . null) . unfoldr (Just . splitAt n)
但对我而言,新的冠军是
import Data.Maybe -- additional imports
import Control.Applicative
chunks n = unfoldr $ (<$) . splitAt n <*> listToMaybe
-- \xs -> splitAt n xs <$ listToMaybe xs
(通过@ user3237465调整代码from this comment)。
这里使用的位是:
unfoldr :: (b -> Maybe (a, b)) -> b -> [a]
(f <*> g) x = f x (g x) -- S combinator
x <$ c = fmap (const x) c -- "mapped into"
listToMaybe xs = head (map Just xs ++ [Nothing])
另一种类似的写作方式是
import Control.Monad -- an additional import
chunks n = unfoldr $ listToMaybe . join ((<$) . splitAt n)
-- \xs -> listToMaybe (splitAt n xs <$ xs)
使用monadic join
作为函数,
join f x = f x x -- W combinator