【问题标题】:Element-wise addition (multiplication, exponentiation, etc.) of lists in HaskellHaskell 中列表的元素加法(乘法、求幂等)
【发布时间】:2014-02-15 23:12:42
【问题描述】:

如果我在 Haskell 中有两个大小相同的列表

list1 = [1.0,2.0,3.0]
list2 = [3.0,5.0,7.0]

我将如何执行元素相加来创建第三个相同大小的列表?

[4.0,7.0,10.0]

具体来说,我想做一个这样的函数:

listAdd :: [Float] -> [Float] -> [Float]
listAdd a b
    | length a /= length b = error "length mismatch"
    otherwise              = ????

我不知道用什么来代替“????”。我认为它必须涉及'map'和某些版本的'+',但是部分评估的事情让我感到困惑,并且正确的语法被证明是难以捉摸的。

编辑 1:

我以为我理解了与 cons 运算符的模式匹配,所以我接下来尝试了这个:

listAdd :: [Float] -> [Float] -> [Float]
listadd (x:xs) (y:ys) = (x+y) : listAdd xs ys
listAdd []     []     = []
listAdd _      _      = error "length mismatch"

但是还是有问题,如

listAdd [1.0,2.0] [2.0,3.0]

跳过有用的模式并返回错误。

编辑 2:

消除错别字,

listAdd :: [Float] -> [Float] -> [Float]
listAdd (x:xs) (y:ys) = (x+y) : listAdd xs ys
listAdd []     []     = []
listAdd _      _      = error "length mismatch"

按广告宣传。由于在我研究的早期阶段,管理任意维度张量的类型超出了我的能力范围,因此我决定仅将其扩展到矩阵加法:

mAdd :: [[Float]] -> [[Float]] -> [[Float]]
mAdd (x:xs) (y:ys) = listAdd x y : mAdd xs ys
mAdd []     []     = []
mAdd _      _      = error "length mismatch"

我意识到将功能捆绑在一起可能并不理想,因为它会降低模块化/可移植性,但它可以满足我的需要。

编辑 3:

我希望现在宣布某种程度的学习已经发生还为时过早。我现在有这个:

listCombine :: (Float -> Float -> Float) -> [Float] -> [Float] -> [Float]
listCombine f (x:xs) (y:ys) = (f x y) : listCombine f xs ys
listCombine f []     []     = []
listCombine _ _      _      = error "length mismatch"

这可能与 zipWith 相同,只是它会因长度不匹配而出错。它处理了我扔给它的极端案例并取得了预期的结果。

【问题讨论】:

  • 虽然您可以像以前一样使用显式递归,但在列表长度相等的情况下,更惯用的 Haskell 版本只是 zipWith (+)
  • 在这种情况下,我想长期坚持下去。我希望在让它与列表一起工作之后,我将看到如何扩展它以按元素添加任意大小的张量。知道为什么我的功能不起作用吗?
  • @user2790167 你有一个错字:listadd (x:xs) (y:ys) = 应该是listAdd (x:xs) (y:ys) =。我猜你没有复制粘贴我发布的代码,而是重新输入了它。
  • 我不敢相信我打错了。感谢您指出这一点。
  • 好的,我想我明白了。您使用 listCombine f (x:xs) (y:ys) = (f x y) : listCombine f xs ys 对未指定的函数 f 进行模式匹配。

标签: list haskell map


【解决方案1】:

单独计算参数列表的长度是个坏主意。我们通常希望消耗尽可能少的输入,同时产生尽可能多的输出。这被称为“不强制输入太多”,即尽可能地懒惰。

在您的情况下,当我们分析两个参数时,如果一个列表为空而另一个不是,我们将知道长度不匹配:

listAdd :: [Float] -> [Float] -> [Float]
listAdd (x:xs) (y:ys) =  (x+y) : listAdd ... ...
listAdd []     []     = []
listAdd _      _      =  error "length mismatch"

这种方式甚至适用于无限列表。

与此类似的内置函数是zipWith,但它忽略了列表长度不匹配:

Prelude> zipWith(+) [1,2] [3]
[4]

相当于上面的定义,最后两行替换为catch-all子句

listAdd _      _      = []

【讨论】:

  • 我遵循懒惰的想法,但是你函数的第二行是做什么的? : 位对我来说很神秘。
  • : 在等式左侧是模式的一部分:(x:xs) 匹配头部为 x 和尾部为 xs 的非空列表。右侧是一个列表构造函数:x:xs 描述了一个带有头部 x 和尾部 xs 的列表。
【解决方案2】:

这并没有解决原始问题,而是解决了 cmets 之一:张量相加。我对张量的理解是 n 维矩阵。你已经知道元素加法只是zipWith 的一个应用。但是我们可以使用 applicative 和 functor 类型类编写函数 addLists

import Control.Applicative

addLists :: [Float] -> [Float] -> [Float]
addLists x y = (*) <$> x <*> y

或等效:

addLists :: [Float] -> [Float] -> [Float]
addLists = liftA2 (*) 

请注意,&lt;$&gt; == fmap&lt;*&gt;Applicative 类中。

首先我们为 rank-0 张量定义一个类型:

newtype Identity a = Identity {getIdentity :: a}

instance Functor Identity where 
  fmap f (Identity a) = Identity (f a) 

instance Applicative Identity where 
  pure a = Identity a
  (<*>) (Identity f) (Identity a) = Identity (f a)

然后是在rank-n张量之上添加一个新层的类型,创建一个rank n+1张量:

newtype Compose f g a = Compose {getCompose :: f (g a)}

instance (Functor f, Functor g) => Functor (Compose f g) where 
  fmap f (Compose a) = Compose (fmap (fmap f) a)

instance (Applicative f, Applicative g) => Applicative (Compose f g) where 
  pure a = Compose (pure (pure a))
  (<*>) (Compose f) (Compose a) = Compose (liftA2 (<*>) f a)

但是等等,还有更多!这些类型及其实例已经为您定义好了,即在Data.Functor.IdentityData.Functor.Compose 中。您现在可以编写通用张量元素加法:

addTensor :: Applicative f => f Float -> f Float -> f Float
addTensor = liftA2 (*) 

函数liftA2只是将zipWith推​​广到Applicatives。对于 rank-1,您有:

addTensor1 :: Compose [] Identity Float -> Compose [] Identity Float -> Compose [] Identity Float
addTensor1 = addTensor

这种类型有点吵。您可以轻松定义类型同义词:

type Tensor0 = Identity
type Tensor1 = Compose [] Tensor0
type Tensor2 = Compose [] Tensor1
type Tensor3 = Compose [] Tensor2
type Tensor4 = Compose [] Tensor3

然后:

addTensor3 :: Tensor3 Float -> Tensor3 Float -> Tensor3 Float
addTensor3 = addTensor

addTensor4 :: Tensor4 Float -> Tensor4 Float -> Tensor4 Float
addTensor4 = addTensor

由于addTensor 对所有Applicatives 进行抽象,您可能需要定义Applicative 的子类,它们形成有效的张量:

class Applicative t => Tensor t 
instance Tensor Identity
instance Tensor t => Tensor (Compose [] t)

addTensor :: Tensor f => f Float -> f Float -> f Float
addTensor = liftA2 (*) 

我怀疑您可能对张量进行了除加法之外的其他操作。这种通用形式使得定义大范围的操作变得非常容易。但假设你只想要addTensor,你可以这样做:

class Tensor a where 
  addTensor :: a -> a -> a 

instance Tensor Float where 
  addTensor = (+)

instance Tensor t => Tensor [t] where 
  addTensor = zipWith addTensor 

更简单的类型结果:addTensor :: [[[[Float]]]] -&gt; [[[[Float]]]] -&gt; [[[[Float]]]] 用于 rank-4。

【讨论】:

  • 我怀疑我需要一段时间才能理解这个答案,但谢谢。
  • 你有addTensor = liftA2 (*) 。您的意思可能是 (+) 而不是 (*)multTensor
  • 我认为&lt;$&gt; 在您的第一个代码sn-p 中的第二次出现应该是&lt;*&gt;
猜你喜欢
  • 1970-01-01
  • 2018-08-21
  • 1970-01-01
  • 2019-05-05
  • 1970-01-01
  • 2020-05-21
  • 1970-01-01
  • 2015-02-09
  • 1970-01-01
相关资源
最近更新 更多