如何快速将带状矩阵的指数映射到一维数组?

时间:2017-09-28 18:46:40

标签: haskell matrix

这与问题密切相关:How to map the indexes of a matrix to a 1-dimensional array (C++)?

我需要为带状矩阵中的每个非零元素分配一个可逆索引 在正常的完整矩阵中,很容易做到:

           |-------- 5 ---------|
  Row      ______________________   _ _
   0      |0    1    2    3    4 |   |
   1      |5    6    7    8    9 |   4
   2      |10   11   12   13   14|   |
   3      |15   16   17   18   19|  _|_
          |______________________|
Column     0    1    2    3    4 

要查找数组索引,我们只使用以下双射公式:

matrix[ i ][ j ] = array[ i*m + j ]

在我的例子中,我们有一个对称带状矩阵,对角线的距离有一些约束。例如,以下使用1的上限和下限:

           |-------- 5 ---------|
  Row      ______________________   _ _
   0      |0    1    X    X    X |   |
   1      |2    3    4    X    X |   4
   2      |X    5    6    7    X |   |
   3      |X    X    8    9    10|  _|_
          |______________________|
Column     0    1    2    3    4 

在这种情况下,我想为带宽内的每个元素分配索引位置,并忽略外部的所有内容。有两种方法可以做到这一点,其中一种方法是创建所有可接受的索引ix's的列表,然后使用地图查找在(row,col)对和a之间快速来回切换奇异指数:

ix's :: [(Int,Int)] -- List of all valid indices 

lkup :: Map (Int,Int) Int
lkup = M.fromList $ zip ix's [0..]

rlkup :: Map Int (Int, Int)
rlkup = M.fromList $ zip [0..] ix's

fromTup :: (Int, Int) -> Int
fromTup tup = fromMaybe 0 $ M.lookup tup lkup

toTup :: Int -> (Int, Int)
toTup i = fromMaybe (0,0) $ M.lookup i rlkup

对于大型矩阵,这会导致大量的地图查找,从而导致瓶颈。是否有更有效的公式在有效地址k(row,col)对之间进行转换?

1 个答案:

答案 0 :(得分:1)

您可能会发现在矩阵的开头和结尾“浪费”一些索引更为直接,因此请分配:

  Row         ______________________   _ _
   0    (0)  |1    2    X    X    X |   |
   1         |3    4    5    X    X |   4
   2         |X    6    7    8    X |   |
   3         |X    X    9   10   11 |  _|_
             |______________________|
Column        0    1    2    3    4 

其中(0)是被忽略的索引。

这类似于备受推崇的LAPACK库使用的band matrix representation

在执行可能影响已使用元素的操作时,您只需要注意未使用的元素被正确忽略。 (例如,可以编写快速填充例程而不考虑使用或未使用哪些元素;但矩阵乘法需要更多关注。)

如果采用这种方法,则双射非常简单:

import Data.Char
import Data.Maybe

type Index = Int

-- |(row,col) coordinate: (0,0) is top level
type Coord = (Int, Int)

-- |Matrix dimensions: (rows, cols, edges) where edges gives
-- the count of auxiliary diagonals to *each side* of the main
-- diagonal (i.e., what you call the maximum distance), so the
-- total band width is 1+2*edges
type Dims = (Int, Int, Int)

-- |Get index for (row,col)
idx :: Dims -> Coord -> Index
idx (m, n, e) (i, j) = let w = 1+2*e in w*i+(j-i+e)

-- |Get (row,col) for index
ij :: Dims -> Index -> Coord
ij  (m, n, e) idx    = let w = 1+2*e
                           (i, j') = idx `quotRem` w
                       in (i, j'+i-e)

--
-- test code
--

showCoords :: Dims -> [(Coord, Char)] -> String
showCoords (m, n, _) cs =
  unlines $
  for [0..m-1] $ \i ->
    for [0..n-1] $ \j ->
      fromMaybe '.' $ lookup (i,j) cs
  where for = flip map

test :: Dims -> IO ()
test dm@(m,n,_) = do
  putStrLn $ "Testing " ++ show dm
  let idxs = [0..]
  -- get valid index/coordinates for this matrix
  let cs = takeWhile (\(_, (i,j)) -> i<m || j<n)
           $ filter (\(_, (i,j)) -> i>=0 && j>=0)
           $ map (\ix -> (ix, ij dm ix)) idxs
  -- prove the coordinates are right
  putStr $ showCoords dm (map (\(ix, (i,j)) -> ((i,j), chr (ord 'A' + ix))) cs)
  -- prove getIndex inverts getCoord
  print $ all (\(ix, (i,j)) -> idx dm (i,j) == ix) cs
  putStrLn ""

main = do test (4, 5, 1)   -- your example
          test (3, 8, 2)   -- another example