haskellhaskell-vector

Data.Vector.modify with nested vectors


I have a vector nested inside another. I want to use modify to update this matrix in place. So I use it for the inner vector, but do I also need to use it for the outer?


Solution

  • My suggestion from the comments still stands, if you do not need to operate on a ragged array then the usual rectangular array implementation is better. Here is a short list of drawbacks of vector of vectors:

    Nevertheless question still stands: how would you modify a vector of vectors in place. Below I'll provide an example function, which uses mutation to reverse rows of a ragged array and another function that reverses both rows and columns. Difference is that in the former we only mutate elements of each row, while in the latter we also mutate the outer boxed vector that corresponds to rows themselves:

    {-# LANGUAGE RankNTypes #-}
    import Control.Monad as M
    import Control.Monad.ST
    import Prelude as P
    import Data.Vector as V
    import Data.Vector.Generic.Mutable as VGM
    import Data.Vector.Mutable as VM
    import Data.Vector.Primitive as VP
    import Data.Vector.Primitive.Mutable as VPM
    
    raggedModifyRows ::
         VP.Prim a
      => (forall s. V.Vector (VPM.MVector s a) -> ST s ())
      -> V.Vector (VP.Vector a)
      -> V.Vector (VP.Vector a)
    raggedModifyRows action arr = runST $ do
      -- thaw will create a copy of each row, so they can be safely modified
      mvs <- V.mapM VP.thaw arr
      action mvs
      -- We are freezing mutated copies, so it is safe to use unsafeFreeze here too
      V.mapM VP.unsafeFreeze mvs
    
    raggedModify ::
         VP.Prim a
      => (forall s. VM.MVector s (VPM.MVector s a) -> ST s ())
      -> V.Vector (VP.Vector a)
      -> V.Vector (VP.Vector a)
    raggedModify action arr = runST $ do
      arr' <- V.mapM VP.thaw arr
      -- mapM already created a copy of a boxed vector, so we can use unsafeThaw
      mv <- V.unsafeThaw arr'
      action mv
      v <- V.unsafeFreeze mv
      V.mapM VP.unsafeFreeze v
    
    generateMatrix ::
         Prim a => (Int, Int) -> ((Int, Int) -> a) -> V.Vector (VP.Vector a)
    generateMatrix (m, n) f = V.generate m $ \ i -> VP.generate n $ \j -> f (i, j)
    
    generateRagged ::
         Prim a => V.Vector Int -> ((Int, Int) -> a) -> V.Vector (VP.Vector a)
    generateRagged v f = V.imap (\ i n -> VP.generate n $ \j -> f (i, j)) v
    
    reverseST :: (VGM.MVector v a) => v s a -> ST s ()
    reverseST mv =
      let n = VGM.length mv
       in M.forM_ [0 .. (n `div` 2) - 1] $ \j -> VGM.swap mv j (n - j - 1)
    
    reverseRaggedRows :: Prim a => V.Vector (VP.Vector a) -> V.Vector (VP.Vector a)
    reverseRaggedRows = raggedModifyRows $ \rows -> V.forM_ rows reverseST
    
    reverseRagged :: Prim a => V.Vector (VP.Vector a) -> V.Vector (VP.Vector a)
    reverseRagged =
      raggedModify $ \mrows -> do
        let reverse' i = VM.read mrows i >>= reverseST
        let m = VM.length mrows
        M.forM_ [0 .. (m `div` 2) - 1] $ \i -> do
          reverse' i
          VM.swap mrows i (m - i - 1)
          reverse' i
        M.when (odd m) $ reverse' (m `div` 2)
    

    Which can be used as follows:

    λ> m = generateMatrix (3, 4) $ \(i, j) -> i+j
    λ> m
    [[0,1,2,3],[1,2,3,4],[2,3,4,5]]
    λ> reverseRaggedRows m
    [[3,2,1,0],[4,3,2,1],[5,4,3,2]]
    λ> reverseRagged m
    [[5,4,3,2],[4,3,2,1],[3,2,1,0]]
    λ> m = generateRagged (V.fromList [1,2,3]) $ \(i, j) -> i+j
    λ> m
    [[0],[1,2],[2,3,4]]
    λ> reverseRaggedRows m
    [[0],[2,1],[4,3,2]]
    λ> reverseRagged m
    [[4,3,2],[2,1],[0]]
    

    Alternatively we could have used Data.Vector.modify to operate on the outer vector or map a destructive action that uses modify across all rows. There are all sorts of ways to go about it, depends on what you are trying to achieve, for example:

    λ> m = generateRagged (V.fromList [1,2,3]) $ \(i, j) -> i+j
    λ> V.map (VP.modify reverseST) m
    [[0],[2,1],[4,3,2]]
    λ> V.modify reverseST (V.map (VP.modify reverseST) m)
    [[4,3,2],[2,1],[0]]
    

    I did recommend using massiv for regular multidimensional arrays. Therefore here is also an example of how to achieve the same with withMArrayST:

    {-# LANGUAGE FlexibleContexts #-}
    import Control.Monad as M
    import Data.Massiv.Array as A
    
    reverseMatrix :: Mutable r Ix2 e => Array r Ix2 e -> Array r Ix2 e
    reverseMatrix arr =
      withMArrayST arr $ \marr -> do
        let Sz2 m n = msize marr
            ix2@(m2 :. n2) = m `div` 2 :. n `div` 2
        A.forM_ (0 ..: ix2) $ \ix@(i :. j) -> do
          A.swapM_ marr ix (m - i - 1 :. n - j - 1)
          A.swapM_ marr (i :. n - j - 1) (m - i - 1 :. j)
        when (odd m) $ A.forM_ (0 ..: n2) $ \ j ->
          A.swapM_ marr (m2 :. j) (m2 :. n - j - 1)
        when (odd n) $ A.forM_ (0 ..: m2) $ \ i ->
          A.swapM_ marr (i :. n2) (m - i - 1 :. n2)
    

    Which can be used as follows:

    λ> a = makeArrayR P Seq (Sz2 3 4) $ \ (i :. j) -> i + j
    λ> a
    Array P Seq (Sz (3 :. 4))
      [ [ 0, 1, 2, 3 ]
      , [ 1, 2, 3, 4 ]
      , [ 2, 3, 4, 5 ]
      ]
    λ> reverseMatrix a
    Array P Seq (Sz (3 :. 4))
      [ [ 5, 4, 3, 2 ]
      , [ 4, 3, 2, 1 ]
      , [ 3, 2, 1, 0 ]
      ]