简体   繁体   English

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

[英]Haskell: return the “list” result of a function as a “list of lists” without using an empty list “[]:foo”

What would be the syntax (if possible at all) for returning the list of lists ([[a]]) but without the use of empty list ([]:[a])? 返回列表列表([[a]])但不使用空列表([]:[a])的语法(如果可能的话)是什么? (similar as the second commented guard (2) below, which is incorrect) (类似于下面的第二个评论后卫(2),这是不正确的)

This is a function that works correctly: 这是一个可以正常工作的函数:

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

This is the correct result 这是正确的结果

Prelude> splitWith (== 'a') "miraaaakojajeja234"
["mir","koj","jej","234"]

However, it must use "filter" to clean up its result, so I would like to get rid of function "filter". 但是,它必须使用“过滤器”来清理其结果,因此我想摆脱功能“过滤器”。 This is the result without the use of filter: 这是不使用过滤器的结果:

["mir","","","","koj","jej","234"]

If " | shouldSplit x = imp' shouldSplit xs " is used instead the first guard, the result is incorrect: 如果使用了“ | ShouldSplit x = imp'shouldSplit xs ”代替了第一个防护,则结果不正确:

["mirkojjej234"]

The first guard (1) adds empty list so (I assume) compiler can treat the result as a list of lists ([[a]]). 第一个警卫(1)添加了空列表,因此(我假设)编译器可以将结果视为列表列表([[a]])。

(I'm not interested in another/different solutions of the function, just the syntax clarification.) (我对函数的其他/不同解决方案不感兴趣,仅对语法进行了说明。)

.
.
.

ANSWER : 回答

Answer from Dave4420 led me to the answer, but it was a comment, not an answer so I can't accept it as answer. Dave4420的答案将我带到了答案,但这只是评论,而不是答案,所以我不能接受它作为答案。 The solution of the problem was that I'm asking the wrong question. 问题的解决方案是我问错了问题。 It is not the problem of syntax, but of my algorithm. 这不是语法问题,而是我的算法问题。

There are several answers with another/different solutions that solve the empty list problem, but they are not the answer to my question. 可以使用其他/不同的解决方案来解决空列表问题,但是这并不是我的问题的答案。 However, they expanded my view of ways on how things can be done with basic Haskell syntax, and I thank them for it. 但是,他们扩大了我对使用基本Haskell语法可以完成事情的方式的看法,对此我深表感谢。

Edit: 编辑:

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

This one utilizes pattern matching to complete the task of not producing empty interleaving lists in a single traversal: 这是一种利用模式匹配完成以下任务的方法:一次遍历不产生空的交织列表:

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

Running it: 运行它:

main = do
  print $ splitWith (== 'a') "miraaaakojajeja234"
  print $ splitWith (== 'a') "mirrraaaakkkojjjajeja234"
  print $ splitWith (== 'a') "aaabbbaaa"

Produces: 产生:

["mir","koj","jej","234"]
["mirrr","kkkojjj","jej","234"]
["bbb"]

The problem is quite naturally expressed as a fold over the list you're splitting. 问题很自然地表示为您要拆分的列表的折叠。 You need to keep track of two pieces of state - the result list, and the current word that is being built up to append to the result list. 您需要跟踪两种状态-结果列表,以及正在构建以附加到结果列表的当前单词。

I'd probably write a naive version something like this: 我可能会写一个像这样的天真的版本:

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)

Note that this also leaves in the empty lists, because it appends the current word to the result whenever it detects a new element that satisfies the predicate p . 请注意,这也会留在空白列表中,因为只要它检测到满足谓词p的新元素,就会将当前单词附加到结果中。

To fix that, just replace the list cons operator (:) with a new operator 要解决此问题,只需用新的运算符替换列表cons运算符(:)

(~:) :: [a] -> [[a]] -> [[a]]

that only conses one list to another if the original list is non-empty. 如果原始列表是非空的,则仅将一个列表包含在另一个列表中。 The rest of the algorithm is unchanged. 其余算法不变。

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

which does what you want. 做什么你想要的。

I guess I had a similar idea to Chris, I think, even if not as elegant: 我想我和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

This is basically just an alternating application of dropWhile and break , isn't it: 这基本上只是dropWhilebreak的交替应用, dropWhile

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

You say you aren't interested in other solutions than yours, but other readers might be. 您说您对您自己的解决方案不感兴趣,但其他读者可能会感兴趣。 It sure is short and seems clear. 它肯定很短,看起来很清楚。 As you learn, using basic Prelude functions becomes second nature. 如您所知,使用基本的Prelude函数已成为第二性。 :) :)

As to your code, a little bit reworked in non-essential ways (using short suggestive function names, like p for "predicate" and g for a main worker function), it is 对于您的代码,以非必要的方式进行了一些重做(使用简短的提示性函数名称,例如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

Also, there's no need to pass the predicate as an argument to the worker (as was also mentioned in the comments). 同样,也没有必要将谓词作为参数传递给工作程序(在注释中也提到过)。 Now it is arguably a bit more readable. 现在可以说它更具可读性。

Next, with a minimal change it becomes 接下来,只需进行最小的更改即可

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

which works as you wanted. 可以按您想要的方式工作。 The top-level case is removing at most one empty word here, which serves as a separator marker at some point during the inner function's work. 顶层case是在此最多删除一个空词,该空词在内部函数工作期间的某些时候用作分隔符。 Your filter (not.null) is essentially fused into the worker function g here, with the conditional opening 1 of a new word (ie addition 1 of an empty list). 您的filter (not.null)基本上融合成工人功能g这里,有一个新词(即空列表的另外1)有条件开放1。

Replacing your let with where allowed for the variables ( z etc.) to became accessible in both branches of the second clause of the g definition. where g定义的第二个子句的两个分支都可以访问的变量( z等)替换let

In the end, your algorithm was close enough, and the code could be fixed after all. 最后,您的算法已经足够接近了,毕竟代码可以修复。


1 when thinking "right-to-left". 1想,如果“从右至左”。 In reality the list is constructed left-to-right, in guarded recursion ⁄ tail recursion modulo cons fashion. 实际上,该列表是按照保护递归⁄ 尾递归模态从左到右构造的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM