Μια συνάρτηση στην Python είναι κομμάτι κώδικα που φέρει το δικό του όνομα (ακολουθεί τη λέξη κλειδί def
στον ορισμό της συνάρτησης, έχει τα δικά της ορίσματα ή παραμέτρους (arguments) και όταν τελειώνει η εκτέλεσή του επστρέφει στο κομμάτι του κώδικα που την "κάλεσε" (με την εντολή return
).
Όπως βλέπουμε στα παραδείγματα παρακάτω ο ορισμός μιας συνάρτησης αρχίζει με μια γραμμή της μορφής
def ΌνομαΣυνάρτησης ( ΌνομαΟρίσματος1, ΌνομαΟρίσματος2, ...):
Ακολουθεί το κείμενο (ορισμός) της συνάρτησης γραμμένο πιο μέσα (indented) με τρόπο ώστε να υπάγεται στο def
.
Στα παρακάτω παραδείγματα θα δούμε πώς φτιάχνουμε μια συνάρτηση και πώς τη χρησιμοποιούμε. Ο κύριος λόγος για τον οποίο χρησιμοποιούμε συναρτήσεις είναι για να μην επαναλαμβάνουμε ξανά και ξανά τα ίδια κομμάτια κώδικα. Μάλιστα αν, γράφοντας ένα πρόγραμμα, αντιληφθούμε ότι γράφουμε και δεύτερη φορά το ίδιο κομμάτι κώδικα τότε θα πρέπει να αναρωτηθούμε αν είναι μήπως καλύτερα αυτό το κομμάτι να το πακετάρουμε σε μια συνάρτηση την οποία μετά εύκολα θα καλούμε εκεί όπου τη χρειαζόμαστε.
Αν έχουμε μια συνάρτηση $f:[a,b]\to{\mathbb R}$ που είναι συνεχής και έχει ετερόσημες τιμές στα άκρα του διαστήματος τότε, από το θεώρημα ενδιάμεσης τιμής, η συνάρτηση $f$ έχει σίγουρα ρίζα (σημείο μηδενισμού) μέσα στο διάστημα $[a,b]$.
Ένας τρόπος να υπολογίσουμε προσεγγιστικά μια τέτοια ρίζα είναι η λεγόμενη μέθοδος της διχοτόμησης. Σε κάθε βήμα της μεθόδου έχουμε ένα διάστημα στα άκρα του οποίου η συνάρτηση παίρνει ετερόσημες τιμές. Υπολογίζουμε την τιμή στο μέσο του διαστήματος και ελέγχουμε τα δύο μισά διαστήματα για το αν έχουν επίσης την ιδιότητα των ετερόσημων άκρων. Αν δεν την έχει το αριστερό μισό διάστημα τότε σίγουρα την έχει το δεξί. Επιλέγουμε ένα από τα δύο μισά διαστήματα που έχει την ιδιότητα και συνεχίζουμε τον υποδιπλασιασμό του διαστήματος έως ότου το διάστημα που έχουμε να είναι τόσο μικρό όσο και η προσέγγιση της ρίζας που είμαστε διατεθειμένοι να δεχτούμε. Τότε σταματάμε και αναφέρουμε το ένα από τα δύο άκρα (π.χ. το αριστερό) ως ρίζα του διαστήματος.
Στο επόμενο υλοποιούμε τη μέθοδο αυτή για τη συνάρτηση $$ f(x) = (x-1)^3 -\frac12, $$ στο διάστημα $[1, 2]$, η οποία είναι συνεχής και έχει ετερόσημες τιμές στα δύο άκρα.
a = 1; b = 2 # Τα άκρα του διαστήματος όπου ψάχνουμε τη ρίζα. Τα a και b θα παριστάνουν κάθε φορά
# το διάστημα στο οποίο έχουμε εγκλωβίσει τη ρίζα.
while b-a > 1e-6: # Όσο το διάστημα είναι >= 1e-6 (αυτή είναι η ακρίβεια προσέγγισης της ρίζας που επιθυμούμε)
m = (a+b)/2 # m είναι το μέσο του διαστήματος
fa = (a-1)**3-0.5 # Η τιμή της συνάρτησης στο a
fm = (m-1)**3-0.5 # Η τιμή της συνάρτησης στο b
if fa*fm <= 0: # Αν είναι ετερόσημες
b = m # μεταφέρουμε το b στο m και συνεχίζουμε με το αριστερό μισό διάστημα
else: # αλλιώς
a = m # μεταφέρουμε το a στο m και συνεχίζουμε με το δεξί μισό διάστημα
print("Η ρίζα είναι το {}".format(a))
Παρατηρείστε ότι αν στο παραπάνω πρόγραμμα θέλουμε να αλλάξουμε τη συνάρτηση τότε πρέπει να τροποποιήσουμε δύο γραμμές του κώδικα με πολύ όμοιο τρόπο (τους ορισμούς των fa
και fm
).
Αντί γι' αυτό γράφουμε τη συνάρτησή μας ως μια συνάρτηση python με όνομα και πάλι f
(θα μπορούσε όμως να είναι οτιδήποτε) και μέσα στον αλγόριθμο διχοτόμησης χρησιμοποιούμε κλήσεις προς τη συνάρτηση αυτή αντί να γράφουμε τον τύπο της συνάρτησης μέσα στον αλγόριθμο διχοτόμησης. Έτσι και ο αλγόριθμος διχοτόμησης είναι γραμμένος με πιο καθαρό τρόπο και φαίνεται που ακριβώς υπολογίζεται η συνάρτησή μας, όποια κι αν είναι αυτή, αλλά και η αλλαγή της συνάρτησης γίνεται πιο εύκολα, σε ένα και μόνο σημείο, μέσα στον ορισμό της συνάρτησης python f
(ήδη το κάναμε και αλλάξαμε τη συνάρτηση στην $f(x) = (x-1)^5-\frac12$.
a = 1; b = 2
def f(x): # Επικεφαλίδα ορισμού της συνάρτησης f. Το x είναι η μοναδική παράμετρος (όρισμα) της συνάρτησης
return (x-1)**5-0.5 # Δοθέντος του x επιστρέφουμε την τιμή που θέλουμε να έχει η συνάρτησή μας στο x.
while b-a > 1e-10:
m = (a+b)/2
fa = f(a) # υπολογίζουμε τη συνάρτηση στο αριστερό άκρο
fm = f(m) # και στο μέσο
if fa*fm <= 0:
b = m
else:
a = m
print("Η ρίζα είναι το {}".format(a))
Στο επόμενο πακετάρουμε και τον αλγόριθμο διχοτόμησης σε μια συνάρτηση findroot
, με παραμέτρους την f
(που οφείλει να είναι μια συνάρτηση python η οποία να επιστρέφει για κάθε πραγματικό αριθμό $x$ τον αριθμό $f(x)$), το a
και το b
(που είναι το αριστερό και δεξί άκρο του διαστήματος όπου ψάχνουμε τη ρίζα). Η συνάρτηση findroot
μας επιστρέφει τη ρίζα που βρήκε.
Έπειτα καλούμε τη συνάρτηση findroot
με δύο διαφορετικές μαθηματικές συναρτήσεις που έχουμε ορίσει παραπάνω, τις f
και f1
, δίνοντας απλά το όνομα της συνάρτησης της οποίας ρίζα ψάχνουμε. Ο κόπος που γλυτώνουμε είναι φανερός. Δε χρειάζεται να γράψουμε δύο φορές τον αλγόριθμο διχοτόμησης, μια φορά για κάθε συνάρτηση. Τον γράφουμε μια φορά ως τη συνάρτηση findroot
την οποία καλούμε όσες φορές θέλουμε, με όποιες συναρτήσεις θέλουμε και όποια άκρα διαστημάτων θέλουμε.
def f(x):
return (x-1)**3-0.5
def f1(x):
return (x-1)**5-0.5
def findroot(f, a, b): # Επαναλαμβάνουμε εδώ ουσιαστικά τη μέθοδο διχοτόμησης όπως παραπάνω αλλά επιστρέφουμε
while b-a > 1e-10: # τι ρίζα που βρήκαμε με το return παρακάτω.
m = (a+b)/2
fa = f(a)
fm = f(m)
if fa*fm <= 0:
b = m
else:
a = m
return a
r = findroot(f, 1, 2) # Εδώ βρίσκουμε ρίζα της συνάρτησης f
print("Η ρίζα είναι το {}".format(r))
r = findroot(f1, 1, 2) # Εδώ βρίσκουμε ρίζα της συνάρτησης f1
print("Η ρίζα είναι το {}".format(r))
Το επόμενο πρόβλημα που κοιτάμε είναι το εξής. Μας δίνεται ένα σύνολο από σημεία στο επίπεδο
$$
(x_1, y_1), (x_2, y_2), \ldots, (x_N, y_N)
$$
και ένα ακόμη σημείο $(x, y)$ και θέλουμε να βρούμε την απόσταση του σημείου $(x, y)$ από το σύνολο σημείων. Με άλλα λόγια θέλουμε να βρούμε την ελάχιστη τιμή της ποσότητας
$$
dist( (x,y), (x_j, y_j) ),\ \ \ \text{όταν $j=1,2,\ldots,N$}.
$$
Εδώ $dist( (x, y), (z, w) )$ είναι η απόσταση των σημείων $(x, y)$ και $(x, w)$ η οποία γνωρίζουμε ότι δίνεται από τον τύπο (Πυθαγόρειο θεώρημα):
$$
dist( (x, y), (z, w) ) = ( (x-z)^2 + (y-w)^2 )^{1/2}.
$$
Τα σημεία μας τα έχουμε σε μια λίστα L
ως λίστες μήκους 2 το καθένα, και το μοναδικό μας σημείο ως τις μεταβλητές x
και y
.
import math # Το χρειαζόμαστε για τον υπολογισμό της τετραγωνικής ρίζας
L = [ [1, 2], [0.1, -2], [3, 4], [-10, -3] ] # Η λίστα με το σύνολο των σημείων
x, y = 0.1, 3 # Το σημείο του οποίου ψάχνουμε την απόσταση από τα σημεία της L
D = [] # Εδώ αποθηκεύουμε τις αποστάσεις
for p in L: # παίρνουμε τις αποστάσεις του (x,y) από κάθε σημείο p της λίστας
d = math.sqrt((x-p[0])**2 + (y-p[1])**2) # αυτή είναι η απόσταση του (x, y) από το p
D.append(d) # τη βάζουμε στη λίστα των αποστάσεων
mindistance = min(D) # υπολογίζουμε το ελάχιστο στοιχείο της λίστας D
print("Η ελάχιστη απόσταση είναι {}".format(mindistance))
Τώρα πακετάρουμε τον προηγούμενο αλγόριθμο σε μια συνάρτηση mindistance(q, L)
όπου q
είναι το σημείο την απόσταση του οποίου ψάχνουμε και L
είναι η λίστα των σημείων. Η συνάρτηση επιστρέφει την ελάχιστη από τις αποστάσεις του q
από τα σημεία της L
.
import math
def mindistance(q, L):
D = []
for p in L:
d = math.sqrt((q[0]-p[0])**2 + (q[1]-p[1])**2)
D.append(d)
return min(D)
md = mindistance([0.1, 3], [ [1, 2], [0.1, -2], [3, 4], [-10, -3] ]) # Εδώ καλούμε τη συνάρτηση με
# q = [0.1, 3] και L = [ [1, 2], [0.1, -2], [3, 4], [-10, -3] ]
print("Η ελάχιστη απόσταση είναι {}".format(md))
Στο επόμενο πρόγραμμα χρησιμοποιούμε τη συνάρτηση mindistance(q, L)
για να υπολογίσουμε, με τη συνάρτηση minpair(L)
, την ελάχιστη απόσταση ανάμεσα σε δύο σημεία της λίστας L
. Η συνάρτηση minpair(L)
επιστρέφει την ελάχιστη απόσταση ανάμεσα σε δύο σημεία της L
. Το κάνει αυτό ως εξής: για κάθε σημείο της L
βρίσκει την απόστασή του από τα υπόλοιπα σημεία της L
χρησιμοποιώντας τη συνάρτηση mindistance
. Τέλος βρίσκει τον ελάχιστο από αυτούς τους αριθμούς.
import math
def mindistance(q, L): # Είναι ίδια όπως και στο προηγούμενο πρόγραμμα
D = []
for p in L:
d = math.sqrt((q[0]-p[0])**2 + (q[1]-p[1])**2)
D.append(d)
return min(D)
def minpair(L): # Επιστρέφει την ελάχιστη απόσταση ανάμεσα σε δύο σημεία της λίστας L (έχει την ίδια μορφή όπως πριν)
D = [] # σε αυτή τη λίστα κρατάμε όλες τις αποστάσεις ενός σημείου από όλα τα υπόλοιπα
for i in range(len(L)): # για κάθε σημείο της λίστας L
d = mindistance(L[i], L[:i]+L[i+1:]) # βρίσκουμε την απόστασή του από τα υπόλοιπα σημεία
# Η λίστα L[:i]+L[i+1:] περιέχει όλα τα σημεία της L εκτός από το L[i]
D.append(d)
return min(D) # επιστρέφουμε την ελάχιστη τέτοια απόσταση (σημείου προς όλα τα υπόλοιπα)
X = [ [0, 0], [1, 0], [1, 1], [0, 1], [0.5, 0.6] ] # αυτά είναι τα σημεία από τα οποία ψάχνουμε το πλησιέστερο ζεύγος
mp = minpair(X) # καλούμε τη συνάρτηση που μόλις γράψαμε
print("Η ελάχιστη απόσταση ζεύγους είναι {}".format(mp))
Στο επόμενο πρόγραμμα τροποποιούμε τη συνάρτηση mindistance
στη συνάρτηση newmindistance
ώστε όχι μόνο να μας βρίσκει την ελάχιστη απόσταση του σημείου q
από όλα τα σημεία της L
αλλά να μας λέει και από ποιο σημείο της L
είναι αυτή η απόσταση (η ελάχιστη απόσταση μπορεί βέβαια να "πιάνεται" σε παραπάνω από ένα σημείο της L
αλλά η συνάρτησή μας επιστρέφει ένα από αυτά).
Η συνάρτησή μας επιστρέφει τώρα μια λίστα δύο στοχείων. Το πρώτο στοιχείο της λίστας είναι η ελάχιστη απόσταση και το δεύτερο είναι η θέση στην L
ενός σημείου όπου υλοποιείται η ελάχιστη απόσταση.
import math
def newmindistance(q, L):
D = []
for p in L:
d = math.sqrt((q[0]-p[0])**2 + (q[1]-p[1])**2)
D.append(d) # μέχρι εδώ είναι όπως και προηγουμένως
tmpd = D[0] # στο tmpd κρατάμε την τρέχουσα ελάχιστη απόσταση
wherefrom = 0 # και στον ακέραιο wherefrom κρατάμε ένα σημείο της L όπου αυτή πιάνεται
# αρχικές τιμές και των δύο είναι η απόσταση από το πρώτο στοιχείο της L και η θέση 0 (πρώτη θέση της L)
for i in range(1,len(D)): # για κάθε σημείο της L από το 2ο και πέρα
if D[i] < tmpd: # αν η απόστασή του από το q (έχει ήδη υπολογιστεί στο D[i]) είναι μικρότερη από την
# τρέχουσα τότε ενημερώνουμε την τρέχουσα απόσταση και τη θέση της (wherefrom)
tmpd = D[i]; wherefrom = i
return [tmpd, wherefrom] # επιστρέφουμε το ζεύγος τιμών που βρήκαμε
# Στο παρακάτω καλούμε τη συνάρτηση newmindistance με το σημείο [0.1, 300] και τη λίστα σημείων
# [ [-1000, -2000], [0.1, -200], [3, 4], [-10000, -3] ]. Στη μεταβλητή d αποθηκεύεται η ελάχιστη απόσταση
# που επιστρέφει η mindistance και στη μεταβλητή i το σημείο της L όπου αυτή πιάνεται.
[d, i] = newmindistance([0.1, 300], [ [-1000, -2000], [0.1, -200], [3, 4], [-10000, -3] ])
print("Η ελάχιστη απόσταση είναι {} από το σημείο L[{}]".format(d, i))
Τα αποτελέσματα των print
στο παρακάτω πρόγραμμα είναι τουλάχιστον περίεργα εκ πρώτης όψεως. Για να ξεκαθαρίσετε μερικά πράγματα σχετικά με τις τοπικές και τις καθολικές μεταβλητές σε μια συνάρτηση κι ένα πρόγραμμα διαβάστε το αντίστοιχο κομμάτι από τη σελίδα [http://fourier.math.uoc.gr/prog2/files/nb/feb24.html] του Προγραμματισμού ΙΙ (Εαρινό Εξάμηνο 2014-15). Η σελίδα αυτή αφορά την Python 2 αλλά η μόνη διαφορά που θα δείτε εσείς είναι ότι το print
συντάσσεται χωρίς παρενθέσεις (όπως στην Python 3).
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)