计数有序列表中的元素

时间:2018-09-29 23:08:28

标签: list haskell

我想做一个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的新手。有人可以帮我一些信息吗?

感谢您的帮助。

6 个答案:

答案 0 :(得分:8)

存在一些语法问题以及类型问题。第一行如下:

contaOcs [] = [_,_]

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

第二个:

   contaOcs [x] = [1,x]

此处,您的目标是返回包含两个元素的列表:1x(即String)。在Haskell中,列表的元素都具有 same 类型。您可以做的是返回一个包含两个元组的列表,其中第一个为Int,第二个为String,如签名所示,但是随后您需要将值包装在2-中元组,例如contaOcs [x] = [(1,x)]

在最后一个子句中,您写:

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

没什么意义:输入类型是一个列表(此处为String个),而不是带有Int的2元组和一个字符串列表。

所以输入看起来像:

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

输出(如(i+1,(x2:xs))也不与签名中建议的输出类型“和谐”,它看起来像是带有Int的2元组和{{1 }},所以String,而不是(Int, [String])

基于以上评论,我们得出了类似的内容:

[(Int, String)]

所以现在有两部分需要填写。如果contaOcs :: [String] -> [(Int, String)] contaOcs [] = [] contaOcs [x] = [(1,x)] contaOcs (x1:x2:xs) | x1 == x2 = -- ... | otherwise = -- ... x1 相等,则意味着我们可以首先生成一个元组{{1} },然后在列表的其余部分(包括x2)后面跟随(1, x1)的结果,因此:

contaOcs

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

因此,我们可以使用模式防护,并操纵结果,例如:

contaOcs

我们还可以使用“ as-pattern ”:我们只需要引用以(x2:xs)开头的列表尾部,而不是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)

x2

但是,以上内容并不十分优雅。最好在这里使用 accumulator ,我将其保留为练习。

答案 1 :(得分:4)

让我们看看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的元组。 / p>

因此,列表中的每个元素应该是一个元组。语法[1,x]表示具有两个元素的列表:1x。 GHC注意到xString,而不是元组。 (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再次提醒您,它需要一个元组列表,但是在这种情况下,您只给了它一个元组。

错误与此基本相同。

答案 2 :(得分:4)

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

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

我们知道我们需要按字符串的唯一性对字符串进行分组,并对每个唯一字符串的总出现次数进行计数。您可以遵循这个想法,自己实现其余的想法。 contaOcs获取一个列表并生成一个新列表,因此列表理解应该可以为您提供所需的内容。您要将一个列表转换为另一个列表,因此fmap可以累加一个函数。您也可以只使用自然递归或累加器。这是写contaOcs的一种方法:

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

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

答案 3 :(得分:2)

单排即可完成

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函数混合使用。您可以自己熟悉的基本概念。

答案 4 :(得分:2)

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

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)。我们将最长的前缀的长度(加上一个,因为我们已经去除了前面的字符)加上一个元组中的字符本身的长度,然后在递归的情况下进行了验证-将列表的其余部分移回外部函数来处理。

答案 5 :(得分:1)

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