1

返回列表列表([[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' shouldSplit 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

4 回答 4

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"]
于 2013-03-11T15:10:32.960 回答
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)

请注意,这也会留在空列表中,因为每当它检测到满足 predicate 的新元素时,它都会将当前单词附加到结果中p

要解决此问题,只需将 list cons 运算符替换(:)为 new 运算符

(~:) :: [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

哪个做你想要的。

于 2013-03-11T15:03:27.410 回答
1

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

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
于 2013-03-11T15:11:31.927 回答
1

这基本上只是 and 的交替应用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

此外,无需将谓词作为参数传递给工作人员(正如评论中提到的那样)。现在可以说它更具可读性。

接下来,通过最小的变化,它变成

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)本质上融合到了worker函数g中,有条件地打开一个新词1(即空列表的加1

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

最后,您的算法足够接近,并且代码毕竟可以修复。


1在思考“从右到左”时。实际上,该列表是从左到右构建的,以受保护的递归/尾递归模 cons方式。

于 2013-03-13T00:40:40.620 回答