Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting all the diagonals of a matrix in Haskell

Tags:

haskell

matrix

The two-dimensional list looks like:

1 | 2 | 3
- - - - -
4 | 5 | 6
- - - - -
7 | 8 | 9

Or in pure haskell

[ [1,2,3], [4,5,6], [7,8,9] ]

The expected output for diagonals [ [1,2,3], [4,5,6], [7,8,9] ] is

[ [1], [4, 2], [7, 5, 3], [8, 6], [9] ]

Writing allDiagonals (to include anti-diagonals) is then trivial:

allDiagonals :: [[a]] -> [[a]]
allDiagonals xss = (diagonals xss) ++ (diagonals (rotate90 xss))

My research on this problem

Similar question here at StackOverflow

  • Python this question is about the same problem in Python, but Python and Haskell are very different, so the answers to that question are not relevant to me.

  • Only one This question and answer are in Haskell, but are only about the central diagonal.

Hoogle

Searching for [[a]] -> [[a]] gave me no interesting result.

Independent thinking

I think the the indexing follows a kind of count in base x where x is the number of dimensions in the matrix, look at:

1 | 2
- - -
3 | 4

The diagonals are [ [1], [3, 2], [4] ]

  • 1 can be found at matrix[0][0]
  • 3 can be found at matrix[1][0]
  • 2 can be found at matrix[0][1]
  • 1 can be found at matrix[1][1]

That is similar to counting in base 2 up to 3, that is the matrix size minus one. But this is far too vague to be translated into code.

like image 504
Caridorc Avatar asked Sep 08 '15 19:09

Caridorc


People also ask

How do you extract the diagonal elements of a matrix?

D = diag( v ) returns a square diagonal matrix with the elements of vector v on the main diagonal. D = diag( v , k ) places the elements of vector v on the k th diagonal. k=0 represents the main diagonal, k>0 is above the main diagonal, and k<0 is below the main diagonal.

How many diagonals a matrix has?

There are two diagonals in any square, so number of what you call diagonals in row * col matrix are just 2 times the value that you get with teachers function.

How do you find the diagonal of a element?

An element aij of a matrix A = [aij] is a diagonal elements of matrix if i = j, such as when rows and column suffixes are equal. Thus, a11 , a22 , a33, a44, … so on are diagonal elements of the matrix A = [aij].


2 Answers

Here is a recursive version, assuming that the input is always well-formed:

diagonals []       = []
diagonals ([]:xss) = xss
diagonals xss      = zipWith (++) (map ((:[]) . head) xss ++ repeat [])
                                  ([]:(diagonals (map tail xss)))

It works recursively, going from column to column. The values from one column are combined with the diagonals from the matrix reduced by one column, shifted by one row to actually get the diagonals. Hope this explanation makes sense.

For illustration:

diagonals [[1,2,3],[4,5,6],[7,8,9]]
= zipWith (++) [[1],[4],[7],[],[],...] [[],[2],[5,3],[8,6],[9]]
= [[1],[4,2],[7,5,3],[8,6],[9]]

Another version that works on the rows instead of the columns, but based on the same idea:

diagonals []       = repeat []
diagonals (xs:xss) = takeWhile (not . null) $
    zipWith (++) (map (:[]) xs ++ repeat [])
                 ([]:diagonals xss)

Compared to the specified result, the resulting diagonals are reversed. This can of course be fixed by applying map reverse.

like image 88
Tobias Weck Avatar answered Sep 19 '22 10:09

Tobias Weck


Starting with universe-base-1.0.2.1, you may simply call the diagonals function:

Data.Universe.Helpers> diagonals [ [1,2,3], [4,5,6], [7,8,9] ]
[[1],[4,2],[7,5,3],[8,6],[9]]

The implementation in full looks like this:

diagonals :: [[a]] -> [[a]]
diagonals = tail . go [] where
    -- it is critical for some applications that we start producing answers
    -- before inspecting es_
    go b es_ = [h | h:_ <- b] : case es_ of
        []   -> transpose ts
        e:es -> go (e:ts) es
        where ts = [t | _:t <- b]

The key idea is that we keep two lists: a rectangular chunk we haven't started inspecting, and a pentagonal chunk (a rectangle with the top left triangle cut out!) that we have. For the pentagonal chunk, picking out the first element from each list gives us another diagonal. Then we can add a fresh row from the rectangular, un-inspected chunk to what's left after we delete that diagonal.

The implementation may look a bit unnatural, but it's intended to be quite efficient and lazy: the only thing we do to lists is destructure them into a head and tail, so this should be O(n) in the total number of elements in the matrix; and we produce elements as soon as we finish the destructuring, so it's quite lazy/friendly to garbage collection. It also works well with infinitely large matrices.

(I pushed this release just for you: the previous closest thing you could get was using diagonal, which would only give you [1,4,2,7,5,3,8,6,9] without the extra structure you want.)

like image 42
Daniel Wagner Avatar answered Sep 20 '22 10:09

Daniel Wagner