TRANSPOSITION FOR THREE-BY-THREE MATRICES

Jeremy Gibbons
29th November 2005


Transposition is easy enough to write on matrices represented as
(let's say, non-empty) lists of lists, either as a fold

> trans = foldr1 (zipWith (++)) . map (map (:[]))

or as an unfold

> trans' = unfold (any null) (map head) (map tail)
> unfold p f g x = if p x then [] else f x : unfold p f g (g x)

However, nearly all reasoning about transposition revolves around
showing that is its own inverse. In fact, in general it is not: on a
non-rectangular list of lists, transposition necessarily loses
information. As you might expect, transposition turns out to be its
own inverse on rectangular lists of lists. Since rectangular lists of
lists are all and only those results returned by trans, this is
equivalent to

  trans . trans . trans = trans

Sadly, this fact turns out to be a real pig to prove. I'm sure this is
because it - in a sense I will not try to make precise - only
"accidentally" rather than "naturally" true. 

My suspicion is that it would be much easier to prove if it were
"naturally" true; for example, if the rectangularity side-condition
were expressed and enforced in the type. This script considers the
case of three-by-three matrices.


A triple has three elements of the same type.

> data Trip a = Trip a a a deriving Eq

> fst3, snd3, thd3 :: Trip a -> a
> fst3 (Trip a b c) = a
> snd3 (Trip a b c) = b
> thd3 (Trip a b c) = c

> contents :: Trip a -> [a]
> contents (Trip a b c) = [a,b,c]

> fill :: [a] -> Trip a
> fill [a,b,c] = Trip a b c

> instance Show a => Show (Trip a) where
>   show = show . contents


Various combinators on triples - triplication:

> trip :: a -> Trip a
> trip a = Trip a a a

"zip with apply":

> zap3 :: Trip (a->b) -> Trip a -> Trip b
> zap3 (Trip f g h) (Trip a b c) = Trip (f a) (g b) (h c)

and in terms of these, map:

> instance Functor Trip where
>   fmap f abc = zap3 (trip f) abc

...and fork:

> fork3 :: Trip (a->b) -> a -> Trip b
> fork3 fgh a = zap3 fgh (trip a)


Here is an example three-by-three matrix.

> m :: Trip (Trip Int)
> m = fill $ map fill $ [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]


Here is a kind of coinductive definition of transposition (in the
sense that the computation is structured after the output, like the
unfold version - even though it is not actually recursive).

> tr3 :: Trip (Trip a) -> Trip (Trip a)
> tr3 = fork3 (Trip (fmap fst3) (fmap snd3) (fmap thd3))


Now, the proof that transposition is its own inverse turns out to be
entirely straightforward. (I use some simple properties of fork3.)

  tr3 . tr3
=   { definition of tr3 }
  fork3 (Trip (fmap fst3) (fmap snd3) (fmap thd3)) . tr3
=   { fork fusion }
  fork3 (Trip (fmap fst3 . tr3) (fmap snd3 . tr3) (fmap thd3 . tr3))
=   { naturality of fst3, snd3, thd3 - see below }
  fork3 (Trip fst3 snd3 thd3)
=   { fork identity }
  id

The intermediate lemma mentioned above is that if k is a natural
transformation from Trip to Id (as fst3, snd3 and thd3 all are), then

  fmap k . tr3
=   { definition of tr3 }
  fmap k . fork3 (Trip (fmap fst3) (fmap snd3) (fmap thd3))
=   { fork-map fusion: 
      fmap k . fork3 (Trip f g h) = fork3 (Trip (k . f) (k . g) (k . h)) }
  fork3 (Trip (k . fmap fst3) (k . fmap snd3) (k . fmap thd3))
=   { naturality of k }
  fork3 (Trip (fst3 . k) (snd3 . k) (thd3 . k))
=   { fork fusion }
  fork3 (Trip fst3 snd3 thd3) . fst3
=   { fork identity }
  k


As an application, consider Richard Bird's sudoku problem. He wanted
to extract the rows, columns and boxes from a sudoku board, manipulate
them, and reassemble them to reconstruct the board. We model the board
as a nonet of nonets, where a nonet is a triple of triples.

> type Nine a = Trip (Trip a)
> type Mat a = Nine (Nine a)

We might consider each row as a list, so rows should yield a list of
lists; but then it will of course require a different function (of the
converse type) as its inverse. Richard saw that if each of rows, cols
and boxs was an endofunction, then at least the type is right for them
to be their own inverses.

> rows, cols, boxs :: Mat a -> Mat a

Now, it so happens that each extraction is simply some combination of
transpositions of various of the four dimensions of this
three-to-the-fourth hypergrid. Precisely which transpositions you need
depend on precisely how you represent the nine-by-nine sudoku square
in this hypercube; but with the interpretation I chose, I get:

> rows = id
> cols = fmap tr3 . tr3 . fmap (fmap tr3) . fmap tr3
> boxs = fmap tr3

Happily, the fact that each of these is its own inverse is a corollary
of the same holding for tr3 (together with naturality of tr3), as can
easily be shown. My question for Problem-Solving Club was: can we
generalize this definition of transposition and the result that it is
its own inverse to fixed-size lists of arbitrary size?


Here is a sudoku-sized board to play with, and the results of
computing its rows, columns and boxes.

> triples :: [a] -> [Trip a] -- better have length divisible by 3!
> triples = unfold null (fill . take 3) (drop 3)

> lay :: Show a => Nine a -> IO ()
> lay = putStrLn . unlines . map show . concat . map contents . contents 

> m' :: Trip (Trip (Trip (Trip Int)))
> m' = head $ triples $ triples $ triples $ triples $ [1..81]

*Main> lay $ m'
[[1,2,3],[4,5,6],[7,8,9]]
[[10,11,12],[13,14,15],[16,17,18]]
[[19,20,21],[22,23,24],[25,26,27]]
[[28,29,30],[31,32,33],[34,35,36]]
[[37,38,39],[40,41,42],[43,44,45]]
[[46,47,48],[49,50,51],[52,53,54]]
[[55,56,57],[58,59,60],[61,62,63]]
[[64,65,66],[67,68,69],[70,71,72]]
[[73,74,75],[76,77,78],[79,80,81]]

*Main> lay $ rows m'
[[1,2,3],[4,5,6],[7,8,9]]
[[10,11,12],[13,14,15],[16,17,18]]
[[19,20,21],[22,23,24],[25,26,27]]
[[28,29,30],[31,32,33],[34,35,36]]
[[37,38,39],[40,41,42],[43,44,45]]
[[46,47,48],[49,50,51],[52,53,54]]
[[55,56,57],[58,59,60],[61,62,63]]
[[64,65,66],[67,68,69],[70,71,72]]
[[73,74,75],[76,77,78],[79,80,81]]

*Main> lay $ cols m'
[[1,10,19],[28,37,46],[55,64,73]]
[[2,11,20],[29,38,47],[56,65,74]]
[[3,12,21],[30,39,48],[57,66,75]]
[[4,13,22],[31,40,49],[58,67,76]]
[[5,14,23],[32,41,50],[59,68,77]]
[[6,15,24],[33,42,51],[60,69,78]]
[[7,16,25],[34,43,52],[61,70,79]]
[[8,17,26],[35,44,53],[62,71,80]]
[[9,18,27],[36,45,54],[63,72,81]]

*Main> lay $ boxs m'
[[1,2,3],[10,11,12],[19,20,21]]
[[4,5,6],[13,14,15],[22,23,24]]
[[7,8,9],[16,17,18],[25,26,27]]
[[28,29,30],[37,38,39],[46,47,48]]
[[31,32,33],[40,41,42],[49,50,51]]
[[34,35,36],[43,44,45],[52,53,54]]
[[55,56,57],[64,65,66],[73,74,75]]
[[58,59,60],[67,68,69],[76,77,78]]
[[61,62,63],[70,71,72],[79,80,81]]

