Haskell 有没有一种非递归的方式来写这个;“分页”;功能?
以下函数不在标准Haskell库中,因此我编写了它:Haskell 有没有一种非递归的方式来写这个;“分页”;功能?,haskell,Haskell,以下函数不在标准Haskell库中,因此我编写了它: paginate _ [] = [] paginate n xs = let (h, t) = splitAt n xs in h : paginate n t 然后,我想重写它,不使用显式递归,但仍然很容易理解 我尝试了修复版本,但效果不太理想: paginate = fix go where go _ _ [] = [] go v n xs = let (h, t) = splitAt n xs in h : v n
paginate _ [] = []
paginate n xs = let (h, t) = splitAt n xs in h : paginate n t
然后,我想重写它,不使用显式递归,但仍然很容易理解
我尝试了修复
版本,但效果不太理想:
paginate = fix go
where
go _ _ [] = []
go v n xs = let (h, t) = splitAt n xs in h : v n t
有没有使用Prelude函数的更简单的解决方案?当我看到这类问题时,我喜欢思考您想要的函数的“形状”。在本例中,您将从一个列表开始,并生成一个列表列表。这是从单个值开始并生成列表的特殊情况,该列表对应于展开。因此,如果您不介意导入
Data.List
,您可以使用unfover
整洁地编写函数
让我们看看如何一步一步地推导出这个公式。首先,如上所述,您只需了解unfover
,并查看它是否适用。这就是一点经验(比如阅读像这样的答案:)的来源
接下来,我们看一下展开器的类型:
Prelude Data.List> :t unfoldr
unfoldr :: (b -> Maybe (a, b)) -> b -> [a]
我们的想法是从一个“内核”值(b
)开始,然后重复应用一个步长函数(b->可能(a,b)
),直到我们点击Nothing
。我们的起始值是我们想要分割成块的列表,因此我们不需要在那里进行任何处理。这意味着我们的最后一步是实现step函数
因为我们从一个值列表开始,我们可以用[n]
替换b
;此外,由于我们希望在最后生成一个列表列表,因此可以将[a]
替换为[[n]]]
,从而将a
替换为[n]
。这为我们提供了阶跃函数必须实现的最终类型:
[n] -> Maybe ([n], [n])
import Data.List (unfoldr)
paginate n = unfoldr go
where go [] = Nothing -- base case
go ls = Just (splitAt n ls)
嘿,这看起来非常类似于splitAt
的类型
splitAt :: Int -> a -> ([a], [a])
事实上,我们可以将splitAt
的结果包装在just
中,并满足我们的类型。但这遗漏了一个非常重要的部分:最后的Nothing
命令unbover
停止!因此,我们的helper函数需要一个额外的基本情况,以便[]
返回正确的结果
把所有这些放在一起,我们就有了最终的功能:
[n] -> Maybe ([n], [n])
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)
仅供参考,根据Tikhon Jelvis的回答,我最终得出以下结论:
boolMaybe p f x = if p x then Just (f x) else Nothing
groupWith = unfoldr . boolMaybe null
paginate = groupWith . splitAt
这对我来说有点太模糊了。回到递归版本。这里是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
paginate n=unfover$(*>)列表可能只是。斯普利塔特n
总是有这样的情况:
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
Data.List.Split
来自Split
包的chunksOf
,如果您希望它已经实现的话。这里有一个可爱的小技巧:如果您启用MonadComprehensions
,您可以对其他类型使用列表理解语法,包括Maybe
。有了这个扩展,您可以用[fx | px]
替换,如果px,那么就用fx代替,这是一个简洁的习惯用法。为什么要在右侧使用
,在左侧使用
?如果左侧也有
,则更明显的是
属于应用程序的函数。-很好的技巧,使用*>
对if
进行编码。顺便说一句,严格的数据。这个列表版本只是takeWhile(不是.null)。展开器(Just.splitAt n)
@Will Ness,我更容易测试减少f g1 g2。。。gn在我的脑海里,而不是f。G1G2。。。gn
。但这是因为我知道,可能使用了什么应用程序实例。我认为,展开$()常量。splitAt n listToMaybe
比Unfover$()更具可读性。康斯特。splitAt n listtomabe
。我确实想区分一个fmap
和另一个,当然是YMMV。顺便说一句,你的最后一个也是(
join f x = f x x -- W combinator