Introdução a Haskell

Published:

Matrizes

De forma simplificada, podemos definir uma matriz no Haskell como uma lista de listas, em que cada elemento da lista principal corresponde a uma linha e cada elemento de cada uma dessas listas corresponde a uma coluna da matriz.

matriz = [ [1,2], [3,4] ]

Para trabalharmos adequadamente com as listas é importante definir funções básicas (nota: essas funções não são padrões do Haskell, porém existe como utilizar módulos externos que contém tais funções.):

tamanhoMtx      :: [[a]] -> (Int, Int)
somaMtx         :: Num a => [[a]] -> [[a]] -> [[a]]
subMtx          :: Num a => [[a]] -> [[a]] -> [[a]]
somaConstMtx    :: Num a => a -> [[a]] -> [[a]]
multConstMtx    :: Num a => a -> [[a]] -> [[a]]
transpostaMtx   :: [[a]] -> [[a]]
multMtx         :: Num a => [[a]] -> [[a]] -> [[a]]

Repare que as funções que tratam de operações numéricas são restringidas à classe Num.

A primeira função, tamanhoMtx recebe uma matriz como parâmetro e retorna uma tupla com as dimensões da matriz, basta definirmos os tamanhos m e n como o tamanho da lista externa e interna:

tamanhoMtx mtx = (m, n)
  where
    m = length mtx
    n = length $ head mtx

As funções de soma e subtração entre matrizes podem ser definidas através de uma sequência de zipWith. Simplificando o problema, imagine que temos uma função somaListas que soma duas listas:

somaListas l1 l2 = zipWith (+) l1 l2

Uma vez que os parâmetros são apenas repassados para a função zipWith, podemos definir tal função resumidamente como:

somaListas = zipWith (+)

A soma de matrizes nada mais é do que a soma de listas para cada par de lista extraída de cada uma das matrizes:

somaMtx m1 m2 = zipWith somaListas m1 m2

ou:

somaMtx = zipWith (zipWith (+))

Exercício 01: Modifique a função somaMtx para definir a função subMtx.

Para somar uma constante x a uma matriz precisamos aplicar a soma de x em cada elemento da linha para cada linha. Simplificando novamente, para somar uma constante a uma linha definimos:

somaConst x = map (+x)

E para aplicar essa função em cada linha da matriz:

somaConstMtx x = map (map (+x))

Exercício 02: Modifique a função somaConstMtx para definir a função multConstMtx.

A função transposta transforma todas as linhas em colunas. Para simplificar o problema, vamos criar uma função chamada primeiraCol que extrairá a primeira coluna de uma matriz qualquer. Dado que cada linha da matriz é representada por uma lista, a primeira coluna é a junção dos primeiros elementos de cada lista:

primeiraCol m = map head m

Da mesma forma podemos criar a função removePrimeiraCol que retorna a matriz sem a primeira coluna:

removePrimeiraCol m = map tail m

A função transposta pode ser definida facilmente como a composição da primeira coluna e a chamada recursiva sobre o restante da matriz:

transpostaMtx m = primeiraCol m : primeiraCol (removePrimeiraCol m) : primeiraCol (removePrimeiraCol (removePrimeiraCol m)) ...

Finalmente temos:

transpostaMtx ([]:_) = []
transpostaMtx m = (map head m) : transpostaMtx (map tail m)

O primeiro padrão faz-se necessário para o caso base, em que a cabeça da lista é vazia.

A função multMtx deve ser capaz de multiplicar duas matrizes, m1, m2, cujas dimensões internas são as mesmas. Basicamente, o elemento (i, j) resultante dessa multiplicação é o produto interno da linha i de m1 e da coluna j de m2. O primeiro passo é definir a função prodInterno:

prodInterno l1 l2 = sum $ zipWith (*) l1 l2

Então temos que a multiplicação pode ser definida como:

multMtx m1 m2 = [ [prodInterno (row i m1) (col j m2) | j <- [0..ncols m2 - 1]] | i <- [0..nrows m1 - 1] ]

A expressão ainda necessita de algumas funções suportes e está muito complexa. Podemos simplificar assumindo que a dimensão da matriz resultante será (m,n):

multMtx m1 m2 = [ [prodInterno (row i m1) (col j m2) | j <- [0..n]] | i <- [0..m] ]
  where
    m = fst $ tamanhoMtx m1
    n = snd $ tamanhoMtx m2

A extração da linha i de m1 pode ser feita como m1 !! i, ou seja, o i-ésimo elemento de m1. Para poder fazermos o mesmo com m2 teremos que trabalhar com sua transposta:

multMtx m1 m2 = [ [prodInterno (m1 !! i) (m2' !! j) | j <- [0..n-1]] | i <- [0..m-1] ]
  where
    m   = fst $ tamanhoMtx m1
    n   = snd $ tamanhoMtx m2
    m2' = transpostaMtx m2

Ao invés de usarmos os índices, podemos capturar cada linha diretamente com:

multMtx m1 m2 = [ [prodInterno ri rj | rj <- m2'] | ri <- m1 ]
  where
    m2' = transpostaMtx m2
    prodInterno l1 l2 = sum $ zipWith (*) l1 l2

Note que caso as dimensões internas das matrizes não sejam iguais, devemos emitir uma mensagem de erro:

multMtx m1 m2 
  | k1 /= k2  = error "Matrizes incompativeis!"
  | otherwise = [ [prodInterno ri rj | rj <- m2'] | ri <- m1 ]
  where
    m2' = transpostaMtx m2
    prodInterno l1 l2 = sum $ zipWith (*) l1 l2
    k1 = snd $ tamanhoMtx m1
    k2 = fst $ tamanhoMtx m2

Com isso temos as operações básicas para trabalharmos com matrizes em Haskell.