Σημειωματάριο Πέμπτης 17 Δεκ. 2015

Μια Χριστουγεννιάτικη κάρτα

Σήμερα χρησιμοποιήσαμε μερικά από αυτά που μάθαμε κατά τη διάρκεια του εξαμήνου, κυρίως γραφικά και λίγη γεωμετρία, για να φτιάξουμε μια Χριστουγεννιάτικη κάρτα για το τμήμα μας.

Η ιδέα είναι να φτιάξουμε ένα Χριστουγεννιάτικο δέντρο βασιζόμενοι στο τρίγωνο του Pascal (δείτε και εδώ για να πάρετε μια ιδέα του τι προσπαθούμε να κάνουμε).

Χρησιμοποιήσαμε πολύ τη μικρή βιβλιοθήκη διανυσματικής άλγεβρας που είχαμε γράψει παλιότερα (και την οποία αντιγράψαμε αυτούσια εδώ, ακόμη και κομμάτια της που δε χρειαζόμαστε) και επίσης μια συνάρτηση που είχαμε γράψει παλιότερα για υπολογισμό διωνυμικών συντελεστών. Γενικά, σχεδιάσαμε το τρίγωνο του Pascal μέχρι κάποιο βάθος, με εξαγωνικά κελλιά. Ανάλογα με το ποιος είναι ο ακέραιος (διωνυμικός συντελεστής) που βρίσκεται σε κάθε κελλί δίνουμε και χρώμα στο κελλί.

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

In [6]:
import math
import matplotlib.pyplot as plt
import matplotlib

font = {'family' : 'normal',
        'weight' : 'bold',
        'size'   : 10}
matplotlib.rc('font', **font) # Καθορίζουμε ποιο font χρησιμοποιεί η matplotlib στα κείμενα που γράφει



def plot_polygon(L, fillcolor="white"): 
    """
    L είναι μια λίστα ζευγών (λίστες μήκους δύο, [x, y]) και ζωγραφίζουμε
    το κλειστό πολύγωνο που ορίζουν οι κορυφές αυτές.
    fillcolor είναι το χρώμα με το οποίο γεμίζει το πολύγωνο.
    Αν δε δοθεί παίρνει άσπρο.
    """
    x = [pair[0] for pair in L] + [ L[0][0] ]
    y = [pair[1] for pair in L] + [ L[0][1] ]
    plt.plot(x, y, color="green")
    plt.fill(x, y, color=fillcolor)
    
 
def Vadd(v, w):
    """
    Επιστρέφει το διάνυσμα v+w
    """
    ret = []
    for i,x in enumerate(v):
        ret.append(x+w[i])
    return ret

def Vneg(v):
    """
    Επιστρέφει το αντίθετο διάνυσμα, -v
    """
    ret = []
    for x in v:
        ret.append(-x)
    return ret

def Vrotate(theta, v):
    """
    Επιστρέφει το v περιστραμμένο κατά theta γύρω από την αρχή των αξόνων, (0,0).
    Εδώ το v πρέπει να είναι διδιάστατο διάνυσμα.
    """
    [x, y] = v # παίρνουμε τις συντεταγμένες του v και πολλαπλασιάζουμε με τον πίνακα περιστροφής
    return [ math.cos(theta)*x-math.sin(theta)*y, math.sin(theta)*x + math.cos(theta)*y ]

def Vmul(t, v):
    """
    Επιστρέφει το διάνυσμα v πολλαπλασιασμένο με τον αριθμό t
    """
    ret = []
    for x in v:
        ret.append(t*x)
    return ret

def Vinner(v, w):
    """
    Επιστρέφει το εσωτερικό γινόμενο των v, w
    """
    s = 0.
    for i,x in enumerate(v):
        s += x*w[i]
    return s

def Vcross(v, w):
    """
    Επιστρέφει το εξωτερικό γινόμενο των 3-διάστατων διανυσμάτων v, w.
    Αν τα v, w είναι 2-διάστατα τους προσθέτει ένα μηδενικό στο τέλος.
    """
    vv = v[:]; ww = w[:] # δημιουργεί αντίγραφα των v, w
    if len(vv)==2:
        vv.append(0.)
    if len(ww)==2:
        ww.append(0.)
    return [ vv[1]*ww[2]-vv[2]*ww[1],
             -( vv[0]*ww[2]-vv[2]*ww[0]),
             vv[0]*ww[1]-vv[1]*ww[0] ]


def TriangleArea(v, w, z):
    """
    Επιστρέφει το εμβαδό του τριγώνου με κορυφές τα διανύσματα v, w, z.
    Χρησιμοποιεί τον τύπο βάση x ύψος / 2, και υπολογίζει τα δύο αυτά ύψη με γεωμετρικές πράξεις
    χρησιμοποιώντας τις παραπάνω πράξεις που υλοποιήσαμε ως συναρτήσεις.
    """
    vw = Vadd(w, Vneg(v)) # vw = w -v
    lvw = math.sqrt(Vinner(vw, vw)) # μήκος του vw.
    u = Vmul(1./lvw, vw) # μοναδιαίο διάνυσμα παράλληλο με το vw
    vz = Vadd(z, Vneg(v)) # vz = z -v
    x = Vadd(v, Vmul( Vinner(vz, u), u )) # το x είναι η ορθή προβολή του z στην πλευρά vw
    xz = Vadd(z, Vneg(x)) # xz είναι το διάνυσμα από την κορυφή z στο ίχνος του ύψους της, x
    lxz = math.sqrt(Vinner(xz,xz)) # το μήκος της xz είναι το ύψος που αντιστοιχεί στην πλευρά vw
    return lvw*lxz*0.5

def TriangleAreaNew(v, w, z):
    """
    Επιστρέφει το εμβαδό του τριγώνου με κορυφές τα διανύσματα v, w, z
    με τον τύπο του Ήρωνα
    """
    vw = Vadd(w, Vneg(v)) # το διάνυσμα vw = w - v
    lvw = math.sqrt(Vinner(vw, vw)) # μήκος του vw
    wz = Vadd(z, Vneg(w)) # το διάνυσμα wz = z - w
    lwz = math.sqrt(Vinner(wz, wz)) # μήκος του wz
    zv = Vadd(v, Vneg(z)) # το διάνυσμα zv = v - z
    lzv = math.sqrt(Vinner(zv, zv)) # μήκος του zv
    s = (lvw+lwz+lzv)/2. # ημιπερίμετρος του τριγώνου
    return math.sqrt(s*(s-lvw)*(s-lwz)*(s-lzv)) #ο τύπος του Ήρωνα

def TriangleAreaVeryNew(v, w, z):
    """
    Επιστρέφει το εμβαδό του τριγώνου με κορυφές τα διανύσματα v, w, z
    με εξωτερικό γινόμενο
    """
    vw = Vadd(w, Vneg(v))
    vz = Vadd(z, Vneg(v))
    cross = Vcross(vz, vw) # cross είναι το εξωτερικό γινόμενο των vz, vw
    l = math.sqrt(Vinner(cross, cross)) # το μήκος του cross
    return 0.5*l

def SquareOnSide(v, w):
    """
    Ζωγραφίζει τετράγωνο με πλευρά vw, δεξιά όπως κοιτάμε από v στο w.
    Τα v, w πρέπει να είναι διδιάστατα διανύσματα.
    """
    vw = Vadd(w, Vneg(v))
    vx = Vrotate(-math.pi/2., vw)
    x = Vadd(vx, v)
    y = Vadd(x, vw)
    plot_polygon([ v, w, y, x])
    
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 isprime(n):
    """
    Επιστρέφει True αν και μόνο αν ο n είναι πρώτος
    """
    if n<=1: # Πρώτοι θεωρούνται μόνο φυσικοί αριθμοί > 1
        return False
    d = 2
    while d*d <= n: # Δοκιμάζουμε όλους τους ακεραίους από 2 έως την τετρ. ρίζα του n
        if n%d == 0: # Αν βρήκαμε διαιρέτη επιστρέφουμε αμέσως False
            return False
        d += 1
    return True # Το while παραπάνω τελείωσε και δε βρήκαμε διαιρέτη. Επιστρέφουμε True.

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

def hexagon(A, l, text, fillcolor="white"):
    """
    Ζωγραφίζει ένα κανονικό εξάγωνο με δύο απέναντι πλευρές του παράλληλες με τον άξονα των x.
    Α είναι το σημείο στο αριστερό άκρο της πάνω από τις δύο αυτές πλευρές.
    l είναι το μήκος κάθε πλευράς
    text είναι το κείμενο που γράφεται (κεντραρισμένο) στο κέντρο του εξαγώνου
    fillcolor είναι το χρώμα με το οποίο γεμίζει το εξάγωνο (white αν δε δοθεί)
    """
    B = Vadd(A, [l, 0])
    u = Vrotate(-math.pi/3., [l, 0])
    C = Vadd(B, u)
    D = Vadd(C, Vrotate(-math.pi/3., u))
    E = Vadd(D, [-l, 0])
    F = Vadd(E, Vneg(u))
    plot_polygon([A, B, C, D, E, F], fillcolor=fillcolor)
    pos = Vadd(A, u)
    plt.text(pos[0], pos[1], text, fontsize=10, horizontalalignment="center", verticalalignment="center")
    
L = 2 # Μήκος πλευράς του κάθε εξαγώνου
N = 10 # Πόσα επίπεδα ζωγραφίζουμε στο τρίγωνο του Pascal
W = [3*L, 0] # Διάνυσμα διαφορά του κάθε εξαγώνου από το προηγούμενό του (αριστερά του)
V = Vadd([-L,0], Vrotate(math.pi/3., [-L,0])) # Διάνυσμα διαφορά του κάθε εξαγώνου από το αμέσως πάνω και δεξιά του
V = Vadd([0, -0.2*L], Vmul(1.5, V)); W = Vmul(1.5, W) # Τα αραιώνουμε λίγο

first = [0,0] # θέση του πρώτου (πάνω) εξαγώνου

T = Vadd(first, [0.5*L, 2*L]) # T, T1, T2 είναι οι κορυφές του πράσινου τριγώνου που ζωγραφίζουμε πίσω από τα εξάγωνα
T1 = Vadd(T, Vmul((N+2.), V))
V1 = [-V[0], V[1]]
T2 = Vadd(T, Vmul((N+2.), V1))
plot_polygon([T, T1, T2], fillcolor="lightgreen") # Ζωγραφίζουμε το τρίγωνο που θα είναι πίσω από τα εξάγωνα.

hexagon(first, L, "1", fillcolor="yellow") # Το πάνω εξάγωνο
for n in range(1,N): # Για κάθε γραμμή του τριγώνου του Pascal
    first = Vadd(first, V)
    second = first
    for k in range(n+1): # Για κάθε στήλη του τριγώνου του Pascal
        r = binomial(n,k) # Ο αριθμός μέσα στο εξάγωνο
        c = "green" if r%2!=1 else "red" # Χρώμα του εξαγώνου: πράσινο για άρτια, κόκκινο για περιττά νούμερα
        hexagon(second, L, "%d"%r, fillcolor=c) # Ζωγραφίζουμε το εξάγωνο
        second = Vadd(second, W)

plt.axes().set_aspect(2) # Τεντώνουμε λίγο τον y-άξονα για να δείχνει πιο ψηλό το δέντρο.
plt.axis('off')
plt.show()
In []: