为什么不是 sum == foldl1 (+)?
Why isn't sum == foldl1 (+)?
我用 foldl1 (+)
对矩阵列表求和,因为我注意到 sum
实际上 returns 将左上角元素的总和作为 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 (+)
,因为 'removes error on []
'(附带问题:如何我优雅地 []
- 安全地求和矩阵?)
为什么 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.
...
¹ 在整数相加的上下文中
开始吧:
why isn't generally always sum == foldl1 (+)?
foldl1 (+) []
*** Exception: Prelude.foldl1: empty list
sum []
0
这是主要区别,sum 允许与 []
进行模式匹配,而 foldl1 (+)
则不允许。
foldl1
用于非空列表,因为有些函数对空列表没有意义,例如:
foldl1 max
[]
的 max
是什么?没有最大值,因为没有元素。
foldl1 max [5,4,2,3]
5
因为 Num
的 Data.Matrix
实例通过返回 1x1 矩阵实现 fromInteger
,而 +
通过添加 elementwise
来实现 elementwise
左边的大小(如果左边大于右边则出错)。
sum
是 foldl (+) 0
,它以 0
中的 1x1 矩阵开始,并将列表中的所有矩阵截断为该大小。 foldl1 (+)
不必从整数生成矩阵,因此结果是列表中最小矩阵的大小。
此外,sum = getSum #. foldMap Sum
是默认的 Foldable
实现,但它被列表实例覆盖为 sum = foldl (+) 0
。
如果操作数的形状不同,+
(如上所示)的 Data.Matrix
实现(如上所示)到版本 0.3.1.1 会产生错误,但在版本 0.3.2.0 中它假设没有检查它们是相同的形状,并且在版本 0.3.3.0 中它再次更改(根据评论):
-- | Perform an operation element-wise.
-- The second matrix must have at least as many rows
-- and columns as the first matrix. If it's bigger,
-- the leftover items will be ignored.
-- If it's smaller, it will cause a run-time error.
-- You may want to use 'elementwiseUnsafe' if you
-- are definitely sure that a run-time error won't
-- arise.
直到当前版本 0.3.6.1
,实现似乎都是一样的
我认为@pat 已经很好地回答了这个问题,但我会尝试回答附带的问题:
How do I elegantly and []-safely sum up matrices?
您不能安全地汇总矩阵列表,因为您不确定列表中每个矩阵的大小是多少,除非您在类型级别具有该大小信息。想象一个类型 data Matrix (m :: Nat) (n :: Nat) e = ...
,那么可以保证列表中的每个矩阵都具有完全相同的维度。我不确定通用数组库在该领域是否有任何工作,但我在 OpenCV bindings and in mapalgebra and briefly considered using it in massiv 中看到过这种方法。这种方法有一些缺点,但这对这个问题并不重要。
另一种方法是不使用矩阵列表,而是使用 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 ]
]
如果 a
没有索引为 0
到 2
的页面,上面将生成 Nothing
。另一方面,如果您对运行时异常没问题,那么这会更清楚一些,但不那么安全:
λ> 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
pragma 的用法,这使我相信您实际上关心性能。一个小而重要的事实:matrix
包在 Matrix
数据类型下面使用盒装向量,这总是会给你带来糟糕的性能,没有 pragma 可以帮助你。
我用 foldl1 (+)
对矩阵列表求和,因为我注意到 sum
实际上 returns 将左上角元素的总和作为 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 (+)
,因为 'removes error on []
'(附带问题:如何我优雅地 []
- 安全地求和矩阵?)
为什么 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.
...
¹ 在整数相加的上下文中
开始吧:
why isn't generally always sum == foldl1 (+)?
foldl1 (+) []
*** Exception: Prelude.foldl1: empty list
sum []
0
这是主要区别,sum 允许与 []
进行模式匹配,而 foldl1 (+)
则不允许。
foldl1
用于非空列表,因为有些函数对空列表没有意义,例如:
foldl1 max
[]
的 max
是什么?没有最大值,因为没有元素。
foldl1 max [5,4,2,3]
5
因为 Num
的 Data.Matrix
实例通过返回 1x1 矩阵实现 fromInteger
,而 +
通过添加 elementwise
来实现 elementwise
左边的大小(如果左边大于右边则出错)。
sum
是 foldl (+) 0
,它以 0
中的 1x1 矩阵开始,并将列表中的所有矩阵截断为该大小。 foldl1 (+)
不必从整数生成矩阵,因此结果是列表中最小矩阵的大小。
此外,sum = getSum #. foldMap Sum
是默认的 Foldable
实现,但它被列表实例覆盖为 sum = foldl (+) 0
。
如果操作数的形状不同,+
(如上所示)的 Data.Matrix
实现(如上所示)到版本 0.3.1.1 会产生错误,但在版本 0.3.2.0 中它假设没有检查它们是相同的形状,并且在版本 0.3.3.0 中它再次更改(根据评论):
-- | Perform an operation element-wise.
-- The second matrix must have at least as many rows
-- and columns as the first matrix. If it's bigger,
-- the leftover items will be ignored.
-- If it's smaller, it will cause a run-time error.
-- You may want to use 'elementwiseUnsafe' if you
-- are definitely sure that a run-time error won't
-- arise.
直到当前版本 0.3.6.1
,实现似乎都是一样的我认为@pat 已经很好地回答了这个问题,但我会尝试回答附带的问题:
How do I elegantly and []-safely sum up matrices?
您不能安全地汇总矩阵列表,因为您不确定列表中每个矩阵的大小是多少,除非您在类型级别具有该大小信息。想象一个类型 data Matrix (m :: Nat) (n :: Nat) e = ...
,那么可以保证列表中的每个矩阵都具有完全相同的维度。我不确定通用数组库在该领域是否有任何工作,但我在 OpenCV bindings and in mapalgebra and briefly considered using it in massiv 中看到过这种方法。这种方法有一些缺点,但这对这个问题并不重要。
另一种方法是不使用矩阵列表,而是使用 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 ]
]
如果 a
没有索引为 0
到 2
的页面,上面将生成 Nothing
。另一方面,如果您对运行时异常没问题,那么这会更清楚一些,但不那么安全:
λ> 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
pragma 的用法,这使我相信您实际上关心性能。一个小而重要的事实:matrix
包在 Matrix
数据类型下面使用盒装向量,这总是会给你带来糟糕的性能,没有 pragma 可以帮助你。