Σημειωματάριο διάλεξης της 19ης Φεβ. 2015

Σήμερα κάναμε κατ' αρχήν μια επανάληψη των βημάτων που απαιτούνται για να μπορέσετε να κατεβάσετε από τη σελίδα τα προγράμματα που έχετε να φτιάξετε (δύο αρχεία για κάθε άσκηση: user.py και tester.py, τα οποία θα πρέπει να τα αποθηκεύσετε στον υπολογιστή σας σε ένα ξεχωριστό κατάλογο (directory, folder) για κάθε μια από τις ασκήσεις). Ασχοληθήκαμε με την άσκηση 2 και τη λύσαμε πλήρως, την περάσαμε από τον tester και την ανεβάσαμε στο σύστημα.

Θα πρέπει βεβαίως όλοι σας να φροντίσετε να περάσετε αρκετό χρόνο στα εργαστήρια πριν τις ημερομηνίες των εξετάσεων ώστε να εξοικειωθείτε αρκετά με τους υπολογιστές στους οποίους θα κάνετε τις εξετάσεις σας.

Συναρτήσεις στη γλώσσα python

Το βασικό νέο πράγμα που μάθαμε σήμερα είναι η έννοια της συνάρτησης (function) στην python.

Μια συνάρτηση (ο ορισμός της αρχίζει με def ...) είναι ένα κομμάτι κώδικα το οποίο καλείται από άλλα σημεία του προγράμματος και έχει κάποιες παραμέτρους (τα ορίσματα της συνάρτησης) και επίσης επιστρέφει σε αυτόν που την κάλεσε κάποια τιμή ή κάποιες τιμές (με την εντολή return).

Στο πρόγραμμα που φαίνεται παρακάτω έχουμε ορίσει μια συνάρτηση grade(x) την οποία και καλούμε και στο πρόγραμμα παρακάτω (το κυρίως πρόγραμμα αρχίζει από τηνεντολή while).

Στη συνάρτηση grade(x) υλοποιήσαμε το στρογγύλεμα ενός πραγματικού αριθμού x στον πλησιέστερο μισό ακέραιο. Το κίνητρο που έχουμε για αυτή τη συνάρτηση είναι ο τρόπος με τον οποίο στρογγυλεύονται οι βαθμοί στο πανεπιστήμιο (σε ακέραιους ή μισούς ακέραιους, δηλ. ακέραια πολλαπλάσια του 1/2). Έτσι π.χ. το 8.31 γίνεται 8.5, και το 8.2 γίνεται 8.

Η δήλωση της συνάρτησης είναι η γραμμή def grade(x): στην οποία ορίζεται (α) το όνομα της συνάρτησης (grade) και (β) πόσες και ποιες παραμέτρους έχει η συνάρτηση. Η συνάρτηση αυτή έχει μία και μόνη παράμετρο στην οποία δώσαμε το όνομα x. Όταν κατά την κλήση της συνάρτησης θα εκτελεστεί ο κώδικας της συνάρτησης η τιμή που θα έχει η παράμετρος αυτή θα είναι αυτή που θα περάσουμε στη συνάρτηση εκεί που την καλούμε. Αν π.χ. η συνάρτηση κληθεί με την εντολή grade(5.3) τότε η τιμή που θα έχει η x θα είναι 5.3.

Η πρώτη γραμμή μετά το def είναι η import math με την οποία δηλώνουμε στον python interpreter ότι θα χρειαστούμε τη βιβλιοθήκη math της python. Η γραμμή αυτή θα μπορούσε και να είχε μπει στην αρχή του κειμένου, πριν δηλ από τη γραμμή def.

Στο πρώτο if της συνάρτησης ελέγχουμε αν το x που μας δόθηκε είναι στο κατάλληλο διάστημα για βαθμός και αποφασίζουμε ότι αν μας δοθεί κάποιο x που είτε είναι αρνητικό είναι μεγαλύτερο του 10 τότε θα επιστρέφουμε την τιμή 0 και τυπώνουμε (πριν επιστρέψουμε με return) ένα μήνυμα ERROR. Είναι σημαντικό να τονίσουμε εδώ ότι με την εκτέλεση της εντολής return (α) επιστρέφεται η τιμή και (β) ο έλεγχος (εντολή που εκτελείται) επανέρχεται στο πρόγραμμα που κάλεσε τη συνάρτηση.

Στην εντολή am = math.floor(x) υπολογίζουμε το ακέραιο μέρος της ποσότητας x και το αποθηκεύουμε στη μεταβλητή am. Αμέσως μετά υπολογίζουμε και το κλασματικό μέρος του x και το αποθηκεύουμε στη μεταβλητή km. Η ποσότητα km είναι φυσικά στο διάστημα [0,1).

Είναι φανερό ότι αν ο αριθμός km είναι έως 0.25 (μη συμπεριλαμβανομένου, αυτό στρογγυλεύεται στον επόμενο μισό ακέραιο) τότε η απάντηση πρέπει να είναι το am, αλλιώς αν το km είναι από 0.25 έως 0.75 (μη συμπεριλαμβανομένου, αυτό στρογγυλεύεται στον επόμενο ακέραιο) τότε η απάντηση είναι am+1/2, αλλιώς η απάντηση είναι am+1.

Το κυρίως πρόγραμμα αρχίζει με την εντολή ανακύκλωσης while True:. Εδώ η συνθήκη που ελέγχει το αν θα εκτελεστούν οι εντολές που υπάγονται στο while είναι απλά η λογική σταθερά True (υπάρχει επίσης και η λογική σταθερά False). Αυτή η συνθήκη είναι πάντα αληθής, όπως λέει και το όνομά της, οπότε η ανακύκλωση αυτή εκτελείται επ' άπειρον. Ο μόνος τρόπος να τη σταματήσουμε (και αυτό κάναμε στην εκτέλεση του προγράμματος αυτού, και φαίνεται παρακάτω) είναι να διακόψουμε την εκτέλεση του προγράμματος, συνήθως πατώντας το συνδυασμό πλήκτρων Control, C.

Μέσα στο while με την εντολή y = float(raw_input("Give me a number between 0 and 10:")) διαβάζουμε ένα αριθμό από το χρήστη, στη μεταβλητή y, και στην αμέσως επόμενη εντολή g = grade(y) καλούμε τη συνάρτηση grade με όρισμα ακριβώς τον αριθμό y. Η τιμή που επιστρέφει η κλήση αυτή της grade αποθηκεύεται στη μεταβλητή g η οποία τυπώνεται στην τελευταία γραμμή της ανακύκλωσης.

Βλέπουμε στην εκτέλεση παρακάτω τις τιμές που επιστρέφει η grade για διάφορες τιμές του ορίσματος. Τα "ακαταλαβίστικα" που φαίνονται αμέσως μετά είναι τα μηνύματα που βγάζει ο python interpreter μετά διακόψαμε το πρόγραμμα με "βάναυσο" τρόπο.

In [1]:
def grade(x):
    import math
    if (x<0) or (x>10):
        print "ERROR"
        return 0
    am = math.floor(x)
    km = x - am
    if(km < 0.25):
        return am
    if(km < 0.75):
        return am+0.5
    return am+1

while True:
    y = float(raw_input("Give me a number between 0 and 10:"))
    g = grade(y);
    print "Your grade is", g
Give me a number between 0 and 10:3.2
Your grade is 3.0
Give me a number between 0 and 10:4.6
Your grade is 4.5
Give me a number between 0 and 10:10
Your grade is 10.0
Give me a number between 0 and 10:0
Your grade is 0.0
Give me a number between 0 and 10:6.5
Your grade is 6.5
Give me a number between 0 and 10:6.75
Your grade is 7.0

---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
<ipython-input-1-c13394002795> in <module>()
     13 
     14 while True:
---> 15     y = float(raw_input("Give me a number between 0 and 10:"))
     16     g = grade(y);
     17     print "Your grade is", g

/usr/lib/python2.7/dist-packages/IPython/kernel/zmq/ipkernel.pyc in <lambda>(prompt)
    359         # raw_input in the user namespace.
    360         if content.get('allow_stdin', False):
--> 361             raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
    362             input = lambda prompt='': eval(raw_input(prompt))
    363         else:

/usr/lib/python2.7/dist-packages/IPython/kernel/zmq/ipkernel.pyc in _raw_input(self, prompt, ident, parent)
    780             except KeyboardInterrupt:
    781                 # re-raise KeyboardInterrupt, to truncate traceback
--> 782                 raise KeyboardInterrupt
    783             else:
    784                 break

KeyboardInterrupt: 

Δύο συναρτήσεις για τη λύση ενός 2x2 γραμμικού συστήματος

Στο παρακάτω γράφουμε δυο συναρτήσεις με σκοπό να λύσουμε το 2x2 γραμμικό σύστημα (με δύο εξισώσεις και δύο αγνώστους):

a x + b y = e

c x + d y = f

Ένα τέτοιο σύστημα περιγράφεται φυσικά από τους συντελεστές του και το δεξί μέλος, δηλ. τις ποσότητες a, b, c, d, e, f, οι οποίες είναι και τα ορίσματα της δεύτερης συνάρτησης που γράψαμε, της linsolve, η οποία παίρνει αυτά τα ορίσματα και επιστρέφει τις δύο λύσεις του συστήματος. Η linsolve δουλεύει μόνο στην περίπτωση που ο πίνακας του συστήματος

a b

c d

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

Η πρώτη συνάρτηση που γράφουμε είναι η oriz η οποία έχει σκοπό να υπολογίζει την ορίζουσα ενός 2x2 πίνακα όταν της δώσουμε τα στοιχεία του πίνακα, γραμμή προς γραμμή. Όπως βλέπουμε η συνάρτηση αυτή είναι πολύ απλή και κάνει απλά και μόνο ένα υπολογισμό για να βρει την ορίζουσα. Παρατηρείστε στη γραμμή μετά από το def, και στις δύο συναρτήσεις, ότι υπάρχει μόνο ένα string που έχει μια περιγραφή της συνάρτησης. To string αυτό ονομάζεται docstring. Θα μπορούσαμε αντί γι' αυτό να γράψουμε το κείμενο του string μέσα σε ένα σχόλιο. Η διαφορά είναι ότι το docstring υφίσταται και ως μέρος του προγράμματος (και μπορεί να τυπωθεί αν αυτό απαιτηθεί από το χρήστη ιδιαίτερα σε περιπτώσεις όπου ο κώδικας παρουσιάζει σφάλματα) ενώ το σχόλιο δεν το λαμβάνει καθόλου υπόψιν ο python interpreter.

Παρατηρείστε ότι η συνάρτηση oriz καλείται τρεις φορές μέσα στη συνάρτηση linsolve. Είναι μια καλή πρακτική στον προγραμματισμό οποιοδήποτε κομμάτι κώδικα επαναλαμβάνεται έστω και δύο φορές μόνο να το κάνουμε χωριστή συνάρτηση αντί να το γράφουμε δύο φορές. Αυτό βοηθάει πάρα πολύ και στην αναγνωσιμότητα του κώδικα αλλά και στη διόρθωσή του. Αν, π.χ., διαπιστώσουμε ότι έχουμε ένα λάθος σε ένα κομμάτι κώδικα που επαναλαμβάνεται μερικές φορές τότε θα πρέπει να επαναλάβουμε τη διόρθωση αυτού του λάθους όσες φορές είναι γραμμένος ο κώδικας. Αν όμως το έχουμε γράψει ως συνάρτηση τότε δεν έχουμε κάτι να αλλάξουμε στις κλήσεις της συνάρτησης παρά μόνο στον ορισμό της ίδιας της συνάρτησης, που είναι σημαντικά λιγότερη δουλειά.

Το άλλο σημαντικό που παρατηρούμε στη συνάρτηση linsolve είναι ότι επιστρέφει όχι μία αλλά δύο τιμές (χωρισμένες με κόμμα) με την εντολή return x, y. Στο κυρίως πρόγραμμα καλούμε τη συνάρτηση linsolve με κάποιες συγκεκριμένες τιμές και παίρνουμε πίσω τη λύση του γραμμικού συστήματος.

Παρατηρείστε επίσης στη γραμμή D = oriz(a, b, c, d)+0.0 ότι προσθέτουμε το 0.0. Αυτό φυσικά δεν αλλάζει την τιμή της παράστασης αλλά έχει το σημαντικό αποτέλεσμα ότι αναγκάζει τον python interpreter να θεωρεί τη μεταβλητή D από δω και πέρα πραγματικό αριθμό (float) και σε καμία περίπτωση, ακόμη και αν τα νούμερα a, b, c, d έχουν δοθεί ως ακέραιοι από το χρήστη (όπως στο συγκεκριμένο κυρίως πρόγραμμα) να μη θεωρηθεί το D ως ακέραιος γιατί υπάρχει ο κίνδυνος οι διαιρέσεις με το D μετά από τη γραμμή if D!=0: να θεωρηθούν ως ακέραιες διαιρέσεις (πηλίκο ακεραίων δηλ.) και να βγει λάθος αποτέλεσμα.

In [10]:
def oriz(A, B, C, D):
    "Υπολογίζει την ορίζουσα του πίνακα A B C D"
    return A*D-B*C

def linsolve(a, b, c, d, e, f):
    "Λύνει το γραμμικό σύστημα"
    D = oriz(a, b, c, d)+0.0
    if D != 0:
        x = oriz(e, b, f, d)/D
        y = oriz(a, e, c, f)/D
        return x, y
    print "Bad ..."
    return 0,0
    

print linsolve(2, 1, 1, 3, 5, 6.45345)
(1.7093099999999999, 1.58138)