【问题标题】:Count elements on ordered list计算有序列表中的元素
【发布时间】:2018-09-29 23:08:28
【问题描述】:

我想做一个 Haskell 函数,其中输入(字符串列表)是有序的(总是。输入只有在有序时才有效)并且我想获取每个不同字符串的出现次数。

例子:

ContaOcs["a", "a", "b", "c", "c", "c", "d"]

应该返回:

[(2,"a"), (1,"b"), (3,"c"), (1,"d")]

这是我想要做的:

module Main where

contaOcs :: [String] -> [(Int, String)]
contaOcs [] = [_,_]
contaOcs [x] = [1,x]
contaOcs (i, x1:x2:xs)
 | x1 == x2 =  (i+1,(x2:xs))
 | otherwise = (0, (x2:xs))

但是这段代码有一些错误,我不太确定我应该怎么做才能做到这一点 我是函数式编程和 Haskell 的新手。谁能帮我提供一些信息?

感谢您的帮助。

【问题讨论】:

标签: list haskell


【解决方案1】:

存在一些语法问题以及类型问题。第一行看起来像:

contaOcs [] = [_,_]

但结果中的下划线 (_) 没有任何意义,您只能构造其中包含值的列表。当我们统计一个空列表的出现次数时,结果会是一个空列表,所以contaOcs [] = []

至于第二个:

   contaOcs [x] = [1,x]

在这里,您的目标是返回一个包含两个元素的列表:1x(即 String)。在 Haskell 中,列表的元素都具有 same 类型。您可以做的是返回一个 2 元组列表,其中第一项为 Int,第二项为 String,就像签名所暗示的那样,但是您需要将值包装在一个 2 元组中,例如 @ 987654330@.

在你的最后一个子句中,你写:

contaOcs (i, x1:x2:xs) = ...

这没有多大意义:输入类型是一个列表(这里是 Strings),而不是带有 Int 的 2 元组和字符串列表。

所以输入看起来像:

contaOcs (x1:x2:xs) = ...

(i+1,(x2:xs)) 这样的输出也不与签名中建议的输出类型“和谐”,这看起来像一个带有IntStrings 列表的二元组,所以@ 987654338@,不是[(Int, String)]

基于上面的 cmets,我们推导出了类似的东西:

contaOcs :: [String] -> [(Int, String)]
contaOcs [] = []
contaOcs [x] = [(1,x)]
contaOcs (x1:x2:xs)
     | x1 == x2 =  -- ...
     | otherwise = -- ...

所以现在有两部分要填写。如果x1x2 相等,这意味着我们可以首先在列表中产生一个元组(1, x1),然后由列表其余部分中contaOcs 的结果(包括x2),所以:

(1, x1) : contaOcs (x2:xs)

在后一种情况下,这意味着我们首先使用(x2:xs) 递归调用contaOcs,然后递增该列表第一项的计数器。我们确信这样的元素存在,因为我们使用包含至少一个元素的列表进行递归调用,并且通过归纳,这意味着结果也至少包含一个元素,因为基本情况包含一个元素,递归情况要么将元素添加到结果中,要么更新这些。

所以我们可以使用模式保护,并操纵结果,例如:

contaOcs :: [String] -> [(Int, String)]
contaOcs [] = []
contaOcs [x] = [(1,x)]
contaOcs (x1:x2:xs)
     | x1 == x2, ((yi, yv):ys) <- contaOcs (x2:xs) = (yi+1, yv) : ys
     | otherwise = (1, x1) : contaOcs (x2:xs)

我们也可以使用“as-pattern”:我们只需要引用以x2开头的列表尾部,而不是xs

contaOcs :: [String] -> [(Int, String)]
contaOcs [] = []
contaOcs [x] = [(1,x)]
contaOcs (x1:xs@(x2:_))
     | x1 == x2, ((yi, yv):ys) <- contaOcs xs = (yi+1, yv) : ys
     | otherwise = (1, x1) : contaOcs xs

然而,上面的内容不是很优雅。在这里使用 累加器 可能会更好,我将其留作练习。

【讨论】:

  • 谢谢。这个答案是最完整的。
【解决方案2】:
contaOcs ::  [String] -> [(Int, String)]

contaOcs 使用一个字符串列表:xss,对于每个唯一字符串:xsxss 中,我们生成一对:p,其第一个元素表示xs 的出现次数在xss 中,p 的第二个元素是xs 本身。

我们知道我们需要根据字符串的唯一性对字符串进行分组,并计算每个唯一字符串的总出现次数。您可以遵循这个想法并自己实施其余的。 contaOcs 接受一个列表并生成一个新列表,因此列表理解应该给你你想要的。您正在将一个列表转换为另一个列表,因此 fmap 一个累积的函数应该可以工作。您也可以只使用自然递归或累加器。这是contaOcs的一种写法:

contaOcs = (return . liftA2 (,) length head =<<) . group  

首先写下签名、目的声明、一些示例数据和测试用例,然后只需找到最适合您需求的解决方案即可。

【讨论】:

    【解决方案3】:

    我们来看看ghc提到的一些错误。当 GHC 谈论预期和实际类型时,请务必密切关注,因为这些消息总是很有启发性。 Expected 表示 GHC认为 你应该写什么。 Actual 表示您写的内容。您要么需要更改您编写的内容(阅读:更改您的代码),要么更改 GHC 认为您应该编写的内容(阅读:更改您的类型注释)。在这种情况下,主要是前者。

    hw.hs:2:16: error:
        • Found hole: _ :: (Int, String)
        • In the expression: _
          In the expression: [_, _]
          In an equation for ‘contaOcs’: contaOcs [] = [_, _]
        • Relevant bindings include
            contaOcs :: [String] -> [(Int, String)] (bound at hw.hs:2:1)
      |
    2 | contaOcs [] = [_,_]
      |                ^
    
    hw.hs:2:18: error:
        • Found hole: _ :: (Int, String)
        • In the expression: _
          In the expression: [_, _]
          In an equation for ‘contaOcs’: contaOcs [] = [_, _]
        • Relevant bindings include
            contaOcs :: [String] -> [(Int, String)] (bound at hw.hs:2:1)
      |
    2 | contaOcs [] = [_,_]
      |                  ^
    

    下划线用作占位符(或“孔”),稍后填写。 GHC 告诉你,你应该想办法把这些洞塞进去。

    hw.hs:3:19: error:
        • Couldn't match type ‘[Char]’ with ‘(Int, String)’
          Expected type: (Int, String)
            Actual type: String
        • In the expression: x
          In the expression: [1, x]
          In an equation for ‘contaOcs’: contaOcs [x] = [1, x]
      |
    3 | contaOcs [x] = [1,x]
      |
    

    您已声明函数的返回类型为[(Int, String)],换句话说,一个List,其中列表的每个元素都是IntString 的元组。

    因此,列表中的每个元素都应该是一个元组。语法[1,x] 表示包含两个元素的列表:1x。 GHC 注意到x,然而,它被称为String,它不是一个元组。 (GHC 没有注意到 1 不是元组,原因是……原因。Haskell 中的数字有点奇怪,GHC 对这些没有太大帮助。)

    也许你的意思是写(1, x),它是1(Int)和x(String)的元组。但是,不要忘记以某种方式将该元组放入列表中,因为您的返回类型是一个 list 元组。

    hw.hs:4:10: error:
        • Couldn't match expected type ‘[String]’
                      with actual type ‘(Integer, [a0])’
        • In the pattern: (i, x1 : x2 : xs)
          In an equation for ‘contaOcs’:
              contaOcs (i, x1 : x2 : xs)
                | x1 == x2 = (i + 1, (x2 : xs))
                | otherwise = (0, (x2 : xs))
      |
    4 | contaOcs (i, x1:x2:xs)
      |          ^^^^^^^^^^^^^
    

    GHC 再次提醒您它需要一个元组列表,但在这种情况下,您只给了它一个元组。

    错误大多与此相同。

    【讨论】:

      【解决方案4】:

      你想要的,单线就可以完成

      Prelude> import Data.List
      Prelude Data.List> ls = ["a", "a", "b", "c", "c", "c", "d"]
      Prelude Data.List> [(length x, head x) | x <- group ls]
      [(2,"a"),(1,"b"),(3,"c"),(1,"d")]
      

      我将列表理解与group 函数混合使用。您可以让自己熟悉的基本概念。

      【讨论】:

      • 应该是 OP 的描述中的(length x, head x),并且您应该添加一些解释(或者至少,指向列表理解和组函数的文档的指针)(downvote 不是我的,有一些小的改进这可能是一个很好的答案)。
      • 这个即使在它的第一个版本中也是一个很好的答案。元组的交换并不重要,甚至可以引起 OP 的注意。 (-2) 那个??我不明白这一点。 ---- 顺便说一句,另一种写法是map (length &amp;&amp;&amp; head) . group就是
      • @Redu 差不多。 :) 但首先:对我来说,. 很清楚,但是对于&lt;$&gt;,我需要仔细考虑以了解它是哪个实例......在这里你可能也认为它是map(我做到了,起初,在我检查类型之前),但实际上是.。你的代码相当于liftA2 (,) length head . group :: Eq b =&gt; [b] -&gt; [(Int, b)],但我们需要map (liftA2 (,) length head) . group :: Eq b =&gt; [b] -&gt; [(Int, b)]。把它写成(liftA2 (,) length head &lt;$&gt;) &lt;$&gt; group 只会是一个极端的混淆,IMO。 :)
      • @Redu 我有一个错字,这阻碍了我评论的全部意义。你的代码类型是Eq b =&gt; [b] -&gt; (Int, [b])(我从 GHCi 窗口复制了错误的东西)。它计算出完全不同的结果。
      • @Will Ness 是的,我明白你的意思。由于嵌套函数,它应该像((,) &lt;$&gt; length &lt;*&gt; head &lt;$&gt;) &lt;$&gt; group。谢谢指正。
      【解决方案5】:

      这是一个很好的例子,说明协递归函数何时有用。

      contaOcs :: [String] -> [(Int, String)]
      

      我们将contaOcs 定义为接受字符串列表并返回元组的外部函数。首先我们来看一些琐碎的案例:

      contaOcs [] = []
      contaOcs [x] = [(1,x)]
      

      传递一个空列表,你应该得到一个空列表。传递一个元素列表,你应该得到一个包含一个元素的列表:(1, x)。现在我们可以保证任何其他列表的长度都是 2+ 个元素。

      contaOcs (x:xs) = go x xs
      

      go?你可能会问go 是什么?好吧,让我们在where 子句中定义它:

        where
        go cur xs = let (this, rest) = span (==x) xs
                    in (succ . length $ this, cur) : contaOcs diff
      

      这有点多,所以让我们打开包装吧。 go 是函数助手的惯用术语(这可以很容易地命名为 ffrobnicator,没关系)。它采用我们正在计数的字符,该字符与列表的其余部分分开,并将其称为x。它针对列表的其余部分运行span (==x),将其拆分为元组(longestPrefixThatMatches, rest)。我们返回最长前缀的长度(加一,因为我们已经剥离了前面的字符)与元组中的字符本身配对,然后将其与递归情况相结合——将列表的其余部分交给外部要处理的函数。

      【讨论】:

        【解决方案6】:
        contaOcs :: [String] -> [(Int, String)]
        contaOcs xs = foldr foldContaOcs [] xs
                      where foldContaOcs s [] = (1, s):[]
                            foldContaOcs s ((n, ch):xs) = if ch == s then (n + 1, s) : xs 
                                                          else (1, s): (n, ch): xs
        

        【讨论】:

        • 删除了 [x] 案例
        • take 2 . contaOcs $ ["a","b"] ++ repeat "c" 有效吗? :)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-02-25
        • 2018-07-25
        • 1970-01-01
        • 2014-05-14
        • 2021-03-09
        • 2013-09-11
        相关资源
        最近更新 更多