Haskell:将函数的“list”结果作为“列表列表”返回,而不使用空列表“[]:foo”

时间:2013-03-11 14:31:11

标签: 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 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

4 个答案:

答案 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)

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

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方式。