如何在某些点将列表拆分为子列表?

时间:2013-08-01 11:02:41

标签: haskell

如何手动将[1,2,4,5,6,7]拆分为[[1],[2],[3],[4],[5],[6],[7]]?手动意味着不使用休息。

然后,如何根据谓词将列表拆分为子列表?像这样

f even [[1],[2],[3],[4],[5],[6],[7]] == [[1],[2,3],[4,5],[6,7]]
PS:这不是家庭作业,我已经花了好几个小时自己解决这个问题。

3 个答案:

答案 0 :(得分:3)

要回答您的第一个问题,这是一个基于元素的转换而不是拆分。执行此操作的适当功能是

map :: (a -> b) -> [a] -> [b]

现在,您需要一个函数(a -> b) b[a],因为您希望将元素转换为包含相同类型的单例列表。这是:

mkList :: a -> [a]
mkList a = [a]

所以

map mkList [1,2,3,4,5,6,7] == [[1],[2],...]

关于你的第二个问题:如果你不允许(作业?)使用break,你是否可以使用takeWhiledropWhile来形成结果的两半break

无论如何,对于没有它们的解决方案(“手动”),只需使用累加器进行简单的递归:

f p [] = []
f p (x:xs) = go [x] xs
    where go acc [] = [acc]
          go acc (y:ys) | p y = acc : go [y] ys
                        | otherwise = go (acc++[y]) ys

这将以递归方式遍历整个列表尾部,始终记住当前子列表的内容,以及何时到达适用p的元素,输出当前子列表并开始新的子列表。

请注意,首先接收[x]而不是[]以提供第一个元素已满足p x的情况,并且我们不希望输出空的第一个子列表。

此外,它还会在原始列表([1..7])上运行,而不是[[1],[2]...]。但你也可以在转换过的那个上使用它:

> map concat $ f (odd . head) [[1],[2],[3],[4],[5],[6],[7]]
[[1,2],[3,4],[5,6],[7]]

答案 1 :(得分:2)

首先:

map (: [])

第二

f p xs = 
  let rs = foldr (\[x] ~(a:r) -> if (p x) then ([]:(x:a):r) else ((x:a):r))
           [[]] xs
  in case rs of ([]:r) -> r ; _ -> rs

foldr的操作很容易可视化:

foldr g z [a,b,c, ...,x] = g a (g b (g c (.... (g x z) ....)))

因此在编写组合函数时,期望有两个参数,第一个是列表的“当前元素”,第二个是“处理其余的结果”。这里,

g [x] ~(a:r) | p x    = ([]:(x:a):r)
             | otherwise = ((x:a):r)

因此,从右侧开始可视化,它只是添加到最新的子列表中,并在必要时打开一个新的子列表。但由于列表实际上是从左侧访问的,因此我们使用惰性模式~(a:r)保持惰性。现在它甚至可以在无限列表上运行:

Prelude> take 9 $ f odd $ map (:[]) [1..]
[[1,2],[3,4],[5,6],[7,8],[9,10],[11,12],[13,14],[15,16],[17,18]]

第一个参数的模式反映了预期输入列表的特殊结构。

答案 2 :(得分:2)

首先,你可以使用列表理解:

>>> [[x] | x <- [1,2,3,4,5,6]]
[[1], [2], [3], [4], [5], [6]]

对于第二个问题,您可以使用split包提供的Data.List.Split模块:

import Data.List.Split

f :: (a -> Bool) -> [[a]] -> [[a]]
f predicate = split (keepDelimsL $ whenElt predicate) . concat

这是列表的第一个concat,因为split中的函数适用于列表而不是列表列表。生成的单个列表是使用拆分包中的函数再次拆分。