Διάλεξη 10ης Οκτωβρίου 2016

Προγραμματισμός με τη γλώσσα Python

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

Πρόβλημα 1: Βρείτε τον μέγιστο τριών ακεραίων $x, y, z$.


x = 12
y = 19
z = 17

largest = x
if y > largest: largest = y
if z > largest: largest = z

print('The max of', x, y, z, 'is', largest)

Η μέθοδος γενικεύεται εύκολα για οποιοδήποτε πλήθος αριθμών αλλά και στην εύρεση του ελαχίστου μεταξύ κάποιων αριθμών ή ακόμα και σε προβλήματα όπου ο ζητούμενος αριθμός ικανοποιεί περισσότερες από μια συνθήκες, όπως στο παρακάτω όπου βρίσκουμε τον μεγαλύτερο περιττό μεταξύ των $x, y, z$.


x = 16; y = 19; z = 24
largest = None

if x%2: largest = x
if y%2 and (largest == None or y > largest): largest = y
if z%2 and (largest == None or z > largest): largest = z

if largest != None:
    print('The max odd number among', x, y, z, 'is', largest)
else:
    print('All numbers are even.')

Παρατηρήστε εδώ ότι η σύγκριση δύο αριθμών γίνεται μόνο στην περίπτωση που είναι και οι δύο περιττοί. Για να χειριστούμε την περίπτωση όπου και οι τρεις αριθμοί είναι άρτιοι, δίνουμε αρχικά στην μεταβλητή largest την τιμή None. Αν μετά τις συγκρίσεις των αριθμών ανα ζεύγη η τιμή της μεταβλητής αυτής παραμείνει None τότε και οι τρεις αριθμοί είναι άρτιοι.

Πρόβλημα 2: Ζωγραφίστε ένα χριστουγεννιάτικο δέντρο ύψους $n$, όπου $n$ είναι περιττός αριθμός. Για παράδειγμα, αν $n = 3$ το δέντρο πρέπει να είναι το


  *
 ***
*****

Η λύση στο συγκεκριμένο πρόβλημα είναι αρκετά απλή. Αφου βεβαιωθούμε ότι το ύψος είναι περιττός αριθμός


while 1:
    n = int(input('Height of tree [must be odd]: '))
    if n%2: break

ζωγραφίζουμε το δέντρο κατά επίπεδα. Το πρώτο επίπεδο έχει ένα αστεράκι, το δεύτερο, τρία και εν γένει το $i$-στό επίπεδο θα έχει $2i-1$ αστεράκια. Όλα αυτά πρέπει να εκτυπωθούν σε ένα πεδίο πλάτους $2n-1$. Eπειδή $2n-1 - (2i-1) = (n-i) + (n-i)$ εκτυπώνουμε $n-i$ κενούς χαρακτήρες ακολουθούμενους από $2i-1$ αστεράκια.


i = 1
while i <= n:
    print( (n-i)*' ' + (2*i-1)*'*' )
    i += 1

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


    *
   *o*
  *o*o*
 *o*o*o*
*o*o*o*o*

Πρόβλημα 3: Η μέθοδος της δυαδικής αναζήτησης. Ας υποθέσουμε ότι αναζητούμε μια προσέγγιση του αριθμού $\sqrt{2}$. Γνωρίζουμε, φυσικά, ότι $1 \lt \sqrt{2} \lt 2$ οπότε μια πρώτη προσέγγιση είναι ο αριθμός $\frac{3}{2} = 1.5$, το μέσο του διαστήματος $[1, 2]$. Τώρα $1.5^2 = 2.25 \gt 2$ άρα ο αριθμός $\sqrt{2}$ βρίσκεται στο διάστημα $[1, 1.5]$ και επομένως μια καλύτερη(;) προσέγγισή του είναι το μέσο αυτού του διαστήματος, δηλαδή ο αριθμός $1.25$. Μπορούμε να συνεχίσουμε αυτή τη διαδικασία υποδιπλασιάζοντας κάθε φορά το μήκος του διαστήματος το οποίο περιέχει τον αριθμος $\sqrt{2}$.


x = 2; eps = 1.0e-3

low = 1; high = 2;
mid = (low + high) / 2

while abs(x - mid**2) > eps:
    print('low =', low, 'high =', high, 'Approximation =', mid)
    if mid**2 < x:
        low = mid
    else:
        high = mid
    mid = (low + high) / 2

print('The square root of 2 is approximately', mid)

Πρόβλημα 4: Ένα ρομπότ βρίσκεται στο σημείο $(0,0)$. Μπορεί να κινηθεί πρός τα τέσσερα σημεία του ορίζοντα (North, South, East, West) κατά μια δεδομένη απόσταση. Για παράδειγμα, μετά την εντολή N2 το ρομπότ θα βρίσκεται στη θέση $(0, 2)$. Που θα βρίσκεται το ρομπότ μετά την εντολή 'N2E3S4W1S2S3E5N9' και πόση απόσταση θα έχει καλύψει; Υποθέτουμε ότι η απόσταση που ζητάμε να κινηθεί το ρομπότ είναι ένας ακέραιος στο διάστημα $[0, 10)$.


x = 0; y = 0
cmd = 'N2E3S4W1S2E3W5N9'
distance = 0
for i in range(0,len(cmd),2):
    d = cmd[i]
    l = int(cmd[i+1])
    if d == 'N':
        y += l
    elif d == 'E':
        x += l
    elif d == 'S':
        y -= l
    else:
        x -= l

    distance += l

print('After the command', cmd, 'the robot is at', x, y)
print('Distance covered =', distance)

Θα μπορούσατε να γενικεύσετε την παραπάνω λύση αν οι εντολές που λαμβάνει το ρομπότ είναι της μορφής H4.3V-1.2H-2.12; Εδώ H, V είναι η οριζόντια και η κάθετη διεύθυνση, αντίστοιχα, και η απόσταση μπορεί να είναι κάποιος πραγματικός αριθμός.

Πρόβλημα 5: Ένας τρόπος που οι επιστήμονες συγκρίνουν δύο αλληλουχίες DNA, π.χ., 'TAATGCCTGAAT' και 'CTCTATGCC' είναι να τοποθετήσουν την πρώτη αλυσίδα στον οριζόντιο άξονα, τη δεύτερη στο κατακόρυφο και σε όποιο σημείο οι βάσεις συμφωνούν να τοποθετήσουν μια μονάδα, διαφορετικά το μηδέν.


dnax = 'TAATGCCTGAAT'
dnay = 'CTCTATGCC'

for x in dnax:
    for y in dnay:
        if x == y: print(1, end=' ')
        else: print(0, end='')
    print()

Παρατηρήστε εδώ τη μορφή της συνάρτησης print. Το επιπλέον όρισμα end=' ' απαγορεύει την αλλαγή γραμμής μετά την εκτύπωση του προηγούμενο ορίσματος αλλά στην εκτύπωση ενός κενού χαρακτήρα. Η εντολή print() (ισοδύναμα print('')) οδηγεί την εκτύπωση στην επόμενη γραμμή. Για τις δύο αλληλουχίες DNA 'TAATGCCTGAAT' και 'CTCTATGCC' το παραπάνω πρόγραμμα εκτυπώνει


0 1 0 1 0 1 0 0 0 
0 0 0 0 1 0 0 0 0 
0 0 0 0 1 0 0 0 0 
0 1 0 1 0 1 0 0 0 
0 0 0 0 0 0 1 0 0 
1 0 1 0 0 0 0 1 1 
1 0 1 0 0 0 0 1 1 
0 1 0 1 0 1 0 0 0 
0 0 0 0 0 0 1 0 0 
0 0 0 0 1 0 0 0 0 
0 0 0 0 1 0 0 0 0 
0 1 0 1 0 1 0 0 0

Ο ενδιαφερόμενος αναγνώστης μπορεί να συμβουλευτεί τον σύνδεσμο Illustrating Python via Bioinformatics Examples για περισσότερα παραδείγματα χρήσης της Python σε προβλήματα βιο-πληροφορικής.

Πρόβλημα 6: Το Sudoku παίζεται μέσα σε ένα 9x9 πλέγμα, που διαιρείται 3x3 στα υπο-πλέγματα αποκαλούμενα "περιοχές". Το παιχνίδι αρχίζει με μερικά κελιά του πλέγματος ήδη γεμάτα με ένα από τα ψηφία 1 ως 9. Σκοπός είναι να συμπληρωθούν τα κενά κελιά έτσι ώστε κάθε αριθμός να εμφανίζεται μια μόνο φορά σε κάθε γραμμή, στήλη και περιοχή. Αν δοθεί η ακολουθία χαρακτήρων '..3.2.6..9..3.5..1..18.64....81.29..7.......8..67.82....26.95..8..2.3..9..5.1.3..', όπου ο χαρακτήρας τελεία υπονοεί ότι το αντίστοιχο κελί είναι άδειο, μπορείτε να την μετατρέψετε στο συνηθισμένο 9x9 πλέγμα του Sudoku; Υποθέτουμε ότι η ακολουθία χαρακτήρων περιέχει τη μια γραμμή μετά την άλλη.


s = '..3.2.6..9..3.5..1..18.64....81.29..7.......8..67.82....26.95..8..2.3..9..5.1.3..'
for i in range(9): print(s[9*i:9*(i+1)])

Για την παραπάνω ακολουθία χαρακτήρων θα εκτυπωθεί το πλέγμα


..3.2.6..
9..3.5..1
..18.64..
..81.29..
7.......8
..67.82..
..26.95..
8..2.3..9
..5.1.3..

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


---+---+---
..3|.2.|6..
9..|3.5|..1
..1|8.6|4..
---+---+---
..8|1.2|9..
7..|...|..8
..6|7.8|2..
---+---+---
..2|6.9|5..
8..|2.3|..9
..5|.1.|3..
---+---+---

Πρόβλημα 7: Το πρόγραμμα mastermind διαλέγει ένα τετραψήφιο αριθμό, τα ψηφία του οποίου είναι μεταξύ του 1 και του 4, και ο παίκτης προσπαθεί να βρεί τον αριθμό. Σε κάθε βήμα το πρόγραμμα τυπώνει μόνο τον αριθμό των ψηφίων που είναι σωστά αλλά όχι τις θέσεις τους.


# This is the number the player will have to guess
x = '3224'
tries = 1
while True:
    g = input('Enter your guess: ')
    n = 0
    for i in range(4):
        if g[i] == x[i]:
            print('*', end='')
            n += 1
    print()
    if n == 4:
        print('Congratulations! You found it in', tries, 'tries.')
        break
    tries += 1