在哈斯克尔分裂范围

时间:2016-02-12 15:08:18

标签: haskell

给出如下列表:

[1, 2, 2, 6, 7, 8, 10, 11, 12, 15]

将其拆分为温和增加范围(可能相等):

[[1, 2, 2], [6, 7, 8], [10, 11, 12], [15]]

我尝试使用递归方法:

splitRanges [] = [[]]
splitRanges (x:y:xs) 
  | x `elem` [y, y + 1] = [x, y] : splitRanges xs
  | otherwise = xs

因此,如果项目在融合它们之后是小于或等于项目的项目。但它说我试图建立一个无限类型:

Occurs check: cannot construct the infinite type: a0 = [a0]
Expected type: [[a0]]
  Actual type: [a0]
  

但是[单调的事实]与列表的拆分方式有什么关系呢?

严格增加会产生不同的结果。

  

或者你真的想说点别的吗?

我希望我不是。

  

列表总是单调吗?

不,拆分单调列表意味着将其分成一个子列表。

  

如果没有,那应该如何影响结果?

如果不是单调的话,你会有很多子列表。

  

总是棕色成三个一组吗?

不,这些组可能包含n个元素。

  

更多例子会很好

splitRanges [1, 3] == [[1], [3]]
splitRanges [1, 2, 5] == [[1, 2], [3]]
splitRanges [0, 0, 1] == [[0, 0, 1]]
splitRanges [1, 5, 7, 9] == [[1], [5], [7], [9]]

我很欣赏暗示而不是完整的答案,因为我想提高自己,复制粘贴并不是改进。

3 个答案:

答案 0 :(得分:2)

尝试将问题分解为更易于管理的部分。

首先,您如何从列表的开头分割出一个平淡增长的范围?让我猜猜应该是splitOne :: [Integer] -> ([Integer], [Integer])

其次,如何重复将splitOne应用于剩余列表?尝试使用splitOne实现splitMany :: [Integer] -> [[Integer]]

对于splitOne,您应该尝试找到什么?第一个分裂的位置。什么是"分拆职位"?让我们说清楚。

split    0     1      2      3      4      …
list  [  | x1, |  x2, |  x3, |  x4, |  x5, …]

因此,0处的拆分为([], [x1,x2,x3,x4,x5,…]),3处的拆分为([x1,x2,x3],[x4,x5,…])。您可以在拆分位置和拆分列表之间看到什么关系?

如何确定列表的第一个拆分位置?可以说它实现为firstSplitPos :: [Integer] -> Integer。空列表的第一个拆分位置是什么?

现在可以使用firstSplitPos实现splitOne吗?

一个可能的答案

-- What are the adjacencies for:
--   1) empty lists?
--   2) lists with one element?
--   3) lists with more than one element?
--
-- Bonus: rewrite in point-free form using <*>
--
adjacencies :: [a] -> [(a,a)]
adjacencies xxs = zip xxs (drop 1 xxs)

-- Bonus: rewrite in point-free form
--
withIndices :: [a] -> [(Int,a)]
withIndices xxs = zip [0..] xxs

-- This is the most involved part of the answer. Pay close
-- attention to:
--   1) empty lists
--   2) lists with one element
--   3) lists which are a blandly increasing sequence
--
firstSplitPos :: (Eq a, Num a) => [a] -> Int
firstSplitPos xxs = maybe (length xxs) pos (find q searchList)
  where q (_,(a,b)) = a /= b && a + 1 /= b
        searchList  = withIndices (adjacencies xxs)
        -- Why is the split position one more than the index?
        pos (i,_)   = i + 1
--

-- Bonus: rewrite in point-free form using <*>
--
splitOne :: (Eq a, Num a) => [a] -> ([a],[a])
splitOne xxs = splitAt (firstSplitPos xxs) xxs

splitMany :: (Eq a, Num a) => [a] -> [[a]]
-- What happens if we remove the case for []?
splitMany []  = []
splitMany xxs = let (l, r) = splitOne xxs in l : splitMany r

另一种方法

这是我对Carsten's solution的解释。它已经很简洁,但我选择了一个不使用2元组的变体。

我们知道Haskell列表是归纳定义的。为了证明这一点,我们可以定义一个等效的数据类型。

data List a = Cons a (List a) -- Cons = (:)
            | Nil             -- Nil  = []

然后问一个问题:我们可以在列表中使用归纳法来解决问题吗?如果是这样,我们只需要解决两种情况:Cons和Nil。 foldr的类型签名向我们展示了:

foldr ::     (a -> b -> b) -- Cons case
          -> b             -- Nil case
          -> [a]           -- The list 
          -> b             -- The result

如果列表是Nil怎么办?然后,唯一温和增加的序列是空序列。因此:

nilCase = [[]]

我们可能需要nilCase = []代替,因为这似乎也是合理的 - 即没有平淡增长的序列。

现在你需要一些想象力。在Cons案例中,我们一次只能查看一个新元素。使用这个新元素,我们可以决定它是属于右邻序列还是开始一个新序列。

右边相邻是什么意思?在[5,4,1,2,2,7]中,1属于右邻序列[2,2]

这看起来怎么样?

-- The rest of the list is empty
consCase new []      = [new] : []

-- The right-adjacent sequence is empty
consCase new ([]:ss) = [new] : ss

-- The right-adjacent sequence is non-empty
-- Why `new + 1 == x` and not `new == x + 1`?
consCase new sss@(xxs@(x:_):ss)
  | new == x || new + 1 == x = (new:xxs):ss
  | otherwise                = [new]:sss

现在我们解决了Nil案和Cons案,我们已经完成了!

splitRanges = foldr consCase nilCase

答案 1 :(得分:1)

我希望你不介意破坏它的一部分,但是当评论正在讨论你想要的东西时(我希望我已经得到它)也许你对另一种可能的解决方案感兴趣?

我不想破坏这一切,但我认为你可以轻松解决这个问题:

blandly :: (Ord a, Num a) => [a] -> [[a]]
blandly = g . foldr f ([],[])
  where f x ([],xss)       = ([x],xss)
        f x (y:ys,xss)
          | abs (x-y) <= 1 = undefined
          | otherwise      = undefined
        g (ys,xss)         = undefined

你只需要填写undefined个洞

这个想法只是从右边折叠列表,在元组的第一项中累积你的内部列表,只要元素不是很远;如果它们是:将它推到第二个项目。

如果操作正确,它将产生:

λ> blandly [1,3]
[[1],[3]]
λ> blandly [1,2,5]
[[1,2],[5]]
λ> blandly [0,0,1]
[[0,0,1]]
λ> blandly [1,5,7,9]
[[1],[5],[7],[9]]

这似乎是你想要的

1小时后 - 我想我可以发布我的解决方案 - 如果你不想被宠坏就停止阅读

blandly :: (Ord a, Num a) => [a] -> [[a]]
blandly = uncurry (:) . foldr f ([],[])
  where f x ([],xs) = ([x],xs)
        f x (y:ys,xs)
          | abs (x-y) <= 1 = (x:y:ys,xs)
          | otherwise     = ([x],(y:ys):xs)

也许我在这里有一点误会(示例没有指明) - 但如果你只想要单调增加内部列表,你只需要更改abs部分:

blandly :: (Ord a, Num a) => [a] -> [[a]]
blandly = uncurry (:) . foldr f ([],[])
  where f x ([],xss)    = ([x],xss)
        f x (y:ys,xss)
          | 0 <= y-x
            && y-x <= 1 = (x:y:ys,xss)
          | otherwise   = ([x],(y:ys):xss)

答案 2 :(得分:1)

编写函数来获取谓词,而不是将拆分条件写入函数本身,这将是有用的和惯用的:

splitBy2 :: (a -> a -> Bool) -> [a] -> [[a]]
splitBy2 ok xs = snd $ f xs [] []
  where f (a:b:xs) acc_list acc_out_lists | ok a b = ...