Haskell-计算多个元素的出现次数并返回元组
嘿,我是Haskell初学者,我假装做以下功能: 事件37[-1,3,-4,3,4,3,-8,7,7,3] 我想要的输出: (4,2) 我做了这个尝试,但效果不太好,我想我在单独计算元素和返回元组时遇到了麻烦Haskell-计算多个元素的出现次数并返回元组,haskell,recursion,Haskell,Recursion,嘿,我是Haskell初学者,我假装做以下功能: 事件37[-1,3,-4,3,4,3,-8,7,7,3] 我想要的输出: (4,2) 我做了这个尝试,但效果不太好,我想我在单独计算元素和返回元组时遇到了麻烦 occurrences a b [] = 0 occurrences a b (x:xs) | x == a = 1 + occurrences a b xs | x == b = 1
occurrences a b [] = 0
occurrences a b (x:xs)
| x == a = 1 + occurrences a b xs
| x == b = 1 + occurrences a b xs
| otherwise = occurrences a b xs
谢谢你给我的建议和帮助;) 你可以用很多不同的方法来解决这个问题,这里是一个折叠的例子
occurrences :: (Eq a) => a -> a -> [a] -> (Int, Int)
occurrences a b list = foldr (\y (a', b') -> ((isEqual y a a'), (isEqual y b b'))) (0, 0) list
where isEqual listEle tupEle count = if (listEle == tupEle) then (count + 1) else count
问题之一是类型不匹配。您需要一种类型的:
(Int, Int)
但是,一旦您有一个空列表,您将在此处返回一种int类型:
occurrences a b [] = 0 -- Int
occurrences a b (x:xs)
| x == a = 1 + occurrences a b xs
| x == b = 1 + occurrences a b xs
| otherwise = occurrences a b xs
您需要某种类型的累加器,您可以通过在其中绑定一个本地函数来实现这一点,该函数接受您的起始元组(0,0)
,或者您可以将其传递给如下事件:
occurrences :: Int -> Int -> [Int] -> (Int, Int) -> (Int, Int)
我建议使用局部函数,因为在这种方法中,您总是希望从(0,0)开始
occurrences' :: (Eq a) => a -> a -> [a] -> (Int, Int)
occurrences' a b list = go list (0,0)
where go x (e1, e2) = if (x == []) then (e1, e2) else (go (tail x) ((isEqual a (head x) e1), (isEqual b (head x) e2)))
isEqual v v' accum = if (v == v') then (accum + 1) else (accum)
这不是最惯用的方法,但它表明了这一点。您应该尝试使用类型来帮助解决此问题。@emg184提供了解决此问题的好方法,但是可以有更干净、更容易阅读的方法来解决此问题。例如:
Prelude Control.Arrow> first (1+) (1,4)
(2,4)
Prelude Control.Arrow> second (1+) (1,4)
(1,5)
出现次数x y xs=(计数x xs,计数y xs)
其中count=(长度)。过滤器。(==)
count
也可以用更可读的格式编写:
count x=(length.filter(=x))
一个好方法是添加类型签名,并使用错误消息指导您:
occurrences :: (Eq a) => a -> a -> [a] -> (Int, Int)
occurrences a b [] = 0
occurrences a b (x:xs)
| x == a = 1 + occurrences a b xs
| x == b = 1 + occurrences a b xs
| otherwise = occurrences a b xs
第一个错误是“无法从上下文Eq a
中的文本0
推断(Num(Int,Int))
”。这意味着我们不能在第一个等式中使用0
,因为它不是一个元组,或者更准确地说,没有Num
实例允许我们通过fromIntegral
将文本0
转换为元组。在基本情况下,我们应该为两个和返回一个包含0的元组:
现在,这将产生预期的结果:
> occurrences 'a' 'b' "ababcb"
(2,3)
但我们可以从几个方面改进这个解决方案。首先,a
和b
在整个计算过程中保持不变,因此我们可以在助手函数中进行递归,而不是将a
和b
传递给每个调用
occurrences :: (Eq a) => a -> a -> [a] -> (Int, Int)
occurrences a b = go
where
go [] = (0, 0)
go (x:xs)
| x == a = let (m, n) = go xs in (m + 1, n)
| x == b = let (m, n) = go xs in (m, n + 1)
| otherwise = go xs
最后,我们可以将每个元素映射到一个值(0或1)并通过求和减少它们,而不是保留一个累加器并逐个添加元素。此映射/缩减模式由foldMap::(可折叠t,monoidm)=>(a->m)->ta->m
捕获,它将容器(ta
)的每个元素映射到一个值(m
),并使用Monoid
实例组合结果。这里使用的幺半群是来自数据的Sum
。幺半群
,其monoid
和半群
实例分别定义mempty
=Sum 0
和Sum a Sum b
=Sum(a+b)
import Data.Coerce (coerce)
import Data.Foldable (foldMap)
import Data.Monoid (Sum(..))
occurrences :: (Eq a) => a -> a -> [a] -> (Int, Int)
occurrences a b = coerce . foldMap go
where
go x
| x == a = (Sum (1 :: Int), mempty)
| x == b = (mempty, Sum (1 :: Int))
| otherwise = mempty
我们可以使用像和这样的函数来构造一个元组,其中我们将一个函数应用于2元组的两个项之一。例如:
Prelude Control.Arrow> first (1+) (1,4)
(2,4)
Prelude Control.Arrow> second (1+) (1,4)
(1,5)
因此,我们可以使用以下内容更新元组:
import Control.Arrow(first, second)
import Data.List(foldl')
occurrences :: (Eq a, Integral i, Integral j, Foldable f) => a -> a -> f a -> (i, j)
occurrences a b = foldl' (flip f) (0, 0)
where f c | a == c = first (1+)
| b == c = second (1+)
| otherwise = id
对于示例输入,这将生成:
Prelude Control.Arrow Data.List> occurrences 3 7 [-1,3,-4,3,4,3,-8,7,7,3]
(4,2)
通过使用镜头更新元组中的一个元素,我们可以很容易地扩展这种行为。您在概念上非常接近-您似乎将元组与其中包含的数值混淆了。作为提示,请尝试在bxs上进行模式匹配。您的基本大小写也是错误的,因为您返回的是数字而不是元组。这很容易解决。是的,我知道我可以用
a b[]=0
来解决基本情况,但是我找不到一种方法来解决模式匹配问题,使之只和一个元组成员…函数将计算有多少a
s和b
s,所以这两者之和。如果a
和b
相等呢。因此,发生了33[-1,3,-4,3,4,3,-8,7,7,3]
?
import Control.Arrow(first, second)
import Data.List(foldl')
occurrences :: (Eq a, Integral i, Integral j, Foldable f) => a -> a -> f a -> (i, j)
occurrences a b = foldl' (flip f) (0, 0)
where f c | a == c = first (1+)
| b == c = second (1+)
| otherwise = id
Prelude Control.Arrow Data.List> occurrences 3 7 [-1,3,-4,3,4,3,-8,7,7,3]
(4,2)