返回列表列表([[a]])但不使用空列表([]:[a])的语法是什么(如果可能的话)? (类似于下面的第二个评论后卫(2),这是不正确的)
这是一个正常工作的功能:
-- Split string on every (shouldSplit == true)
splitWith :: (Char -> Bool) -> [Char] -> [[Char]]
splitWith shouldSplit list = filter (not.null) -- would like to get rid of filter
(imp' shouldSplit list)
where
imp' _ [] = [[]]
imp' shouldSplit (x:xs)
| shouldSplit x = []:imp' shouldSplit xs -- (1) this line is adding empty lists
-- | shouldSplit x = [imp' shouldSplit xs] -- (2) if this would be correct, no filter needed
| otherwise = let (z:zs) = imp' shouldSplit xs in (x:z):zs
这是正确的结果
Prelude> splitWith (== 'a') "miraaaakojajeja234"
["mir","koj","jej","234"]
但是,它必须使用“过滤器”来清理其结果,所以我想摆脱“过滤器”功能。 这是不使用过滤器的结果:
["mir","","","","koj","jej","234"]
如果使用“ | shouldSplit x = imp'utosSplit xs ”而不是第一个警卫,则结果不正确:
["mirkojjej234"]
第一个守卫(1)添加空列表,所以(我假设)编译器可以将结果视为列表列表([[a]])。
(我对该函数的另一个/不同解决方案不感兴趣,只是语法澄清。)
.
.
.
答案:
来自Dave4420的回答让我得到了答案,但这是一个评论,而不是一个答案所以我不能接受它作为答案。解决问题的方法是我提出错误的问题。这不是语法问题,而是我算法的问题。
解决空列表问题的其他/不同解决方案有几个答案,但它们不是我的问题的答案。但是,他们扩展了我对如何使用基本Haskell语法完成工作的看法,我感谢他们。
修改
splitWith :: (Char -> Bool) -> String -> [String]
splitWith p = go False
where
go _ [] = [[]]
go lastEmpty (x:xs)
| p x = if lastEmpty then go True xs else []:go True xs
| otherwise = let (z:zs) = go False xs in (x:z):zs
答案 0 :(得分:3)
这个利用模式匹配来完成不在一次遍历中产生空交错列表的任务:
splitWith :: Eq a => (a -> Bool) -> [a] -> [[a]]
splitWith f list = case splitWith' f list of
[]:result -> result
result -> result
where
splitWith' _ [] = []
splitWith' f (a:[]) = if f a then [] else [[a]]
splitWith' f (a:b:tail) =
let next = splitWith' f (b : tail)
in if f a
then if a == b
then next
else [] : next
else case next of
[] -> [[a]]
nextHead:nextTail -> (a : nextHead) : nextTail
运行它:
main = do
print $ splitWith (== 'a') "miraaaakojajeja234"
print $ splitWith (== 'a') "mirrraaaakkkojjjajeja234"
print $ splitWith (== 'a') "aaabbbaaa"
产地:
["mir","koj","jej","234"]
["mirrr","kkkojjj","jej","234"]
["bbb"]
答案 1 :(得分:2)
这个问题很自然地表达为你正在拆分的列表。您需要跟踪两个状态 - 结果列表,以及正在构建的当前单词以附加到结果列表。
我可能会写一个像这样的天真版本:
splitWith p xs = word:result
where
(result, word) = foldr func ([], []) xs
func x (result, word) = if p x
then (word:result,[])
else (result, x:word)
请注意,这也留在空列表中,因为只要它检测到满足谓词p
的新元素,它就会将当前单词附加到结果中。
要解决此问题,只需将list cons运算符(:)
替换为新运算符
(~:) :: [a] -> [[a]] -> [[a]]
如果原始列表非空,则只将一个列表与另一个列表相关联。算法的其余部分没有改变。
splitWith p xs = word ~: result
where
(result, word) = foldr func ([], []) xs
func x (result, word) = if p x
then (word ~: result, [])
else (result, x:word)
x ~: xs = if null x then xs else x:xs
做你想做的事。
答案 2 :(得分:1)
我想我和Chris有类似的想法,即使不那么优雅:
splitWith shouldSplit list = imp' list [] []
where
imp' [] accum result = result ++ if null accum then [] else [accum]
imp' (x:xs) accum result
| shouldSplit x =
imp' xs [] (result ++ if null accum
then []
else [accum])
| otherwise = imp' xs (accum ++ [x]) result
答案 3 :(得分:1)
这基本上只是dropWhile
和break
的交替应用,不是吗:
splitWith p xs = g xs
where
g xs = let (a,b) = break p (dropWhile p xs)
in if null a then [] else a : g b
你说你对其他解决方案不感兴趣,但其他读者可能会这样。它确实很短,看起来很清楚。如您所知,使用基本Prelude
函数成为第二天性。 :)
关于你的代码,稍微以非必要的方式重做(使用简短的暗示函数名称,例如p
用于“谓词”和g
用于主要代码工人功能),它是
splitWith :: (Char -> Bool) -> [Char] -> [[Char]]
splitWith p list = filter (not.null) (g list)
where
g [] = [[]]
g (x:xs)
| p x = [] : g xs
| otherwise = let (z:zs) = g xs
in (x:z):zs
此外,没有必要将谓词作为参数传递给worker(正如评论中也提到的那样)。现在它可以说更具可读性。
接下来,只需进行最小程度的更改即可
splitWith :: (Char -> Bool) -> [Char] -> [[Char]]
splitWith p list = case g list of ([]:r)-> r; x->x
where
g [] = [[]]
g (x:xs)
| p x = case z of []-> r; -- start a new word IF not already
_ -> []:r
| otherwise = (x:z):zs
where -- now z,zs are accessible
r@(z:zs) = g xs -- in both cases
可以按照您的意愿工作。顶级case
在这里最多删除一个空单词,在内部函数工作期间的某个时刻用作分隔符标记。您的filter (not.null)
基本上融合到此处的工作函数g
中,其中条件打开 1 一个新单词(即添加 1 的空列表。)
将let
替换为where
允许变量(z
等),以便在g
定义的第二个子句的两个分支中都可以访问。
最后,你的算法足够接近,毕竟代码可以修复。
1 。实际上,列表是从左到右构建的,采用保护递归/ tail recursion modulo cons方式。