Σημειωματάριο Τρίτης 3 Νοε 2015

Συναρτήσεις (functions)

Μια συνάρτηση στην Python είναι κομμάτι κώδικα που φέρει το δικό του όνομα (ακολουθεί τη λέξη κλειδί def στον ορισμό της συνάρτησης, έχει τα δικά της ορίσματα ή παραμέτρους (arguments) και όταν τελειώνει η εκτέλεσή του επστρέφει στο κομμάτι του κώδικα που την "κάλεσε" (με την εντολή return).

Όπως βλέπουμε στα παραδείγματα παρακάτω ο ορισμός μιας συνάρτησης αρχίζει με μια γραμμή της μορφής
def ΌνομαΣυνάρτησης ( ΌνομαΟρίσματος1, ΌνομαΟρίσματος2, ...):

Στα παρακάτω παραδείγματα θα δούμε πώς φτιάχνουμε μια συνάρτηση και πώς τη χρησιμοποιούμε. Ο κύριος λόγος για τον οποίο χρησιμοποιούμε συναρτήσεις είναι για να μην επαναλαμβάνουμε ξανά και ξανά τα ίδια κομμάτια κώδικα. Μάλιστα αν, γράφοντας ένα πρόγραμμα, αντιληφθούμε ότι γράφουμε και δεύτερη φορά το ίδιο κομμάτι κώδικα τότε θα πρέπει να αναρωτηθούμε αν είναι μήπως καλύτερα αυτό το κομμάτι να το πακετάρουμε σε μια συνάρτηση την οποία μετά εύκολα θα καλούμε εκεί όπου τη χρειαζόμαστε.

Συνάρτηση για την απόσταση δύο σημείων στο \({\mathbb R}^3\)

Αν \((x_1, y_1, z_1)\) και \((x_2, y_2, z_2)\) είναι δύο σημεία στο \({\mathbb R}^3\) τότε η (Ευκλείδια) απόσταση μεταξύ τους είναι η ποσότητα
\(\sqrt{(x_1-x_2)^2 + (y_1-y_2)^2 + (z_1-z_2)^2}.\)

Στο παρακάτω πρόγραμμα υλοποιούμε τη συνάρτηση distance η οποία δοθέντων δύο σημείων p1 και p2 (κάνουμε εδώ τη σύμβαση ότι παριστάνουμε ένα σημείο στο \({\mathbb R}^3\) ως μια λίστα με τρεις αριθμούς) υπολογίζει και επιστρέφει την απόσταση ανάμεσα στα δύο αυτά σημεία. Πρώτα υπολογίζουμε το επιθυμητό αποτέλεσμα στη μεταβλητή d και μετά επιστρέφουμε (σε όποιον κάλεσε αυτή τη συνάρτηση) την τιμή αυτή με την εντολή return.

Οι μεταβλητές x1. y1, z1, x2, y2, z2, d που χρησιμοποιούνται μέσα στη συνάρτηση αυτή λέγονται τοπικές μεταβλητές της συνάρτησης. Οι μεταβλητές αυτές (όπως και τα ορίσματα της συνάρτησης p1, p2) δε "φαίνονται" από το υπόλοιπο πρόγραμμα. Αν δηλ. κάπου στο πρόγραμμα εκτός της συνάρτησης distance πάμε να χρησιμοποιήσουμε την τιμή της μεταβλητής x1 αυτή θα είναι αόριστη (και θα προκύψει σφάλμα), ακόμη κι αν η συνάρτηση έχει ήδη τρέξει μια φορά. Μια καλή αναλογία είναι να φανταζόμαστε ότι οι μεταβλητές αυτές ξαναγεννιούνται (χωρίς να έχουν τιμή, εκτός από τα ορίσματα που έχουν την τιμή που περνάμε με την κλήση της συνάρτησης) κάθε φορά που εκτελείται η συνάρτηση και πεθαίνουν μετά το τέλος του κώδικα της συνάρτησης (συνήθως μετά την εκτέλεση του return).

Στο τέλος του προγράμματος (το κομμάτι εκτός των συναρτήσεων το ονομάζουμε συνήθως "κυρίως πρόγραμμα") ορίζουμε τρία σημεία στις μεταβλητές a, b, c και υπολογίζουμε, καλώντας ανάλογα τη συνάρτηση distance, όλες τις μεταξύ τους αποστάσεις.

Το κείμενο που υπάρχει ακριβώς κάτω από την επικεφαλίδα της συνάρτησης και είναι μέσα σε τριπλά εισαγωγικά (""") λέγεται docstring της συνάρτησης και δεν επηρεάζει τον κώδικα και την εκτέλεση της συνάρτησης, είναι όμως πολύτιμη περιγραφή του τι κάνει η συνάρτηση και πολύ χρήσιμη σε όποιον διαβάζει αυτή τη συνάρτηση και προσπαθεί να καταλάβει τι κάνει (κάποιος άλλος από το συγγραφέα της συνάρτησης ή και ο ίδιος ο συγγραφέας της μετά από λίγο καιρό).

In [2]:
import math

def distance(p1, p2):
    """
    Υπολογίζει την Ευκλείδια απόσταση ανάμεσα στα δύο σημεία p1, p2.
    Τα σημεία αυτά πρέπει να είναι δύο λίστες με στοιχεία τρεις αριθμούς
    η κάθε μία (τις συντεταγμένες x, y, z των σημείων.
    """
    x1 = p1[0]
    y1 = p1[1]
    z1 = p1[2]
    
    x2 = p2[0]
    y2 = p2[1]
    z2 = p2[2]
    
    d = math.sqrt( (x1-x2)**2 + (y1-y2)**2 + (z1-z2)**2 )
    return d

a = [0., 0., 0.]
b = [1., 1., 0.]
c = [1, 1, 2]

print distance(a, b)
print distance(a, c)
print distance(b, c)
1.41421356237
2.44948974278
2.0

Υποολογισμός ενός μέσου όρου

Στην επόμενη συνάρτηση goodmean υπολογίζουμε το μέσο όρο όλων των αριθμών της λίστας L (το μοναδικό όρισμα της συνάρτησης) εξαιρώντας το μικρότερο στοιχείο της L. Αυτό σημαίνει ότι πρέπει να υπολογίσουμε το άθροισμα όλων των στοιχείων της L πλην του ελάχιστου στοιχείου και να διαιρέσουμε με το πλήθος των στοιχείων της L μείον 1.

Πρέπει βεβαίως η λίστα L να έχει τουλάχιστον δύο στοιχεία ώστε αυτό που κάνουμε να έχει νόημα. Σε αντίθετη περίπτωση κάνουμε τη σύμβαση ότι επιστρέφουμε 0.

Τονίζουμε εδώ ότι η εκτέλεση του return συνεπάγεται ότι σταματάει η εκτέλεση του κώδικα της συνάρτησης και ο έλεγχος του προγράμματος επιστρέφει στο σημείο όπου καλέστηκε η συνάρτηση. Στο πρόγραμμα αυτό μετά από κάθε return ο έλεγχος περνάει στην εντολή print που κάλεσε τη συνάρτηση.

In [2]:
def goodmean(L):
    """
    Υπολογίζει το μέσο όρο των αριθμών της L αφού βγάλουμε το μικρότερο.
    Αν η λίστα έχει μήκος < 2 τότε επιστρέφει 0.
    """
    if len(L)<2:
        return 0
    return (sum(L)-min(L)) / (len(L)-1)

print goodmean([])
print goodmean([1])
print goodmean([1, 2])
print goodmean([1, 2, 3.4, 4.8])
0
0
2
3.4

Υπολογισμός παραγοντικού και διωνυμικού συντελεστή.

Η συνάρτηση factorial(n) παρακάτω υπολογίζει το παραγοντικό του n:
\(n! = 1\cdot 2\cdot 3 \cdots (n-1)\cdot n\).
Η συνάρτηση αυτή χρησιμοποιείται μετά στην κατασκευή της συνάρτησης binomial(n ,k) που υπολογίζει το "διωνυμικό συντελεστή"
\(\displaystyle {n \choose k} =\) πόσα υποσύνολα μεγέθους \(k\) έχει το σύνολο \(\{1, 2, \ldots, n\}\);
Αποδεικνύεται ότι η ποσότητα αυτή δίνεται από τον τύπο
\(\displaystyle {n \choose k} = \frac{n!}{k!(n-k)!}\)

και αυτό τον τύπο χρησιμοποιούμε στη συνάρτηση binomial(n, k) καλώντας τη συνάρτηση factorial όποτε χρειαζόμαστε να υπολογίσουμε κάποιο παραγοντικό.

In [3]:
def factorial(n):
    """
    Υπολογίζει το n! = 1*2*3*...*(n-1)*n
    Αν n=0 η ποσότητα αυτή ορίζεται να είναι ίση με 1.
    """
    p = 1
    for i in range(2,n+1):
        p = p*i
    return p


def binomial(n, k):
    """
    Διωνυμικός συντελεστής n ανά k.
    Αν k>n τότε δε χρειάζεται να χρησιμοποιήσουμε τον τύπο
    με τα παραγοντικά. Από τον ορισμό προκύπτει ότι το αποτέλεσμα είναι 0.
    """ 
    if k>n:
        return 0
    return factorial(n) / ( factorial(k) * factorial(n-k) )
    

print binomial(10, 0)
print binomial(10, 12)
print binomial(10, 1)
print binomial(10, 5)
1
0
10
252

Συνάρτηση που ελέγχει αν αν δύο λίστες είναι η μια μετάθεση της άλλης

Σε πολλά προβλήματα θέλουμε να αποφασίσουμε αν δύο λίστες l1 και l2 έχουν τα ίδια περιεχόμενα χωρίς να μας ενδιαφέρει η σειρά των περιεχομένων τους. Δε μπορούμε απλά να γράψουμε l1 == l2 γιατί αυτό απαιτεί τα περιεχόμενα να είναι στην ίδια σειρά για βγεί True.

Η συνάρτηση eqlists(l1, l2) παρακάτω ελέγχει ακριβώς αυτό. Πρώτα ελέγχει αν οι δύο λίστες έχουν ίδιο μήκος. Αν αυτό δε συμβαίνει τότε αμέσως επιστρέφει False.

Αν οι λίστες έχουν το ίδιο μήκος τότε για κάθε στοιχείο x της l1 ελέγχει αν εμφανίζεται τον ίδιο αριθμό φορών στην l1 και στην l2 (εδώ χρησιμοποιούμε τη μέθοδο l.count(x) μιας λίστας l που μας επιστρέφει το πλήθος των φορών που το x εμφανίζεται στην l). Αν το πλήθος εμφανίσεων του x στην l1 δεν είναι ίδιο με αυτό στην l2 τότε επιστρέφουμε False.

Αν το for τελειώσει και δεν έχουμε επιστρέψει είμαστε πλέον σίγουροι ότι τα περιεχόμενα των l1 και l2 είναι ίδια και επιστρέφουμε True.

In [4]:
def eqlists(l1, l2):
    """
    Επιστρέφει True αν και μόνο αν οι δύο λίστες l1 και l2 έχουν τα ίδια
    περιεχόμενα αλλά, ενδεχομένως, σε άλλη σειρά.
    """
    if len(l1) != len(l2):
        return False
    for x in l1:
        if l1.count(x) != l2.count(x):
            return False      
    return True

print eqlists([2, 1, 1], [1, 1, 2])
print eqlists([1, 1, 2], [1, 2, 2])
True
False

Πόσες φορές μια λέξη εμφανίζεται μέσα σε μια άλλη

Η συνάρτηση counthowmany(where, what) παρακάτω μετράει πόσες φορές εμφανίζεται το string what στο string where.

Θυμόμαστε ότι η μέθοδος s.index(w) μας λέει σε ποια θέση εμφανίζεται το string w μέσα στο string s. Αν εμφανίζεται παραπάνω από μια φορές μας επιστρέφει την πρώτη θέση. Αν δεν εμφανίζεται μας δημιουργεί σφάλμα (οπότε πριν τη χρησιμοποιήσουμε σιγουρευόμαστε με τη χρήση του in ότι όντως υπάρχει η μικρή λέξη στη μεγάλη).

Ξεκινάμε λοιπόν από την πρώτη θέση του string και προχωράμε προς τα δεξιά (αυξάνοντας τη μεταβλητή first) και μετρώντας κάθε φορά (στη μεταβλητή count) το πόσες φορές έχουν συναντήσει τη λέξη what μέχρι τώρα.

Ένας εναλλακτικός τρόπος υλοποίησης της ίδιας συνάρτησης (με το όνομα τώρα counthowmanyalt) δίνεται στην επόμενη συνάρτηση. Εδώ η φιλοσοφία είναι ότι "κυλάμε" τη λέξη what δίπλα στη λέξη where και αυξάνουμε το μετρητή count όποτε η λέξη που βλέπουμε είναι ίδια με τη what.

Η τελευταία συνάρτηση counthowmanyall υπολογίζει και πάλι τον αριθμό των εμφανίσεων της what στη where χωρίς όμως να διαχωρίζει τα κεφαλαία από τα μικρά γράμματα. Για να το πετύχουμε αυτό μετατρέπουμε και το what και το where σε όλα κεφαλαία και μετά καλούμε την προηγούμενη συνάρτηση counthowmany που γράψαμε.

In [13]:
def counthowmany(where, what):
    """
    Μετράει πόσες φορές το γράμμα (ή λέξη) what υπάρχει στο string where
    """
    first = 0
    count = 0
    while what in where[first:]  :
        i = where[first:].index(what)
        count += 1
        first = first+i+1
        if first >= len(where):
            break
        
    return count

def counthowmanyalt(where, what):
    """
    Μετράει πόσες φορές το γράμμα (ή λέξη) what υπάρχει στο string where.
    Εναλλακτικός τρόπος.
    """
    count = 0
    for i in range( len(where)-len(what)+1 ):
        if where[i:i+len(what)] == what:
            count += 1
    return count


def counthowmanyall(where, what):
    """
    Μετράει πόσες φορές το γράμμα (ή λέξη) what υπάρχει στο string where
    χωρίς να μετράει αν είναι κεφαλαία ή μικρά.
    """
    return counthowmany(where.upper(), what.upper())
    
    

print counthowmany("bbabbB", "bb")
print counthowmanyalt("bbabbB", "bb")
print counthowmanyall("bbabbB", "bb")
2
2
3

Τοπικές (local) και καθολικές (global) μεταβλητές

Τα αποτελέσματα των print στο παρακάτω πρόγραμμα είναι τουλάχιστον περίεργα εκ πρώτης όψεως. Για να ξεκαθαρίσετε μερικά πράγματα σχετικά με τις τοπικές και τις καθολικές μεταβλητές σε μια συνάρτηση κι ένα πρόγραμμα διαβάστε το αντίστοιχο κομμάτι από τη σελίδα [http://fourier.math.uoc.gr/prog2/files/nb/feb24.html] του Προγραμματισμού ΙΙ (Εαρινό Εξάμηνο 2014-15).

In [14]:
y=5

def f(x):
    y = 10
    x = x+y
    print "In function: x=", x
    return True

x = 1
f(x)
print "In main program: x=", x
print "In main program: y=", y
In function: x= 11
In main program: x= 1
In main program: y= 5