[英]Element-wise addition (multiplication, exponentiation, etc.) of lists in Haskell
如果我在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相同,只是它给出了不匹配长度的错误。 它处理了我遇到的一些极端情况,并获得了预期的结果。
单独计算参数列表的长度不是一个好主意。 我们通常希望尽可能少地使用输入,同时尽可能多地生成输出。 这被称为“不强迫输入”,即尽可能地懒惰。
在你的情况下,当我们分析这两个参数时,如果一个列表为空而另一个列表为空,我们将知道长度不匹配:
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 _ _ = []
这不是原始问题,而是其中一条评论:增加了张量。 我对张量的理解是作为n维矩阵。 您已经知道元素添加只是zipWith
一个应用程序。 但是我们可以使用applicative和functor类型类编写函数addLists
:
import Control.Applicative
addLists :: [Float] -> [Float] -> [Float]
addLists x y = (*) <$> x <*> y
或等效地:
addLists :: [Float] -> [Float] -> [Float]
addLists = liftA2 (*)
请注意, <$> == fmap
和<*>
在Applicative
类中。
首先,我们为等级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)
然后是一个用于在等级n张量之上添加新层,创建等级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.Identity
和Data.Functor.Compose
。 您现在可以编写通用张量元素添加:
addTensor :: Applicative f => f Float -> f Float -> f Float
addTensor = liftA2 (*)
函数liftA2
只是将zipWith
推广到Applicative
s。 对于等级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
对所有Applicative
抽象,因此您可能需要定义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]]]] -> [[[[Float]]]] -> [[[[Float]]]]
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.