[英]How do you convert a list of numbers into a list of ranges in haskell?
假设你有一个数字列表, [1,2,3,5,6,7,8,9,11,12,15,16,17]
并且您想要一个将其作为输入并返回类似内容的函数
[[1,3],[5,9],[11,12],[15,17]]
或者[(1,3), (5,9), (11,12), (15,17)]
这将如何完成? 我在网上找到的所有解决方案都非常长而且非常复杂,这对于像 haskell 这样的函数式语言来说似乎是一个简单的问题
所以我们有一个数字列表,
xs = [1,2,3,5,6,7,8,9,11,12,14,16,17] -- 14 sic!
我们把它变成一个段列表,
ys = [[x,x+1] | x <- xs]
-- [[1,2], [2,3], [3,4], [5,6], ..., [11,12], [12,13], [14,15], [16,17], [17,18] ]
我们加入感人的片段,
zs = foldr g [] ys
-- [[1,4], [5,10], [11,13], [14,15], [16,18]]
where
g [a,b] [] = [[a,b]]
g [a,b] r@([c,d]:t) | b==c = [a,d]:t
| otherwise = [a,b]:r
我们从每个段的结束值中减去1 ,
ws = [[a,b-1] | [a,b] <- zs]
-- [[1,3], [5,9], [11,12], [14,14], [16,17]]
总之我们得到
ranges :: (Num t, Eq t) => [t] -> [[t]]
ranges = map (\[a,b] -> [a,b-1]) . foldr g [] . map (\x -> [x,x+1])
where
g [a,b] [] = [[a,b]]
g [a,b] r@([c,d]:t) | b==c = [a,d]:t
| otherwise = [a,b]:r
简单明了。
编辑:或者,要适当地懒惰,
where
g [a,b] r = [a,x]:y
where
(x,y) = case r of ([c,d]:t) | b==c -> (d,t) -- delay forcing
_ -> (b,r)
更新:正如dfeuer 所指出的, (a,a)
类型比[a,a]
更好。 无论[P,Q]
出现在此代码中的何处,都将其替换为(P,Q)
。 这将改进代码,零成本的可读性。
因此,人们可能会像@Will Ness关于有状态折叠的想法那样做,或者在相同的答案下进行我的想法。 所有的解释都可以在那里找到。 此外,如果您感到好奇并想阅读更多相关信息,请查看Haskell Continuation Passing Style 页面。 我目前正试图以这样一种方式来实现这一点,即我们可以以有状态的方式拥有foldr1
的变体。 A foldS :: Foldable t => (a -> a -> b) -> ta -> b
。 然而,这仍然不是一般的有状态折叠。 它只是针对这个问题量身定制的。
ranges :: (Ord a, Num a) => [a] -> [[a]]
ranges xs = foldr go return xs $ []
where
go :: (Ord a, Num a) => a -> ([a] -> [[a]]) -> ([a] -> [[a]])
go c f = \ps -> let rrs@(r:rs) = f [c]
in case ps of
[] -> [c]:r:rs
[p] -> if p + 1 == c then rrs else [p]:(c:r):rs
*Main> ranges [1,2,3,5,6,7,8,9,11,12,15,16,17]
[[1,3],[5,9],[11,12],[15,17]]
我没有时间测试任何边缘情况。 欢迎所有建议。
与您提供的第一个表示相比,我绝对更喜欢替代表示。
ranges :: (Num a, Eq a) => [a] -> [(a,a)]
ranges [] = []
ranges (a : as) = ranges1 a as
-- | A version of 'ranges' for non-empty lists, where
-- the first element is supplied separately.
ranges1 :: (Num a, Eq a) => a -> [a] -> [(a,a)]
ranges1 a as = (a, b) : bs
where
-- Calculate the right endpoint and the rest of the
-- result lazily, when needed.
(b, bs) = finish a as
-- | This takes the left end of the current interval
-- and the rest of the list and produces the right endpoint of
-- that interval and the rest of the result.
finish :: (Num a, Eq a) => a -> [a] -> (a, [(a, a)])
finish l [] = (l, [])
finish l (x : xs)
| x == l + 1 = finish x xs
| otherwise = (l, ranges1 x xs)
为了解决罗塞塔代码问题在上面的评论链接,这是不是真的挺的最佳表征。 稍后我将尝试解释如何更精确地匹配表示。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.