如何在 Haskell 中的特定条件下拆分列表?
How do I split a list on certain conditions in Haskell?
作为一个编程练习,我正在尝试在 Haskell 中构建一个函数,其中给定一个列表,只要重复一个元素,它就会拆分列表。例如,[1,2,3,3,4,5] 会拆分为 [[1,2,3],[3,4,5]]。我的第一个想法是将列表拆分为具有单个元素的列表列表,其中 [1,2,3,3,4,5] 将变为 [[1],[2],[3],[3], [4],[5]] 然后仅当被比较的元素不相等时才合并列表,但是实现这个对我来说很头疼,因为我对 Haskell 很陌生,递归总是给我带来麻烦.我认为我用来组合列表的函数有问题,它只会 return 一个列表,其中所有被分解的元素组合在一起,其中 [1,2,3,3,4 ,5] 变为 [[1],[2],[3],[3],[4],[5]] 但我的 split_help 函数会将其转换为 [[1,2,3,3 ,4,5]] 而不是 [[1,2,3],[3,4,5]]
我在下面粘贴了我不完整的代码,它现在不能工作,但它应该给出我正在尝试完成的事情的总体思路。也欢迎任何关于一般 Haskell 代码礼仪的反馈。
split_breaker 将列表分成列表列表,split_help 是我试图用来组合不相等元素的内容。
split_help x y
| x /= y = x ++ y
| otherwise = []
split_breaker :: Eq a => [a] -> [[a]]
split_breaker [] = []
split_breaker [x] = [[x]]
split_breaker (x:xs) = [x]:split_breaker xs
split_at_duplicate :: Eq a => [a] -> [[a]]
split_at_duplicate [x] = [[x]]
split_at_duplicate (x:xs) = foldl1 (split_help) (split_breaker [xs])
你想像这样工作吗?
splitAtDup [1,2,3,3,3,4,4,5,5,5,5,6]
[[1,2,3],[3],[3,4],[4,5],[5],[5],[5,6]]
我说得对吗?
那就简单点:
splitAtDup :: Eq a => [a] -> [[a]]
splitAtDup (x : y : xs) | x == y = [x] : splitAtDup (y : xs)
splitAtDup (x : xs) =
case splitAtDup xs of
x' : xs' -> (x : x') : xs'
_ -> [[x]]
splitAtDup [] = []
这是一个最懒惰的方法:
splitWhen :: (a -> a -> Bool) -> [a] -> [[a]]
splitWhen f = foldr go [[]] where
go x acc = (x:xs):xss where
xs:xss = case acc of
(z:_):_ | f x z -> []:acc
_ -> acc
splitAtDup :: Eq a => [a] -> [[a]]
splitAtDup = splitWhen (==)
要验证懒惰,试试这个:
take 2 $ take 4 <$> splitAtDup (1:2:3:3:4:5:6:undefined)
它可以完全评估为范式[[1,2,3],[3,4,5,6]]
。
作为一个编程练习,我正在尝试在 Haskell 中构建一个函数,其中给定一个列表,只要重复一个元素,它就会拆分列表。例如,[1,2,3,3,4,5] 会拆分为 [[1,2,3],[3,4,5]]。我的第一个想法是将列表拆分为具有单个元素的列表列表,其中 [1,2,3,3,4,5] 将变为 [[1],[2],[3],[3], [4],[5]] 然后仅当被比较的元素不相等时才合并列表,但是实现这个对我来说很头疼,因为我对 Haskell 很陌生,递归总是给我带来麻烦.我认为我用来组合列表的函数有问题,它只会 return 一个列表,其中所有被分解的元素组合在一起,其中 [1,2,3,3,4 ,5] 变为 [[1],[2],[3],[3],[4],[5]] 但我的 split_help 函数会将其转换为 [[1,2,3,3 ,4,5]] 而不是 [[1,2,3],[3,4,5]]
我在下面粘贴了我不完整的代码,它现在不能工作,但它应该给出我正在尝试完成的事情的总体思路。也欢迎任何关于一般 Haskell 代码礼仪的反馈。
split_breaker 将列表分成列表列表,split_help 是我试图用来组合不相等元素的内容。
split_help x y
| x /= y = x ++ y
| otherwise = []
split_breaker :: Eq a => [a] -> [[a]]
split_breaker [] = []
split_breaker [x] = [[x]]
split_breaker (x:xs) = [x]:split_breaker xs
split_at_duplicate :: Eq a => [a] -> [[a]]
split_at_duplicate [x] = [[x]]
split_at_duplicate (x:xs) = foldl1 (split_help) (split_breaker [xs])
你想像这样工作吗?
splitAtDup [1,2,3,3,3,4,4,5,5,5,5,6]
[[1,2,3],[3],[3,4],[4,5],[5],[5],[5,6]]
我说得对吗?
那就简单点:
splitAtDup :: Eq a => [a] -> [[a]]
splitAtDup (x : y : xs) | x == y = [x] : splitAtDup (y : xs)
splitAtDup (x : xs) =
case splitAtDup xs of
x' : xs' -> (x : x') : xs'
_ -> [[x]]
splitAtDup [] = []
这是一个最懒惰的方法:
splitWhen :: (a -> a -> Bool) -> [a] -> [[a]]
splitWhen f = foldr go [[]] where
go x acc = (x:xs):xss where
xs:xss = case acc of
(z:_):_ | f x z -> []:acc
_ -> acc
splitAtDup :: Eq a => [a] -> [[a]]
splitAtDup = splitWhen (==)
要验证懒惰,试试这个:
take 2 $ take 4 <$> splitAtDup (1:2:3:3:4:5:6:undefined)
它可以完全评估为范式[[1,2,3],[3,4,5,6]]
。