为什么不是sum == foldl1(+)?

时间:2019-08-16 12:50:53

标签: haskell matrix sum fold foldable

我用foldl1 (+)对矩阵列表求和,因为我注意到sum实际上将左上角元素的总和作为1x1矩阵返回。

$ stack exec --resolver lts-12.5 --package matrix -- ghci
GHCi, version 8.4.3: http://www.haskell.org/ghc/  :? for help
Prelude> import Data.Matrix
Prelude Data.Matrix> t = [identity 2, identity 2]  -- two 2x2 identity matrices
Prelude Data.Matrix> foldl1 (+) t
┌     ┐
│ 2 0 │
│ 0 2 │
└     ┘
Prelude Data.Matrix> sum t
┌   ┐
│ 2 │
└   ┘

这对我来说是出乎意料的,LYAH建议sum = foldl1 (+)¹,甚至hlint建议我使用sum而不是foldl1 (+),因为这是“消除[]上的错误”(附带问题:如何优雅地和[]安全地汇总矩阵?)

为什么sum /= foldl1 (+)用于矩阵,为什么通常不总是sum == foldl1 (+)

或者,排除空列表的情况:

为什么不是sum == foldl neutralElement (+)?还是sum == foldl (+) (zero 2 2)上的[identity 2, identity 2]

自己的作品:

Prelude Data.Matrix> :t sum
sum :: (Foldable t, Num a) => t a -> a
Prelude Data.Matrix> :info sum
class Foldable (t :: * -> *) where
  ...
  sum :: Num a => t a -> a
  ...
        -- Defined in ‘Data.Foldable’

sum的来源是:

sum :: Num a => t a -> a
sum = getSum #. foldMap Sum

Matrix的实例是:

instance Foldable Matrix where
foldMap f = foldMap f . mvect

instance Num a => Num (Matrix a) where
 fromInteger = M 1 1 . V.singleton . fromInteger
 negate = fmap negate
 abs = fmap abs
 signum = fmap signum

 -- Addition of matrices.
 {-# SPECIALIZE (+) :: Matrix Double -> Matrix Double -> Matrix Double #-}
 {-# SPECIALIZE (+) :: Matrix Int -> Matrix Int -> Matrix Int #-}
 (M n m v) + (M n' m' v')
   -- Checking that sizes match...
   | n /= n' || m /= m' = error $ "Addition of " ++ sizeStr n m ++ " and "
                               ++ sizeStr n' m' ++ " matrices."
   -- Otherwise, trivial zip.
   | otherwise = M n m $ V.zipWith (+) v v'

 -- Substraction of matrices.
 ...
 -- Multiplication of matrices.
 ...

¹在加整数的情况下

3 个答案:

答案 0 :(得分:9)

因为Data.Matrix的{​​{1}}实例通过返回1x1矩阵来实现Num,并且通过添加fromInteger来实现+,该方法将正确的矩阵截断为大小左边的一个(如果左边大于右边则出错)。

elementwisesum,它以foldl (+) 0中的1x1矩阵开始,并将列表中的所有矩阵都截断为该大小。 0不必由整数组成矩阵,因此结果就是列表中最小矩阵的大小。

此外,foldl1 (+)是默认的sum = getSum #. foldMap Sum实现,但列表实例将其覆盖为Foldable

直到sum = foldl (+) 0的{​​{1}}实现(如上所示),直到版本0.3.1.1,如果操作数的形状不同,都会产生错误,但是在版本0.3.2.0中,它假定它们是相同的。相同的形状,无需检查,在版本0.3.3.0中再次更改(根据评论):

Data.Matrix

实现似乎与当前版本0.3.6.1相同

答案 1 :(得分:2)

我认为@pat已很好地回答了这个问题,但我会尝试回答附带的问题:

  

我如何优雅地[]安全地汇总矩阵?

您不能安全地汇总一个矩阵列表,因为您不确定列表中每个矩阵的大小是多少,除非您在类型级别拥有该大小信息。假设类型为data Matrix (m :: Nat) (n :: Nat) e = ...,那么可以保证列表中的每个矩阵都具有完全相同的维。我不确定通用数组库在该领域是否有任何工作,但是我已经在OpenCV绑定和mapalgebra中看到了这种方法,并简要考虑过在{{3}中使用它}。这种方法有一些缺点,但这对这个问题并不重要。

另一种方法是不使用矩阵列表,而是使用3D数组,该数组本质上是大小相同的2D矩阵的页面序列。显然,matrix软件包不支持更大的尺寸,因此您无法使用该库来实现。我将展示如何使用massiv来完成。考虑我们有这个3D阵列a

λ> a = makeArrayR D Par (Sz3 3 4 5) $ \(i :> j :. k) -> i + j * k
λ> a
Array D Par (Sz (3 :> 4 :. 5))
  [ [ [ 0, 0, 0, 0, 0 ]
    , [ 0, 1, 2, 3, 4 ]
    , [ 0, 2, 4, 6, 8 ]
    , [ 0, 3, 6, 9, 12 ]
    ]
  , [ [ 1, 1, 1, 1, 1 ]
    , [ 1, 2, 3, 4, 5 ]
    , [ 1, 3, 5, 7, 9 ]
    , [ 1, 4, 7, 10, 13 ]
    ]
  , [ [ 2, 2, 2, 2, 2 ]
    , [ 2, 3, 4, 5, 6 ]
    , [ 2, 4, 6, 8, 10 ]
    , [ 2, 5, 8, 11, 14 ]
    ]
  ]

然后我们可以使用foldlWithin来折叠数组的任何维度,同时降低其维度:

λ> foldlWithin Dim3 (+) 0 a
Array D Par (Sz (4 :. 5))
  [ [ 3, 3, 3, 3, 3 ]
  , [ 3, 6, 9, 12, 15 ]
  , [ 3, 9, 15, 21, 27 ]
  , [ 3, 12, 21, 30, 39 ]
  ]
λ> foldlWithin Dim1 (+) 0 a
Array D Par (Sz (3 :. 4))
  [ [ 0, 10, 20, 30 ]
  , [ 5, 15, 25, 35 ]
  , [ 10, 20, 30, 40 ]
  ]

请注意,在这里的任何时候都不会引发异常或其他不可预测的行为(当然,不放置异步异常)。另外,也可以截取数组的切片,然后单独添加,但是如果我们想避免出现异常,则该方法会涉及更多点:

λ> a !?> 0
Array D Par (Sz (4 :. 5))
  [ [ 0, 0, 0, 0, 0 ]
  , [ 0, 1, 2, 3, 4 ]
  , [ 0, 2, 4, 6, 8 ]
  , [ 0, 3, 6, 9, 12 ]
  ]
λ> import Data.Maybe
λ> fromMaybe empty $ a !?> 0 >>= \acc0 -> foldlM (\acc pageIx -> (acc +) <$> (a !?> pageIx)) acc0 (1 ... 2)
Array D Par (Sz (4 :. 5))
  [ [ 3, 3, 3, 3, 3 ]
  , [ 3, 6, 9, 12, 15 ]
  , [ 3, 9, 15, 21, 27 ]
  , [ 3, 12, 21, 30, 39 ]
  ]

如果Nothing没有索引为a0的页面,则将产生2。另一方面,如果您可以接受运行时异常,那么这会更清楚一些,但不太安全:

λ> foldlS (\acc pageIx -> acc + (a !> pageIx)) (a !> 0) (1 ... 2)
Array D Par (Sz (4 :. 5))
  [ [ 3, 3, 3, 3, 3 ]
  , [ 3, 6, 9, 12, 15 ]
  , [ 3, 9, 15, 21, 27 ]
  , [ 3, 12, 21, 30, 39 ]
  ]

或使用列表,如实际问题中所述:

λ> Prelude.foldl1 (+) $ Prelude.fmap (a !>) [0 .. 2]
Array D Par (Sz (4 :. 5))
  [ [ 3, 3, 3, 3, 3 ]
  , [ 3, 6, 9, 12, 15 ]
  , [ 3, 9, 15, 21, 27 ]
  , [ 3, 12, 21, 30, 39 ]
  ]

旁注。我注意到SECIALIZE用法的用法,这使我相信您实际上在乎性能。一个小而重要的事实是:matrix软件包在Matrix数据类型的下面使用了装箱的向量,这将始终为您提供出色的性能,而杂用程序无法帮助您。

答案 2 :(得分:0)

开始:

  

为什么通常不总是总和== foldl1(+)?

foldl1 (+) []
*** Exception: Prelude.foldl1: empty list

sum []
0

这是主要区别,sum允许与[]foldl1 (+)匹配的模式。

foldl1本可以与非空列表一起使用,因为有些函数对空列表没有意义,例如:

foldl1 max

max中的[]是什么?没有最大值,因为没有元素。

foldl1 max [5,4,2,3]
5