Γλώσσα Προγραμματισμού ΙΙ

Διάλεξη 24ης Μαρτίου 2015

Θέμα της σημερινής διάλεξης είναι η βιβλιοθήκη NumPy (Numeric Python) η οποία παρέχει τον τύπο ndarray καθώς και μεθόδους για την δημιουργία και επξεργασία πολυδιάστατων πινάκων. Ο ενδιαφερόμενος αναγνώστης μπορεί να βρει περισσότερες πληροφορίες για την συγκεκριμένη βιβλιοθήκη στην ιστοσελίδα www.numpy.org.

Ο προτεινόμενος τρόπος για τη χρήση της βιβλιοθήκης NumPy είναι με την οδηγία  import numpy as np. Η συνάρτηση array() μετατρέπει κάθε (φωλιασμένη) ακολουθία σε αντικείμενο τύπου array. Αν a είναι ένα αντικείμενο τύπου array τότε a.ndim είναι ο αριθμός των διαστάσεων ή αξόνων (axes) του πίνακα. a.shape είναι ένα tuple ακεραίων που δηλώνουν το μέγεθος του πίνακα σε κάθε διάσταση. Για παράδειγμα, ένας πίνακας με m γραμμές και n στήλες έχει διαστάσεις (m, n). Τέλος, a.dtype επιστρέφει τον τύπο των στοιχείων του πίνακα, π.χ. int, float ή complex.


>>> import numpy as np

>>> np.array([1, 2, 3])
array([1, 2, 3])
>>> a = np.array([ (1, 2, 3), (4, 5, 6) ])
array([[1, 2, 3],
       [4, 5, 6]])
>>> a.ndim
2
>>> a.shape
(2, 3)
>>> a = np.array([1, 2, 3], dtype=float)
>>> a.dtype
dtype('float64')

Οι συναρτήσεις zeros, ones και empty, με όρισμα ένα tuple ακεραίων που δηλώνουν το μέγεθος του πίνακα σε κάθε διάσταση, κατασκευάζουν ένα μηδενικό πίνακα, ένα πίνακα όλα τα στοιχεία του οποίου είναι ίσα με 1 και ένα πίνακα τα στοιχεία του οποίου είναι τυχαία υπό την έννοια ότι εξαρτώνται από την κατάσταση της μνήμης του υπολογιστή:


>>> np.zeros( (2, 3) )
array([[ 0.,  0.,  0.],
       [ 0.,  0.,  0.]])
>>> np.ones( (2, 2) )
array([[ 1.,  1.],
       [ 1.,  1.]])
>>> np.empty((2))
array([1.72723371e-77, 2.00389811e+00])
>>> np.ones((2,3), dtype=int) 		# δείτε ότι τώρα τα στοιχεία του πίνακα είναι ακέραιοι
array([[1, 1, 1],
       [1, 1, 1]])

Μπορούμε ακόμα να κατασκευάσουμε πίνακες τα στοιχεία των οποίων είναι ακολουθίες αριθμών. Η συνάρτηση arange είναι ανάλογη της range. Η συνάρτηση linspace είναι χρήσιμη για την κατασκευή διαμερίσεων με ομοιόμορφο βήμα αλλά και τον υπολογισμό συναρτήσεων σε μεγάλο πλήθος σημείων:


>>> a=np.arange(6)
>>> print(a)
[0 1 2 3 4 5]
>>> a=np.arange(9,1,-2)
>>> print(a)
[9 7 5 3]
>>> np.linspace(0,1,5)
array([ 0., 0.25, 0.5, 0.75, 1. ])
x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x)

Το "σχήμα" (shape) ενός πίνακα μπορεί να αλλάξει εύκολα με τις μεθόδους reshape, ravel, transpose και resize:


>>> a=np.arange(12)
>>> print(a)
[ 0  1  2  3  4  5  6  7  8  9 10 11]
>>> a.reshape(3,4)
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>> a.reshape(2,-1)
array([[ 0,  1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10, 11]])
>>> a.resize(6,2)
>>> print(a)
[[ 0  1]
 [ 2  3]
 [ 4  5]
 [ 6  7]
 [ 8  9]
 [10 11]]
>>> a.transpose()
array([[ 0,  2,  4,  6,  8, 10],
       [ 1,  3,  5,  7,  9, 11]])
>>> a.ravel()
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
>>> print(a)  # Το shape εξακολουθεί να είναι (6,2)
[[ 0  1]
 [ 2  3]
 [ 4  5]
 [ 6  7]
 [ 8  9]
 [10 11]]

Πράξεις με πίνακες

Οι αριθμητικοί τελεστές αλλά και οι συνηθισμένες μαθηματικές συναρτήσεις εφαρμόζονται σε ένα πίνακας κατά στοιχείο. Το συνηθισμένο γινόμενο πινάκων μπορεί να υπολογστεί με τη συνάρτηση dot:


>>> a = arange(4).reshape(2,2)
>>> b = arange(4,8).reshape(2,2)
>>> c = a+b
print(c)
[[ 4  6]
 [ 8 10]]
>>> d = a*b
print(d)
[[ 0  5]
 [12 21]]
>>> np.sin(a)
array([[ 0.        ,  0.84147098],
       [ 0.90929743,  0.14112001]])
>>> a**2+b**2
array([[16, 26],
       [40, 58]])
>>> d > 12
array([[False, False],
       [False,  True]], dtype=bool)
>>> np.dot(a,b)
array([[ 6,  7],
       [26, 31]])
>>> np.any(a < 3)
True
>>> np.all(b > 3)
True
>>> a += b
print(a)
[[ 4  6]
 [ 8 10]]
>>> np.exp(a*1j)
array([[-0.65364362-0.7568025j ,  0.96017029-0.2794155j ],
       [-0.14550003+0.98935825j, -0.83907153-0.54402111j]])

Οι μέθοδοι sum, min και max δέχονται ένα προαιρετικό όρισμα, την διάσταη (axis) του πίνακα κατά την οποία εφαρμόζονται:


>>> a = np.arange(12).reshape(3,4)
>>> print(a)
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
>>> a.sum()
66
>>> a.sum(axis=0)
array([12, 15, 18, 21])
>>> a.min(axis=1)
array([0, 4, 8])

Εφαρμογές στη γραμμική άλγεβρα

Το πακέτο linalg της βιβλιοθήκης NumPy έχει παρέχει ένα μεγάλο πλήθος συναρτήσεων χρήσιμων στην αριθμητική γραμμική άλγεβρα. Για παράδειγμα, η συνάρτηση inner(a,b) υπολογίζει το εσωτερικό γινόμενο δύο διανυσμάτων ενώ η συνάρτηση norm υπολογίζει τη νόρμα ενός διανύσματος ή ενός πίνακα:


>>> a = np.array( [1, 2, 3] )
>>> b = np.array( [2, 0, 1] )
>>> np.inner(a,b) 
5
>>> np.linalg.norm(a, 1)
6
>>> np.linalg.norm(a, 2)
3.7416573867739413
>>> np.linalg.norm(a, np.inf)
3

Η συνάρτηση det υπολογίζει την ορίζουσα ενός τετραγωνικού πίνακα και η συνάρτηση inv τον αντίστροφό του. Για τη λύση γραμμικών συστημάτων η βιβλιοθήκη NumPy παρέχει τη συνάρτηση solve και για τον υπολογισμό ιδιοτιμών και ιδιοδιανυσμάτων τη συνάρτηση eig:


>>> A = np.array([ (1, 3, 2), (4, 5, 1), (2, 0, 1) ])
>>> np.linalg.det(A)
-21.000000000000011
>>> B = np.linalg.inv(A)
>>> B
array([[-0.23809524,  0.14285714,  0.33333333],
       [ 0.0952381 ,  0.14285714, -0.33333333],
       [ 0.47619048, -0.28571429,  0.33333333]])
>>> np.dot(A,B)
array([[  1.00000000e+00,  -1.11022302e-16,   0.00000000e+00],
       [  0.00000000e+00,   1.00000000e+00,   0.00000000e+00],
       [  0.00000000e+00,   5.55111512e-17,   1.00000000e+00]])
>>> A = np.array([[3,1,5],[1,0,8],[2,1,4]])
>>> b = np.array([6,7,8])
>>> x = np.linalg.solve(A,b)
>>> print(x)
[-3.28571429 9.42857143 1.28571429]
>>> np.dot(A,x)
array([ 6., 7., 8.])
w, v = np.linalg.eig(A)
>>> w
array([ 7.78377088,  0.63421458, -1.41798546])  # eigenvalues
>>> v
array([[-0.63927142, -0.61967268,  0.02953761],
       [-0.5895579 ,  0.77250748, -0.9848506 ],
       [-0.49371402,  0.13870102,  0.17087078]])

Τυχαίοι αριθμοί

Αρκετή από την αρχική δουλειά στην θεωρία των πιθανοτήτων αφιερώθηκε στην ανάλυση τυχερών παιχνιδιών, κυρίως παιχνιδιών με ζάρια. Το ενδιαφέρoν ενός εκ των πρωτοπόρων στη θεωρία των πιθανοτήτων, του Γάλλου μαθηματικού Blaise Pascal, κινήθηκε από την ερώτηση ενός φίλου κατά πόσο θα ήταν επικερδές να στοιχηματίσει ότι δεν θα φέρει εξάρες αν ρίξει ένας ζεύγος από ζάρια 24 φορές. Σήμερα γνωρίζουμε ότι η πιθανότητα να μην εμφανιστούν εξάρες είναι (35/36)24, δηλαδή περίπου 0.51. Μπορούμε να γράψουμε ένα πικρό πρόγραμμα για να προσομειώσουμε το συγκεκριμένο παιχνίδι και να προσεγγίσουμε πειραματικά την παραπάνω πιθανότητα.


def rollDie():
	return np.random.random_integers(1,6)

def playGame(numTrials):
	yes = 0.0
	for i in range(numTrials):
		for j in range(24):
			d1 = rollDie()
			d2 = rollDie()
			if d1 == 6 and d2 == 6:
				yes += 1
				break
	print 'Probability of losing = ' + str(1.0 - yes / numTrials)