是否有一种非递归方式来编写这个“paginate”函数?

时间:2014-12-17 22:53:47

标签: haskell

以下函数不在标准的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功能?

4 个答案:

答案 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