如何在Haskell中扩展矩阵

时间:2013-11-26 13:36:21

标签: haskell matrix

我一直在尝试创建一个返回给定矩阵的函数,并添加一行&列,其数字是旁边的行/列中的数字的总和  数字是列中数字的总和......

E.g。

(1 2
 3 4)

(1 2 3
 3 4 7
4 6 10)

我的第一个想法是

> extend1 :: Int -> Array (Int,Int) Int
> extend1 ((a,b),(c,d))  = n where
>                 n = array ((a,b),(c,d),(n,n))

因为我必须使用“数组”。现在我不知道该怎么做。是好还是完全错了?

3 个答案:

答案 0 :(得分:4)

使用你对元组的表示而不用考虑一般性,这里是你问题的解决方案。

type Square2 a = ((a,a), (a,a))
type Square3 a = ((a,a,a), (a,a,a), (a,a,a))

extend1 :: Num a => Square2 a -> Square3 a
extend1 ((a,b), (c,d)) = 
  ( (a,  b,  ab  )
  , (c,  d,  cd  )
  , (ac, bd, abcd) ) 

  where
    ab   = a  + b
    cd   = c  + d
    ac   = a  + c
    bd   = b  + d
    abcd = ab + cd

注意我如何使用从输入模式和where子句中的定义中剔除的变量定义。还要注意我在构建一个全新的输出时如何销毁和消耗输入---一些元素被重用,但结构本身被破坏了。最后请注意我如何选择元组内部的类型为常量并受Num类型类约束,这允许我使用(+)进行添加。

类似的函数,通用和使用Array的类型

extend1A :: Num a => Array (Int,Int) a -> Array (Int, Int) a

我们已经失去了知道Array的确切大小的能力,但它表明我们的函数需要一个Array的某个大小,并返回另一个被索引的Array相同且包含相同的Num - 约束类型。

array函数将第一个参数作为一组边界。我们需要根据输入数组的bounds更新它们

extend1A ary0 = array bounds1 ... where
  ((minX, minY), (maxX, maxY)) = bounds ary0
  (maxX1, maxY1) = (succ maxX, succ maxY)
  bounds1 = ((minX, minY, (maxX1, maxY1))

然后array将“assocs”列表作为其第二个参数。我们可以将任何Array ix a视为等效(粗略)为关联列表[(ix, a)],其列出的值表示“在索引ix,值为a”。为此,我们必须知道我们之前管理的bounds类型的ix

要使用旧数据中的信息更新数组,我们可以修改旧数组的assocs以包含新信息。具体来说,这意味着extend1A看起来有点像

extend1A ary0 = array bounds1 (priorInformation ++ extraInformation) where
  priorInformation = assocs ary0
  extraInformation = ...
  ((minX, minY), (maxX, maxY)) = bounds ary0
  (maxX1, maxY1) = (succ maxX, succ maxY)
  bounds1 = ((minX, minY, (maxX1, maxY1))

如果extraInformation为空([]),则extend1A ary在输入ary和{{1}范围内的所有指标上等于ary在其范围之外的所有范围内。我们需要在undefined中填写总和信息。

extraInformation

如果我们考虑将数组扩展为三个部分,extend1A ary0 = array bounds1 (priorInformation ++ extraInformation) where priorInformation = assocs ary0 extraInformation = xExtension ++ yExtension ++ totalExtension xExtension = ... yExtension = ... totalExtension = ... ((minX, minY), (maxX, maxY)) = bounds ary0 (maxX1, maxY1) = (succ maxX, succ maxY) bounds1 = ((minX, minY, (maxX1, maxY1)) 标记为xExtensionab标记为cdextend1标记为{{ {}}中的{}}和yExtension以及acbd标记的extend1我们可以依次计算每个部分。

totalExtension最简单 - 它只是abcd中每个extend1对的“值组件”的总和。它也可以是totalExtension(i,a)的“价值成分”的总和,但为了尽可能明显正确,我们将选择第一个选择并安装在较低位置 - 角落。

priorInformation

请注意,我们可以使用xExtension子句定义新的函数,例如yExtension,这些函数会反复出现。

然后我们可以将扩展计算为extend1A ary0 = array bounds1 (priorInformation ++ extraInformation) where priorInformation = assocs ary0 extraInformation = xExtension ++ yExtension ++ totalExtension sumValues asscs = sum (map snd asscs) xExtension = ... yExtension = ... totalExtension = [((maxX1, maxY1), sumValues priorInformation)] ((minX, minY), (maxX, maxY)) = bounds ary0 (maxX1, maxY1) = (succ maxX, succ maxY) bounds1 = ((minX, minY), (maxX1, maxY1)) 上的列表推导。我们需要在旧的关联上收集一种特定的总和 - 对一个索引固定的所有值进行求和。

where

然后我们完成了。它效率不高,但所有部分都在一起工作。

sumValues

答案 1 :(得分:2)

使用hmatrix:

import Numeric.LinearAlgebra
import Numeric.LinearAlgebra.Util(col,row)

m = (2><2) [1 , 2
           ,3 , 4] :: Matrix Double


extend m = fromBlocks [[m ,sr]
                      ,[sc, s]]
  where
    sr = col $ map sumElements (toRows m)
    sc = row $ map sumElements (toColumns m)
    s  = scalar (sumElements sr)


main = do
    print m
    print $ extend m


(2><2)
 [ 1.0, 2.0
 , 3.0, 4.0 ]
(3><3)
 [ 1.0, 2.0,  3.0
 , 3.0, 4.0,  7.0
 , 4.0, 6.0, 10.0 ]

答案 2 :(得分:1)

首先,您应该了解如何与haskell数组进行交互。 Array数据类型位于Data.Array中,因此有关详细信息,请查看该模块的文档。

请注意,我省略了您在所有这些函数中找到的Ix i约束。这种情况并不重要。

bounds :: Array i e -> (i, i):此函数返回数组的最小和最大索引。对于1D阵列,这些只是数字。对于2D阵列,它是左上角和右下角(对于矩阵)。

array :: (i, i) -> [(i, e)] -> Array i e:此函数从边界的最小/最大对创建数组,以及关联列表;也就是说,从索引到值的映射。您的初始示例可以写为array ((0,0),(1,1)) [((0,0),1),((0,1),2),((1,0),3),((1,1),4)]

assocs :: Array i e -> [(i, e)]:这是array的“对立面”。所以,arr == array (bounds arr) (assocs arr)

现在功能:

extendArray :: Num e => Array (Int, Int) e -> Array (Int, Int) e
extendArray arr = 
    let
      arr' = assocs arr
      ((xMin, yMin), (xMax, yMax)) = bounds arr
      newCol = [ ((n, yMax + 1) , sum [ v | ((x,_),v) <- arr', x == n] ) | n <- [xMin .. xMax]]
      newRow = [ ((xMax + 1, n) , sum [ v | ((_,y),v) <- arr', y == n] ) | n <- [yMin .. yMax]]
      newCorner = [((xMax + 1, yMax + 1), sum $ map snd arr')]
      newArr = array ((xMin, yMin), (xMax + 1, yMax + 1)) (arr' ++ newCol ++ newRow ++ newCorner)

    in newArr

让我们分解这里发生的事情。 let语句中的前两行现在应该是不言自明的。第三行newCol是神奇发生的地方。它使用列表推导,因此如果您不熟悉它们,请参阅here

n <- [xMin .. xMax]:此部分获取所有可能的x值。

[ v | ((x,_),v) <- arr', x == n]:对于列表arr'中的所有值,读取为:索引的x坐标等于n,返回该坐标处的值。

((n, yMax + 1) , sum ...创建一个新索引,其列为1加上旧数组的最大值(因此旧矩阵右侧为1列),其行为n,其值为上一行的总和(即 - 行n中所有值的总和)。

newRow的工作方式相同,但行和列相反。

newCorner获取矩阵中的所有值,计算它们的总和,并将此值放在新数组中的相应索引处。

newArr只是将所有步骤组合成一个新数组。显然,每个方向的界限都会增加一个。新数组的关联将包括所有旧关联,以及我们计算的新关联。

所以:

>let x = array ((0,0),(1,1)) [((0,0),1),((0,1),2),((1,0),3),((1,1),4)]
>x
array ((0,0),(1,1)) [((0,0),1),((0,1),2),((1,0),3),((1,1),4)]
>extendArray x
array ((0,0),(2,2)) [((0,0),1),((0,1),2),((0,2),3),((1,0),3),((1,1),4),((1,2),7),((2,0),4),((2,1),6),((2,2),10)]

请注意,此实现效率不高(但很简单)。在newRow中,我们遍历整个矩阵xMax - xMin次(每个n值一次)。由于assocs始终以相同的顺序(从左到右的行,从上到下的列)返回元素,因此最好将列表arr'拆分为每个长度为yMax - yMin的列表;这会给我们一个行列表。但我会把这个优化留给你。