List 哈斯凯尔:返回;列表“;函数的结果作为一个;名单;不使用空列表“;[]:foo“;

List 哈斯凯尔:返回;列表“;函数的结果作为一个;名单;不使用空列表“;[]:foo“;,list,haskell,syntax,List,Haskell,Syntax,返回列表列表([[a]])但不使用空列表([]:[a])的语法是什么(如果可能的话)? (与下面的第二个注释保护(2)类似,这是不正确的) 这是一个正常工作的功能: -- Split string on every (shouldSplit == true) splitWith :: (Char -> Bool) -> [Char] -> [[Char]] splitWith shouldSplit list = filter (not.null) -- would like

返回列表列表([[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"]
然而,它必须使用“filter”来清理它的结果,所以我想去掉函数“filter”。 这是不使用过滤器的结果:

["mir","","","","koj","jej","234"]
如果使用“| shouldspilt x=imp”shouldspilt 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

问题很自然地表现为要拆分的列表的折叠。您需要跟踪两种状态—结果列表和要附加到结果列表的当前单词

我可能会写一个天真的版本如下:

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

这就是你想要的。

问题很自然地表现为你要拆分的列表的折叠。您需要跟踪两种状态—结果列表和要附加到结果列表的当前单词

我可能会写一个天真的版本如下:

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

这一个利用模式匹配来完成在一次遍历中不生成空交错列表的任务:

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"]

该方法利用模式匹配来完成在一次遍历中不生成空交错列表的任务:

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"]

我想我和克里斯有着相似的想法,即使不是那么优雅:

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

我想我和克里斯有着相似的想法,即使不是那么优雅:

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

这基本上只是
dropWhile
break
的交替应用,不是吗:

splitWith p xs=g xs
哪里
gxs=let(a,b)=中断p(dropwhilepxs)
如果为空a,则[]否则a:g b
你说除了你的解决方案之外,你对其他解决方案不感兴趣,但其他读者可能会感兴趣。它确实很短,而且看起来很清楚。随着您的学习,使用基本的
Prelude
函数成为您的第二天性。:)

至于您的代码,以非必要的方式进行了一点修改(使用简短的提示性函数名,如对“predicate”使用
p
,对主辅助函数使用
g
),它是

splitWith::(Char->Bool)->[Char]->[[Char]]
splitWith p list=过滤器(非.null)(g list)
哪里
g[]=[[]]
g(x:xs)
|px=[]:gxs
|否则=let(z:zs)=gxs
in(x:z):zs
此外,不需要将谓词作为参数传递给worker(注释中也提到了这一点)。现在可以说它更具可读性

接下来,只要稍作改变,它就变成了

splitWith::(Char->Bool)->[Char]->[[Char]]
splitWith p list=案例g列表的([]:r)->r;x->x
哪里
g[]=[[]]
g(x:xs)
|px=情况z为[]->r;——如果还没有开始一个新单词
_->[]:r
|否则=(x:z):zs
其中——现在z,z是可访问的
r@(z:zs)=gxs——在这两种情况下
这是你想要的。顶级的
大小写
在这里最多删除一个空单词,它在内部函数工作期间的某个点用作分隔符标记。您的
过滤器(not.null)
基本上融合到这里的worker函数
g
,有条件地打开一个新词(即空列表的添加)

let
替换为
where
允许变量(
z
等)在
g
定义的第二个子句的两个分支中都可以访问

最后,您的算法已经足够接近了,代码毕竟是可以修复的



1当思考“从右向左”时。实际上,列表是以保护递归的方式从左到右构造的。

这基本上只是
dropWhile
break
的交替应用,不是吗:

splitWith p xs=g xs
哪里
gxs=let(a,b)=中断p(dropwhilepxs)
如果为空a,则[]否则a:g b