【问题标题】:Haskell: return the "list" result of a function as a "list of lists" without using an empty list "[]:foo"Haskell:将函数的“列表”结果作为“列表列表”返回,而不使用空列表“[]:foo”
【发布时间】:2013-03-11 14:31:11
【问题描述】:

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

【问题讨论】:

  • 谢谢,我知道,但是如果我使用(2),编译器会抱怨发生检查:无法构造无限类型:a0 = [a0] 预期类型:[a0] 实际类型:[ [a0]] 在 imp' 调用的返回类型中 在表达式中:imp' shouldSplit xs 编辑:似乎我删除了原始评论。这是关于使用单元素列表 [a]
  • 您似乎认为这是找到正确语法的问题,但这是您算法的结果:当参数连续有4个as时,您自然会得到3个空结果中的列表。
  • imp' shouldSplit xs[]:imp' shouldSplit xs 的类型都是正确的 [[Char]]。他们两个都不工作。 [imp' shouldSplit xs] 将是 [[[Char]]] 类型,这当然是不正确的,也不能工作。没有神奇的语法可以将imp' shouldSplit xs 转换为一个在没有filter 的情况下也可以工作的值,而无需更改算法的其他部分。
  • @dave4420:你让我思考了,所以我用['x'] 替换了[],并意识到守卫中的空列表被添加为关闭最后一个单词的最后一个元素,而其他只是一个结果没有任何元素。我现在意识到这不是语法问题,而是算法问题。谢谢!
  • @n.m.:[]:imp' shouldSplit xs 不起作用是什么意思?这个工作并产生正确的结果。

标签: list haskell syntax


【解决方案1】:

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

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

【讨论】:

  • 不错的基本解决方案。但是,保护条件a == b 有一个小错误。应该是f a && a == b,否则会丢失连续的元素。如果你尝试"mirrraaaakkkojjjajeja234",你会失去 r's、k's 和 j's
  • @VlatkoB 你是对的。我已经更新了解决方案,但为了避免重复出现的情况有所不同。
  • 我应该更加关注param字符串并选择更好的。那个“mirrr.....”的东西看起来确实很奇怪。
  • 我没有指定我的函数的所有要求(它来自“RealWorldHaskell”),所以你无法知道,但是这段代码是不正确的。对于splitWith (=='a') "aaabbbaaa",它产生["","bbba"],而正确的结果应该是["bbb"]。以防万一有人将此视为 RWH 问题的解决方案。
  • 是的,它是最快的。我做了一些计时(使用show (length result)),结果令人惊讶。我合并了“第 4 章。函数式编程”的 3 个完整 HTML,大约 750KB with 58K "words"。结果:Prelude.words = 0.052 秒,为任何字符改编 Prelude.words = 0.212 秒,Nikita = 0.796 秒,Chris = 0.856 秒,VlatkoB = 0.960 秒,并且(非常令人惊讶)Groovy = 685.675 秒。我认为 Groovy 的 ++ 可能花了很多时间。非常有趣的是,只需一点点改变就可以产生如此大的差异(前奏比光快)。
【解决方案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 的新元素时将当前单词附加到结果中。

要解决这个问题,只需将列表 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

你想要什么。

【讨论】:

  • 感谢您的回答,但正如我所说,我对其他解决方案不感兴趣(问题中的解决方案按预期工作,我只是想让它“更简单”)。只是想了解为什么“我的方式”不能做到这一点:-)。我仍在学习基础知识,如果可能的话,我会尽量不使用任何 Prelude 助手。不过,您的解决方案看起来非常简单有趣,所以我会仔细研究一下。
  • 啊,对不起 - 除了最后一行,我已经阅读了你的整个问题!
  • 请参阅我对 Nikita 的评论。也不适用于 RWH 问题。这个函数产生["","bbb"]
  • 对于splitWith (=='a') "aaabbbaaa",它产生["","bbb"],所以仍然有一个空列表。
  • :~ 运算符导致“无效类型签名”,所以也许可以使用~:~
【解决方案3】:

我想我和 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

【讨论】:

  • 目前我最喜欢你,因为它没有
  • @VlatkoB 谢谢你的评论,但我不明白...你为什么喜欢它?
  • 内部实现imp’ 通常称为go。您不需要将 shouldSplit 传递给内部函数,因为它永远不会改变,并且在外部函数的范围内。
  • @pat 是真的..我试图让它尽可能接近他的原作
  • @groovy:看来我的评论被截断了。我的意思是因为 Chris 的解决方案使用了显式折叠,而你的没有。我现在发现这样的解决方案更有趣,因为我试图围绕解决问题的功能性方法来思考,我认为(希望)实现这一目标的最佳方法是仅使用基本语法,而不使用辅助函数(除非我编写它们我)。话虽如此,我假设在我继续前进时,我会更喜欢 Chris 的解决方案。 :-)
【解决方案4】:

这基本上只是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 用于 "predicate"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(正如 cmets 中提到的那样)。现在可以说它更具可读性了。

接下来,稍加改动就变成了

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-04
    • 1970-01-01
    • 1970-01-01
    • 2020-01-22
    • 2022-08-04
    • 2016-01-03
    相关资源
    最近更新 更多