Anna
Anna

Reputation: 41

Haskell scripts to solve identity matrix

how to solve identity for a matrix using Haskell scripts? For example, if with this given type

 type Matrice a = [[a]]
 identity :: Int -> Maybe (Matrice Int)

How can it return the identity matrice for the given size? I know that identity matrice is a square matrice which has zero for all values, except the values on the top-left to bottom-right diagonal which are all one. With the condition of, if the size is less than 1, then the identity matrice isn't defined and Nothing is returned.

So say for example,

Prelude > identity 5
          Just [[1,0,0,0,0],[0,1,0,0,0],[0,0,1,0,0],[0,0,0,1,0],[0,0,0,0,1]]
Prelude > identity 2
          Just [[1,0],[0,1]]

I've tried

identity1 :: Int -> Int -> [Int]
identity1 a b
    | a == 0 []
    | b == 0 (1:identity (a-1) (-1))
    | otherwise = (0:identity' (a-1) (b-1))

identity2 :: Int -> Int -> Matrice Int
identity2 a b
    | b == 0 []
    | otherwise = (0:identity1 (a-1) (b-1) : identity2 a (b-1) 

Upvotes: 2

Views: 876

Answers (4)

chepner
chepner

Reputation: 531888

One short approach is to define the "infinite" identity matrix as

ii = (1 : repeat 0) : fmap (0:) ii

The first row is 1 0 0 ...; each subsequent row is the row above it with a 0 prepended to it.

It should be obvious that the first n rows of the first n columns of the infinite identity matrix is In.

1 | 0 | 0 | 0 | 0 | 0 |
--+   |   |   |   |   |
0   1 | 0 | 0 | 0 | 0 |
------+   |   |   |   |
0   0   1 | 0 | 0 | 0 |
----------+   |   |   |  ...
0   0   0   1 | 0 | 0 |
--------------+   |   |
0   0   0   0   1 | 0 |
------------------+   |
0   0   0   0   0   1 |
----------------------+
           .            .
           .              .
           .                .

Given that, we just use take to obtain the appropriate-sized sub matrix. take n applied to each row will return the first n columns, and take n applied to the result takes just the first n rows.

type Matrix a = [[a]]

identity :: Int -> Maybe (Matrix Int)
identity n | n <= 0 = Nothing
           | otherwise = let ii = (1:repeat 0) : (fmap (0:) ii)
                         in Just $ take n (take n <$> ii)

If recursively defined infinite lists tie your brain in knots, you can also just define an enlarge function that generates In+1 from In. To do so, it is convenient to assume that I0 exists and is represented as an empty list.

enlarge :: Matrix Int -> Matrix Int
enlarge [] = [[1]]
enlarge i@(r:_) = (1:(0<$r)) : fmap (0:) i

Then you can define identity :: Int -> Matrix Int by indexing an infinite list of identity matrices

identity n | n <= 0 = Nothing
identity n = Just (identities !! n)

where identities :: [Matrix Int] is built with either iterate

identities = iterate enlarge []

or Data.List.unfoldr:

identities = unfoldr (\x -> Just (x, enlarge x)) []

It's also worth noting that the infinite identity matrix is the fixed point of enlarge:

import Data.Function
ii = fix enlarge

Upvotes: 6

karakfa
karakfa

Reputation: 67507

deconstructivism

identity n = splitEvery n $ (concat $ replicate (n-1) $ 1: replicate n 0)++[1]

proof without words

[[1,0,0,0,0],[0,1,0,0,0],[0,0,1,0,0],[0,0,0,1,0],[0,0,0,0,1]]  ~
[1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1]            ~
[1,0,0,0,0,0],[1,0,0,0,0,0],[1,0,0,0,0,0],[1,0,0,0,0,0] ++ [1] ~
[1,0,0,0,0,0]{4} ++ [1]                                        ~
(1:[0]{5}){4} ++ [1]

you need to handle special cases (n<0 and n==1)

Upvotes: 1

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 477210

In case one needs some iterative approach in Haskell, recursion is used. This means that we need to define base case(s) as well as inductive case(s).

There are two base cases here:

  1. the value is less than or equal to zero, in that case the value is Nothing;
  2. the case where the value is exactly one, in that case we return a Just with a 1×1 matrix:
1

There is one inductive case: in case the number is greater than 1, we first generate the identity matrix for n-1, and then we add row at the top and a column at the left:

1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1

this means we thus need to prepend all rows of the previous matrix with 0, and we prepend the matrix with a list that contains one 1 and n-1 zeros.

Let us first forget about the first base case (n is less than or equal to zero), and assume n is always strictly positive. In that case there is no need to wrap the value in a Maybe, so we first construct a function:

identity' :: Int -> Matrix Int
identity' = ...

so the base case is where the parameter is 1:

identity' 1 = ...

and the inductive case has shape:

identity' n = first_row : map prepend_zero (identity (n-1))
    where first_row = ...
          prepend_zero = ...

Now we can construct identity in terms of identity' by only once check whether the value is less than or equal to zero:

identity :: Int -> Maybe (Matrix Int)
identity n | n <= 0 = Nothing
           | otherwise = Just (identity' n)
    where identity' 1 = ...
          identity' n = first_row : map prepend_zero (identity (n-1))
              where first_row = ...
                    prepend_zero = ...

I leave the expressions (...) as an exercise that should probably be reasonable.

Upvotes: 2

rampion
rampion

Reputation: 89093

One way to accomplish this is through recursion.

I'm going to ask you some leading questions, since I haven't seen what you've tried so far:

  • What's the identity for 1x1 matrices?
  • Given the identity for nxn matrices, what would you need to add to create the identity for (n+1)x(n+1) matrices?

Or in pseudo-code:

identity 1 = Just $ _1x1_identity
  -- _1x1_identity :: [[Int]]

identity n_plus_1 | n_plus_1 > 1 = fmap _alter_identity (identity n)
  where n = n_plus_1 - 1
  -- _alter_identity :: [[Int]] -> [[Int]]

identity n | n < 1 = Nothing

If you're unfamiliar with fmap, it's used here to unwrap/rewrap the Maybe value returned from the other call to identity.

I could do the same more verbosely as

identity n_plus_1 | n_plus_1 > 1 = case identity n of
    Nothing     -> Nothing
    Just matrix -> Just (_alter_identity matrix)
  where n = n_plus_1 - 1

Your approach in the comments attempts to construct the entire matrix row by row, which is also workable.

One way to implement that approach more directly is through a list comprehension.

List comprehensions make it easy to define new lists and lists of lists:

Prelude> [ i | i <- [0..4] ]
[0,1,2,3,4]
Prelude> [ [(i,j) | j <- [0..3]] | i <- [0..2] ]
[ [(0,0),(0,1),(0,2),(0,3)]
, [(1,0),(1,1),(1,2),(1,3)]
, [(2,0),(2,1),(2,2),(2,3)]
]

Above we can see that we can use a list comprehension to generate a matrix of coordinates - the value (i,j) shows up in the i'th row and the jth column.

List comprehensions allow you to place arbitrary expressions on the left-hand-side of the |, so I could do i + j instead of (i,j) to get a very different matrix:

Prelude> [ [i + j | j <- [0..3]] | i <- [0..2] ]
[ [0,1,2,3]
, [1,2,3,4]
, [2,3,4,5]
]

That's a rectangular matrix. A square matrix would use the same bounds for i and j.

If you were to use a list comprehension like that to create a square matrix, what expression would you put to the left hand side of the | to create the identity matrix? To put it another way, can you express the identity matrix's value at row i column j in terms of i and j?

Upvotes: 4

Related Questions