Algebra lineare: matrici

In matematica, una matrice è uno schieramento rettangolare di oggetti.

Sulle matrici sono definite alcune operazioni e le matrici vengono usate in diversi campi della matematica.

In questo lavoro vedremo come realizzare delle matrici in Python. Prima di immergerci nel nostro lavoro, esploriamo alcuni elementi di Python che saranno utili strumenti con i quali realizzare le matrici.

Python

Liste

Python non ha un oggetto che corrisponda esattamente ad una matrice o ad un array a due dimensioni. Lo si può emulare con una lista di liste. Una lista è una sequenza ordinata di oggetti, la si può creare ponendo tra parentesi quadre gli oggetti della lista separati da virgole:


In [1]:
lista01 = ['pippo', 4, 5.7, ['lista', 'di', 'parole']]

All'identificatore lista01 è associata una lista che contiene quattro elementi:

  1. una stringa;
  2. un numero intero;
  3. un numero in virgola mobile;
  4. una lista di stringhe.

Le liste sono indicizzabili, teniamo presente che l'indice del primo elemento di una lista è sempre il numero zero.


In [2]:
print(lista01[0])
print(lista01[1])
print(lista01[2])
print(lista01[3])
print(lista01[3][0])
print(lista01[3][1])
print(lista01[3][2])


pippo
4
5.7
['lista', 'di', 'parole']
lista
di
parole

Per creare liste numeriche è utile la funzione range che restituisce, uno alla volta i numeri naturali:


In [3]:
for elemento in range(5):
    print(elemento)


0
1
2
3
4

Le liste si possono creare ultilizzando una sintassi molto sintetica detta "listcompreension". Ad esempio se volessi una lista che contiene tutti i numeri naturali minori di 5 potrei scrivere:


In [6]:
[elemento for elemento in range(5)]


Out[6]:
[0, 1, 2, 3, 4]

Se volessi una lista con i quadrati dei primi dieci naturali:


In [7]:
[elemento**2 for elemento in range(5)]


Out[7]:
[0, 1, 4, 9, 16]

E se volessi una lista con i primi cinque naturali, i loro quadrati e i loro cubi:


In [9]:
[[elemento, elemento**2, elemento**3] for elemento in range(5)]


Out[9]:
[[0, 0, 0], [1, 1, 1], [2, 4, 8], [3, 9, 27], [4, 16, 64]]

Possiamo anche creare una lista di numeri interi casuali, per fare questo dobbiamo però caricare una particolare funzione dalla libreria "random":


In [5]:
import random
lista05 = [random.randrange(10) for _ in range(20)]
print(lista05)


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

Una lista di stringhe può essere trasformata in una stringa concatenando tutti i suoi elementi:


In [11]:
lista06 = ['Et', 'telefono', 'casa']
' '.join(lista06)


Out[11]:
'Et telefono casa'

Per eseguire le operazioni tra matrici, avremo bisogno anche di accoppiare elementi tratti da due liste diverse. L'iteratore "zip" fa esattamente questo. Se vogliamo ottenere una lista con le somme dei numeri contenuti in due altre liste, possiamo utilizzare la listcompreension con l'iteratore zip:


In [12]:
lista07 = [random.randrange(10, 50) for _ in range(10)]
lista08 = [random.randrange(10, 50) for _ in range(10)]
somma = [a + b for a, b in zip(lista07, lista08)]
print(lista07)
print(lista08)
print(somma)


[25, 34, 20, 13, 36, 24, 44, 31, 40, 30]
[47, 25, 46, 29, 15, 41, 16, 49, 43, 22]
[72, 59, 66, 42, 51, 65, 60, 80, 83, 52]

Ora che abbiamo preso un po' di confidenza con le lista Python, possiamo emulare la matrice usando una lista di sottoliste dove ogni sottolista rappresenta una riga della matrice:


In [6]:
matr01 = [[1, 4, 6], [2, 3, 7]]
print(matr01)
matr02 = [[random.randrange(10) for _ in range(3)], [random.randrange(10) for _ in range(3)]]
print(matr02)
matr02 = [[random.randrange(10) for _ in range(3)] for _ in range(2)]
print(matr02)


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

Tenendo presente che in Python gli indici iniziano sempre da zero, si uò estrarre o modificare un elemento della matrice utilizzando gli indici. Ad esempio per estrarre il numero 7 dalla matrice precedente bisogna prendere dalla riga numero uno l'elemento numero due.


In [7]:
matr01[1][2]


Out[7]:
7

Allo stesso modo, cioè sempre usando gli indici è anche possibile cambiare un elemento di una matrice:


In [15]:
matr01[1][2] = 9
print(matr01)


[[1, 4, 6], [2, 3, 9]]

In Python possiamo realizzare delle matrici usando liste di liste, ma le matrici matematiche che vogliamo utilizzare devono avere qualcosa di più: con le nostre matrici vogliamo anche poter eseguire alcune operazioni. Vogliamo poter confrontarle, addizionarle, moltiplicarle, ...


In [8]:
matr03 = [[1, 4, 6], [2, 3, 9]]
print(matr03)
print(matr01 == matr03)
print(matr01 + matr03)


[[1, 4, 6], [2, 3, 9]]
False
[[1, 4, 6], [2, 3, 7], [1, 4, 6], [2, 3, 9]]

Il confronto funziona, ma la somma non ci va bene: estende la matrice invece che addizionare gli elementi. Dobbiamo quindi creare noi un nuovo oggetto ch si comporti come una matrice.

Funzioni

Prima di creare una matrice creiamo alcune funzioni che ci saranno utili:

  • una funzione che crea una matrice zero;
  • una funzione che crea una matrice, di date dimensioni, composta da numeri casuali;
  • una funzione che sommi due vettori;
  • una funzione che calcoli il prodotto scalare di due vettori.

Scriviamo le seguenti funzioni e controlliamo che i risultati forniti siano corretti.


In [18]:
def matr_zero(rows, cols):
    """Return a bidimensional matrix of zeros."""
    pass
    
def matr_random(rows, cols, mi, ma):
    """Return a bidimensional matrix of random integers."""
    pass

def vect_add(matr1, matr2):
    """Return vectors sum."""
    pass

def vect_scalar_product(matr1, matr2):
    """Return vectors scalar product."""
    pass

print(matr_zero(4, 5))
print(matr_random(4, 5, 10, 10))
print(vect_add([3, 4, 5], [4, 3, 2]))
print(vect_scalar_product([3, 4, 5], [4, 3, 2]))


None
None
None
None

Dopo aver scritto le funzioni e averne controllato la correttezza, siamo ora in grado di creare il nuovo tipo Matrix. Matrix è il nome associato alla classe che vogliamo realizzare

Classi

In Python è possibile definire un nuovo tipo di dati con tutti i suoi comportamenti, lo si fa definendo una nuova classe:


In [19]:
class Matrix():
    """Classe che implementa le matrici."""

È possibile realizzare un oggetto matrice con il seguente comando:


In [20]:
matr02 = Matrix()
print(matr02)


<__main__.Matrix object at 0x7f439413ec50>

L'oggetto matr02 viene creato, è di tipo matrice, ma questo oggetto non ha molte proprietà, anzi non ne abbiamo definite per niente.

Per dare delle proprietà alla nostra classe dobbiamo definire attributi che sono variabili legate all'oggetto e metodi che sono funzioni legate alla classe.

Aggiungiamo un attributo che contenga la matrice-lista, un attributo per il numero di righe e uno per il numero di colonne della matrice.

La classe Matrix avrà un primo metodo __init__ che viene eseguito quando viene costruito l'oggetto. Questo metodo ha due parametri:

  • self è un riferimento all'oggetto;
  • matr contiene la matrice sotto forma di lista.

Il metodo __init__ lega all'attributo matr dell'oggetto che si sta creando, la lista matr passata come argomento e agli attributi rows e cols rispettivamente il numero di righe e di colonne della matrice.


In [21]:
class Matrix():
    """Matrix class."""
    def __init__(self, matr):      # Metodo che inizializza l'oggetto.
        self.matr = matr           # Attributo che contiene la matrice.
        self.rows = len(matr)      # numero di righe
        self.cols = len(matr[0])   # numero di colonne
        
matr02 = Matrix([[1, 2], [2, 3], [3, 4], [4, 5]])
print('righe:', matr02.rows, 'colonne:', matr02.cols)


righe: 4 colonne: 2

Aggiungiamo anche un metodo che trasformi la matrice in una stringa separando gli elementi di ogni riga con un tabulatore e le righe con un acapo.


In [22]:
class Matrix():
    """Matrix class."""
    def __init__(self, matr):      # Metodo che inizializza l'oggetto.
        self.matr = matr           # Attributo che contiene la matrice.
        self.rows = len(matr)      # numero di righe
        self.cols = len(matr[0])   # numero di colonne
        
    def __str__(self):
        """Matrix to string."""
        return "\n".join(["\t".join(map(str, r)) for r in self.matr])

m_0 = Matrix([[1, 2], [2, 3], [3, 4], [4, 5]])
print(m_0)


1	2
2	3
3	4
4	5

Possiamo ora indicare a Python come sommare due oggetti Matrice. Il programma diventa:


In [23]:
def vect_add(v0, v1):
    """Return vectors sum."""
    return [a+b for a, b in zip(v0, v1)]

class Matrix():
    """Matrix class."""
    def __init__(self, matr):      # Metodo che inizializza l'oggetto.
        self.matr = matr           # Attributo che contiene la matrice.
        self.rows = len(matr)      # numero di righe
        self.cols = len(matr[0])   # numero di colonne
        
    def __str__(self):
        """Matrix to string."""
        return "\n".join(["\t".join(map(str, r)) for r in self.matr])
    
    def __add__(self, other):
        """Matrix sum."""
        return Matrix([vect_add(a, b) for a, b in zip(self.matr, other.matr)])

m_a = Matrix([[2, 3, 4], [0, 2, 3]])
m_b = Matrix([[1, 2, 3], [4, 5, 6]])
print(m_a + m_b)


3	5	7
4	7	9

E se le matrici che vogliamo addizionare non hanno le stesse dimensioni?


In [25]:
m_a = Matrix([[2, 3, 4, 6], [0, 2, 3, 4]])
m_b = Matrix([[1, 2, 3], [4, 5, 6]])
print(m_a + m_b)


3	5	7
4	7	9

Il risultato che otteniamo è sbagliato, bisogna controllare le dimensioni, eseguire l'addizione se è possibile farlo, altrimenti sollevare un'eccezione.


In [26]:
def vect_add(v0, v1):
    """Return vectors sum."""
    return [a+b for a, b in zip(v0, v1)]

class Matrix():
    """Matrix class."""
    def __init__(self, matr):      # Metodo che inizializza l'oggetto.
        self.matr = matr           # Attributo che contiene la matrice.
        self.rows = len(matr)      # numero di righe
        self.cols = len(matr[0])   # numero di colonne
        
    def __str__(self):
        """Matrix to string."""
        return "\n".join(["\t".join(map(str, r)) for r in self.matr])
    
    def __add__(self, other):
        """Matrix sum."""
        if self.rows == other.rows and self.cols == other.cols:
            return Matrix([vect_add(a, b) for a, b in zip(self.matr, other.matr)])
        else:           # raises an exception if the matrixes are not the same size.
            raise Exception("Matrixes can't be added.")

m_a = Matrix([[2, 3, 4], [0, 2, 3]])
m_b = Matrix([[1, 2, 3], [4, 5, 6]])
print(m_a + m_b)

m_a = Matrix([[2, 3, 4, 6], [0, 2, 3, 4]])
m_b = Matrix([[1, 2, 3], [4, 5, 6]])
print(m_a + m_b)


3	5	7
4	7	9
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-26-a9bfb03fd2ce> in <module>()
     27 m_a = Matrix([[2, 3, 4, 6], [0, 2, 3, 4]])
     28 m_b = Matrix([[1, 2, 3], [4, 5, 6]])
---> 29 print(m_a + m_b)

<ipython-input-26-a9bfb03fd2ce> in __add__(self, other)
     19             return Matrix([vect_add(a, b) for a, b in zip(self.matr, other.matr)])
     20         else:           # raises an exception if the matrixes are not the same size.
---> 21             raise Exception("Matrixes can't be added.")
     22 
     23 m_a = Matrix([[2, 3, 4], [0, 2, 3]])

Exception: Matrixes can't be added.

Giustamente la seconda operazione prduce un errore.

Ora abbiamo tutti (quasi) gli elementi per scrivere una libreria che supporti il calcolo con le matrici.

Libreria per il calcolo con le matrici

Capiti i concetti di base, possiamo scrivere una libreria che implementi il calcolo con le matrici.

Partiamo da un file vuoto e impostiamo il lavoro. Lo salviamo con il nome "matrix.py", scriviamo una breve intestazione con qualche informazione esseziale: data, titolo, autore, licenza, ...

Scriviamo un breve testo che illustri lo scopo della libreria.

E importiamo qualche libreria che ci tornerà utile.


In [17]:
# 30 marzo 2015
# Daniele
# Matrici
# GPL 3

"""
class Matrix with operations.
"""

from __future__ import division, print_function   # for compatibility with 2.x
import random

Ora definiamo le funzioni di supporto e il programma principale. Dato che quella che vogliamo scrivere è una libreria che verrà chiamata da altri programmi, il programma principale potrebbe anche essere vuoto, costituito dalla sola istruzione pass o mancare del tutto. Ma noi vogliamo scrive un programma seguendo un metodo ben preciso quindi nel programma principale carichiamo una libreria e ne avviamo una funzione.


In [2]:
def matr_zero(rows, cols):
    """Return a bidimensional matrix of zeros."""
    return [[0 for i in range(cols)] for r in range(rows)]

if __name__=='__main__':
    import doctest
    doctest.testmod(verbose=True)


2 items had no tests:
    __main__
    __main__.matr_zero
0 tests in 2 items.
0 passed and 0 failed.
Test passed.

Test Driven Development

Il TDD è lo sviluppo del software guidato dai test. L'idea di fondo è che prima di scrivere ogni singola funzione del programma, definisco il test che quella funzione dovrà passare.

Nel caso della funzione matr_zero, noi vogliamo poter creare una lista di liste che contengono tutti zeri e vogliamo poterla stampare. Queste condizioni che la funzione deve soddisfare le inseriamo nella stringa di documentazione della funzione con anche il risultato atteso.


In [3]:
def matr_zero(rows, cols):
    """Return a bidimensional matrix of zeros.
>>> m_0 = matr_zero(3, 4)
>>> print(m_0)
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
    """
    return [[0 for i in range(cols)] for r in range(rows)]

if __name__=='__main__':
    import doctest
    doctest.testmod(verbose=True)


Trying:
    m_0 = matr_zero(3, 4)
Expecting nothing
ok
Trying:
    print(m_0)
Expecting:
    [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
ok
1 items had no tests:
    __main__
1 items passed all tests:
   2 tests in __main__.matr_zero
2 tests in 2 items.
2 passed and 0 failed.
Test passed.

Possiamo osservare che sono stati eseguiti due test, uno per ogni istruzione che inizia con >>> e che non hanno dato errori.

Vediamo che doctest ci dice quali test sono passati e quali sono falliti, ma sebbene questo generalmente sia molto incoraggiante, noi siamo più interessati a sapere quali test falliscono e quindi nelle prossime esecuzioni mettiamo a False il parametro verbose.

Funzioni

Riprendiamo la scrittura della nostra libreria. Inseriamo le quattro funzioni sviluppate precedentemente:

  • matr_zero(rows, cols):
  • matr_random(rows, cols, mi, ma):
  • vect_add(matr1, matr2):
  • vect_scalar_product(matr1, matr2):

Questa volta, come abbiamo fatto per matr_zero, aggiungiamo per ognuna una doc string che contenga almeno un test.

Procediamo con la funzione che crea una lista di liste di numei casuali specificando le dimensioni della matrice e il range di numeri. Come sempre scriviamo l'intestazione della funzione, la stringa di documentazione (docstring) e il test.


In [4]:
def matr_random(rows, cols, mi, ma):
    """Return a bidimensional matrix of random numbers.
>>> m_0 = matr_random(3, 4)
>>> print(m_0)
[[3, 6, 4, 9], [1, 5, 8, 6], [3, 7, 0, 9]]
    """

if __name__=='__main__':
    import doctest
    doctest.testmod(verbose=False)


**********************************************************************
File "__main__", line 3, in __main__.matr_random
Failed example:
    m_0 = matr_random(3, 4)
Exception raised:
    Traceback (most recent call last):
      File "/usr/lib/python3.4/doctest.py", line 1324, in __run
        compileflags, 1), test.globs)
      File "<doctest __main__.matr_random[0]>", line 1, in <module>
        m_0 = matr_random(3, 4)
    TypeError: matr_random() missing 2 required positional arguments: 'mi' and 'ma'
**********************************************************************
File "__main__", line 4, in __main__.matr_random
Failed example:
    print(m_0)
Exception raised:
    Traceback (most recent call last):
      File "/usr/lib/python3.4/doctest.py", line 1324, in __run
        compileflags, 1), test.globs)
      File "<doctest __main__.matr_random[1]>", line 1, in <module>
        print(m_0)
    NameError: name 'm_0' is not defined
**********************************************************************
1 items had failures:
   2 of   2 in __main__.matr_random
***Test Failed*** 2 failures.

Ovviamente il test fallisce. Osserviamo gli errori che segnala. Il primo è dovuto al fatto che la funzione matr_random ha due parametri che abbiamo dimenticato. Il secondo deriva dal fatto che non abbiamo definito la funzione, ma solo la sua docstring. Scrivere il codice della funzione che sarà molto simile a quello di matr_zero.


In [5]:
def matr_random(rows, cols, mi, ma):
    """Return a bidimensional matrix of random numbers.
>>> m_0 = matr_random(3, 4, 0, 10)
>>> print(m_0)
[[3, 6, 4, 9], [1, 5, 8, 6], [3, 7, 0, 9]]
    """
    return [[random.randrange(mi, ma) for i in range(cols)] for r in range(rows)]

if __name__=='__main__':
    import doctest
    doctest.testmod(verbose=False)


**********************************************************************
File "__main__", line 4, in __main__.matr_random
Failed example:
    print(m_0)
Expected:
    [[3, 6, 4, 9], [1, 5, 8, 6], [3, 7, 0, 9]]
Got:
    [[9, 0, 9, 3], [4, 1, 6, 5], [3, 2, 1, 2]]
**********************************************************************
1 items had failures:
   1 of   2 in __main__.matr_random
***Test Failed*** 1 failures.

La funzione è corretta, ma il test fallisce perché non riusciamo a prevedere quali numeri si inventerà la funzione randrange. Ma possiamo dire a doctest di considerare questo fatto usando la direttiva: +ELLIPSIS e i tre puntini per i numeri che possono variare.


In [6]:
def matr_random(rows, cols, mi, ma):
    """Return a bidimensional matrix of random numbers.
>>> m_0 = matr_random(3, 4, 0, 10)
>>> print(m_0)                      # doctest: +ELLIPSIS
[[..., ..., ..., ...], [..., ..., ..., ...], [..., ..., ..., ...]]
    """
    return [[random.randrange(mi, ma) for i in range(cols)] for r in range(rows)]

if __name__=='__main__':
    import doctest
    doctest.testmod(verbose=False)

Questa volta il test passa.

Scriviamo ora il test per le funzioni vect_add e vect_mul.


In [7]:
def vect_add(v0, v1):
    """Return vectors sum.
>>> print(vect_add([2, 4, 6], [3, 5, 0]))
[5, 9, 6]
    """

def vect_mul(v0, v1):
    """Return vectors scalar product.
>>> print(vect_mul([2, 4, 6], [3, 5, 0]))
26
    """
if __name__=='__main__':
    import doctest
    doctest.testmod(verbose=False)


**********************************************************************
File "__main__", line 3, in __main__.vect_add
Failed example:
    print(vect_add([2, 4, 6], [3, 5, 0]))
Expected:
    [5, 9, 6]
Got:
    None
**********************************************************************
File "__main__", line 9, in __main__.vect_mul
Failed example:
    print(vect_mul([2, 4, 6], [3, 5, 0]))
Expected:
    26
Got:
    None
**********************************************************************
2 items had failures:
   1 of   1 in __main__.vect_add
   1 of   1 in __main__.vect_mul
***Test Failed*** 2 failures.

Veniamo avvisati che due test non sono passati.

Ottimo!

Andiamo a completare le due funzioni in modo che superino i test.


In [8]:
def vect_add(v0, v1):
    """Return vectors sum.
>>> print(vect_add([2, 4, 6], [3, 5, 0]))
[5, 9, 6]
    """
    return [a+b for a, b in zip(v0, v1)]

def vect_mul(v0, v1):
    """Return vectors scalar product.
>>> print(vect_mul([2, 4, 6], [3, 5, 0]))
26
    """
    return sum([a*b for a, b in zip(v0, v1)])

if __name__=='__main__':
    import doctest
    doctest.testmod(verbose=False)

Benissimo, il nostro programma sta prendendo forma ed è già così robusto da superare i test.

La classe Matrix

È ora di realizzare la classe Matrix. Aggiungiamo l'intestazione della classe e il suo metodo __init__.


In [9]:
class Matrix():
    """Matrix class."""
    def __init__(self, matr):
        """Matrix init.
>>> m_0 = Matrix(matr_zero(4, 3))
>>> print(m_0.rows, m_0.cols)
4 3
>>> print(m_0.matr)
[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
        """
        self.matr = matr           # Attributo che contiene la matrice.
        self.rows = len(matr)      # numero di righe
        self.cols = len(matr[0])   # numero di colonne
        
if __name__ == '__main__':
    import doctest
    doctest.testmod(verbose=False)

Altro metodo utile da scrivere prima di passare alle operazioni con le matrici è il metodo che viene chiamato quando si vuole tradurre la matrice in una stringa, magari per stamparla. Il metodo ha un nome particolare __str__.


In [10]:
class Matrix():
    """Matrix class."""
    def __init__(self, matr):
        """Matrix init.
>>> m_0 = Matrix(matr_zero(4, 3))
>>> print(m_0.rows, m_0.cols)
4 3
>>> print(m_0.matr)
[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
        """
        self.matr = matr           # Attributo che contiene la matrice.
        self.rows = len(matr)      # numero di righe
        self.cols = len(matr[0])   # numero di colonne
        
    def __str__(self):
        """Matrix to string.
>>> m_0 = Matrix(matr_zero(2, 3))
>>> print(m_0)
0  0  0
0  0  0
        """

if __name__=='__main__':
    import doctest
    doctest.testmod(verbose=False)


**********************************************************************
File "__main__", line 18, in __main__.Matrix.__str__
Failed example:
    print(m_0)
Exception raised:
    Traceback (most recent call last):
      File "/usr/lib/python3.4/doctest.py", line 1324, in __run
        compileflags, 1), test.globs)
      File "<doctest __main__.Matrix.__str__[1]>", line 1, in <module>
        print(m_0)
    TypeError: __str__ returned non-string (type NoneType)
**********************************************************************
1 items had failures:
   1 of   2 in __main__.Matrix.__str__
***Test Failed*** 1 failures.

Giusto, abbiamo detto a Python cose deve produrre __str__, ora dobbiamo dire come ottenerlo. Vogliamo realizzare una stringa concatenando gli elementi contenuti in ogni riga separandoli con un tabulatore e concatenare le righe separandole con un acapo.


In [11]:
class Matrix():
    """Matrix class."""
    def __init__(self, matr):
        """Matrix init.
>>> m_0 = Matrix(matr_zero(4, 3))
>>> print(m_0.rows, m_0.cols)
4 3
>>> print(m_0.matr)
[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
        """
        self.matr = matr           # Attributo che contiene la matrice.
        self.rows = len(matr)      # numero di righe
        self.cols = len(matr[0])   # numero di colonne
        
    def __str__(self):
        """Matrix to string.
>>> m_0 = Matrix(matr_zero(2, 3))
>>> print(m_0)
0  0  0
0  0  0
        """
        return "\n".join(["\t".join(map(str, r)) for r in self.matr])

if __name__=='__main__':
    import doctest
    doctest.testmod(verbose=False)


**********************************************************************
File "__main__", line 18, in __main__.Matrix.__str__
Failed example:
    print(m_0)
Expected:
    0  0  0
    0  0  0
Got:
    0	0	0
    0	0	0
**********************************************************************
1 items had failures:
   1 of   2 in __main__.Matrix.__str__
***Test Failed*** 1 failures.

Accidenti, questa volta il test fallisce perché otteniamo un diverso numero di spazi. Ma gli spazi ci interessano relativamente, Possiamo farglielo sapere a doctest usando una direttiva.


In [12]:
class Matrix():
    """Matrix class."""
    def __init__(self, matr):
        """Matrix init.
>>> m_0 = Matrix(matr_zero(4, 3))
>>> print(m_0.rows, m_0.cols)
4 3
>>> print(m_0.matr)
[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
        """
        self.matr = matr           # Attributo che contiene la matrice.
        self.rows = len(matr)      # numero di righe
        self.cols = len(matr[0])   # numero di colonne
        
    def __str__(self):
        """Matrix to string.
>>> m_0 = Matrix(matr_zero(2, 3))
>>> print(m_0)                     # doctest: +NORMALIZE_WHITESPACE
0  0  0
0  0  0
        """
        return "\n".join(["\t".join(map(str, r)) for r in self.matr])

if __name__=='__main__':
    import doctest
    doctest.testmod(verbose=False)

Due metodi utili nelle matrici sono quelli che ci permettono di estrarre una riga o una colonna dati i corrispondenti indici, li chiameremo row e col.


In [13]:
class Matrix():
    """Matrix class."""
    def __init__(self, matr):
        """Matrix init.
>>> m_0 = Matrix(matr_zero(4, 3))
>>> print(m_0.rows, m_0.cols)
4 3
>>> print(m_0.matr)
[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
        """
        self.matr = matr           # Attributo che contiene la matrice.
        self.rows = len(matr)      # numero di righe
        self.cols = len(matr[0])   # numero di colonne
        
    def __str__(self):
        """Matrix to string.
>>> m_0 = Matrix(matr_zero(2, 3))
>>> print(m_0)                     # doctest: +NORMALIZE_WHITESPACE
0  0  0
0  0  0
        """
        return "\n".join(["\t".join(map(str, r)) for r in self.matr])

    def row(self, index):
        """Return a Matrix row.
>>> m_0 = Matrix([[0, 1, 2], [1, 5, 9], [2, 7, 1], [3, 0, 4]])
>>> print(m_0.row(2))
[2, 7, 1]
        """

    def col(self, index):
        """Return a Matrix col.
>>> m_0 = Matrix([[0, 1, 2], [1, 5, 9], [2, 7, 1], [3, 0, 4]])
>>> print(m_0.row(2))
[2, 9, 1, 4]
        """
        
if __name__=='__main__':
    import doctest
    doctest.testmod(verbose=False)


**********************************************************************
File "__main__", line 34, in __main__.Matrix.col
Failed example:
    print(m_0.row(2))
Expected:
    [2, 9, 1, 4]
Got:
    None
**********************************************************************
File "__main__", line 27, in __main__.Matrix.row
Failed example:
    print(m_0.row(2))
Expected:
    [2, 7, 1]
Got:
    None
**********************************************************************
2 items had failures:
   1 of   2 in __main__.Matrix.col
   1 of   2 in __main__.Matrix.row
***Test Failed*** 2 failures.

Bene, descritto cosa vogliamo ottenere, passiamo a scrivere il codice. row sarà semplicissimo: restituisci l'iesimo elemento della matrice. col risulterà più complicato: deve costruire una lista che ha come elementi gli iesimi elementi di ogni riga.


In [14]:
class Matrix():
    """Matrix class."""
    def __init__(self, matr):
        """Matrix init.
>>> m_0 = Matrix(matr_zero(4, 3))
>>> print(m_0.rows, m_0.cols)
4 3
>>> print(m_0.matr)
[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
        """
        self.matr = matr           # Attributo che contiene la matrice.
        self.rows = len(matr)      # numero di righe
        self.cols = len(matr[0])   # numero di colonne
        
    def __str__(self):
        """Matrix to string.
>>> m_0 = Matrix(matr_zero(2, 3))
>>> print(m_0)                     # doctest: +NORMALIZE_WHITESPACE
0  0  0
0  0  0
        """
        return "\n".join(["\t".join(map(str, r)) for r in self.matr])

    def row(self, index):
        """Return a Matrix row.
>>> m_0 = Matrix([[0, 1, 2], [1, 5, 9], [2, 7, 1], [3, 0, 4]])
>>> print(m_0.row(2))
[2, 7, 1]
        """
        return self.matr[index]

    def col(self, index):
        """Return a Matrix col.
>>> m_0 = Matrix([[0, 1, 2], [1, 5, 9], [2, 7, 1], [3, 0, 4]])
>>> print(m_0.col(2))
[2, 9, 1, 4]
        """
        return [row[index] for row in self.matr]
        
if __name__=='__main__':
    import doctest
    doctest.testmod(verbose=False)

Ora abbiamo tutti gli elementi per completare la nostra libreria. Im metodo da seguire prevede alcuni sempli regole:

  • aggiungere una funzione alla volta;
  • scrivere la docstring con il test della funzione;
  • scrivere il codice della funzione correggendolo finché il test non passa.

Seguendo queste indicazionicompletate la sguente libreria.


In [45]:
# 30 marzo 2015
# Daniele
# Matrici
# GPL 3

"""
class Matrix with operations.
"""

from __future__ import division, print_function   # for compatibility with 2.x
import random

def matr_zero(rows, cols):
    """Return a bidimensional matrix of zeros.
>>> m_0 = matr_zero(3, 4)
>>> print(m_0)
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
    """
    return [[0 for i in range(cols)] for r in range(rows)]

def matr_random(rows, cols, mi, ma):
    """Return a bidimensional matrix of random numbers.
>>> m_0 = matr_random(3, 4, 0, 10)
>>> print(m_0)                      # doctest: +ELLIPSIS
[[..., ..., ..., ...], [..., ..., ..., ...], [..., ..., ..., ...]]
    """
    return [[random.randrange(mi, ma) for i in range(cols)] for r in range(rows)]

def vect_add(v0, v1):
    """Return vectors sum.
>>> print(vect_add([2, 4, 6], [3, 5, 0]))
[5, 9, 6]
    """
    return [a+b for a, b in zip(v0, v1)]

def vect_mul(v0, v1):
    """Return vectors scalar product.
>>> print(vect_mul([2, 4, 6], [3, 5, 0]))
26
    """
    return sum([a*b for a, b in zip(v0, v1)])

class Matrix():
    """Matrix class."""
    def __init__(self, matr):
        """Matrix init.
>>> m_0 = Matrix(matr_zero(4, 3))
>>> print(m_0.rows, m_0.cols)
4 3
>>> print(m_0.matr)
[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
        """
        self.matr = matr           # Attributo che contiene la matrice.
        self.rows = len(matr)      # numero di righe
        self.cols = len(matr[0])   # numero di colonne
        
    def __str__(self):
        """Matrix to string.
>>> m_0 = Matrix(matr_zero(2, 3))
>>> print(m_0)                     # doctest: +NORMALIZE_WHITESPACE
0  0  0
0  0  0
        """
        return "\n".join(["\t".join(map(str, r)) for r in self.matr])

    def row(self, index):
        """Return a Matrix row.
>>> m_0 = Matrix([[0, 1, 2], [1, 5, 9], [2, 7, 1], [3, 0, 4]])
>>> print(m_0.row(2))
[2, 7, 1]
        """
        return self.matr[index]

    def col(self, index):
        """Return a Matrix col.
>>> m_0 = Matrix([[0, 1, 2], [1, 5, 9], [2, 7, 1], [3, 0, 4]])
>>> print(m_0.col(2))
[2, 9, 1, 4]
        """
        return [row[index] for row in self.matr]

    def transpose(self):
        """Return transposed Matrix.
>>> 
        """

    def __add__(self, other):
        return [[i*r for i in range(self.matr)] for r in range(self.matr)]
      
    def __neg__(self):
        """Return -Matrix.
>>> 
        """
      
    def __sub__(self, other):
        """Return difference for two matrix
>>> 
        """
    
    def __mul__(self, other):
        """Return Matrix product.
>>> 
        """

    __rmul__ = __mul__

    def determinant(self):
        """Return inverse of Matrix.
>>> 
        """

    def inverse(self):
        """Return inverse of Matrix.
>>> 
        """

    def __div__(self, other):
        """Return Matrix quotient.
>>> 
        """

if __name__=='__main__':
    import doctest
    doctest.testmod(verbose=False)

In [43]:
m = Matrix([[0, 3, 2], [1, 5, 9], [2, 7, 1], [3, 0, 4]])

In [30]:
print(m)


0	3	2
1	5	9
2	7	1
3	0	4

In [46]:
A = m+m


---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-46-ab854913680f> in <module>()
----> 1 A = m+m

<ipython-input-42-c8edf97f3407> in __add__(self, other)
     86 
     87     def __add__(self, other):
---> 88         return [[i*r for i in range(self.matr)] for r in range(self-matr)]
     89 
     90     def __neg__(self):

NameError: global name 'matr' is not defined

In [36]:
print(A)


None

In [ ]: