# data[].attributes.headline
5ο φυλλάδιο ασκήσεων

# data[].attributes.subject
5ο φυλλάδιο ασκήσεων

# data[].attributes.body
Αλγόριθμοι ταξινόμησης

# data[].attributes.headline
5ο φυλλάδιο ασκήσεων

# data[].attributes.subject
5ο φυλλάδιο ασκήσεων

# data[].attributes.body
Πρόβλημα 1

# data[].attributes.attachment_link.readable_text
# Φυλλάδιο 5 - Πρόβλημα 1

# Γραμμική αναζήτηση του μικρότερου στοιχείου στη λίστα L[startIndex:].
# Η συνάρτηση cmpFunc χρησιμοποιείται για τη σύγκριση στοιχείων.

def linearSearch(L, startIndex, cmpFunc):
    index = startIndex
    for i in range(startIndex+1, len(L)):
        if cmpFunc(L[i], L[index]):
            index = i
    return index

# Ταξινόμηση με επιλογή. Η σύγκριση στοιχείων γίνεται με τη συνάρτηση cmpFunc

def selectionSort(L, cmpFunc):
    for i in range(len(L)):
        index = linearSearch(L, i, cmpFunc)
        L[i], L[index] = L[index], L[i]

# Η συνάρτηση isCloser επιστρέφει True αν το σημείο P1 είναι πλησιέστερο στην
# αρχή από το σημείο P2, διαφορετικά επιστρέφει False

def isCloser(P1, P2):
    d1 = P1[0]**2 + P1[1]**2 + P1[2]**2
    d2 = P2[0]**2 + P2[1]**2 + P2[2]**2
    return d1 < d2

# Η λίστα L περιέχει  Ν τυχαία σημεία στο [0, 1000)^3

N = 100
L = []

import random

for i in range(N):
    x = 1000 * random.random()
    y = 1000 * random.random()
    z = 1000 * random.random()
    L.append((x,y,z))

# Ταξινομούμε τα στοιχεία με βάση την απόστασή τους από την αρχή των αξόνων

selectionSort(L, isCloser)

# data[].attributes.headline
5ο φυλλάδιο ασκήσεων

# data[].attributes.subject
5ο φυλλάδιο ασκήσεων

# data[].attributes.body
Πρόβλημα 2

# data[].attributes.attachment_link.readable_text
# Φυλλάδιο 5 - Πρόβλημα 2

# H συνάρτηση merge3way συγχωνεύει τρεις ταξινομημένες λίστες [left,one3], [one3+1,two3]
# και [two3+1,right] σε μια ταξινομημένη λίστα

def merge3way(L, left, one3, two3, right):
    lcopy = L[left:one3+1]
    mcopy = L[one3+1:two3+1]
    rcopy = L[two3+1:right+1]

    i = j = k = 0; l = left

    while i < len(lcopy) and j < len(mcopy) and k < len(rcopy):
        if lcopy[i] <= mcopy[j] and lcopy[i] <= rcopy[k]:
            L[l] = lcopy[i]; i += 1
        elif mcopy[j] <= rcopy[k]:
            L[l] = mcopy[j]; j += 1
        else:
            L[l] = rcopy[k]; k += 1
        l += 1

#   Ελέγχουμε αν οι [left,one3], [one3+1,two3] έχουν εναπομείνοντα στοιχεία
#   μετά τη συγχώνευση

    while i < len(lcopy) and j < len(mcopy):
        if lcopy[i] <= mcopy[j]:
            L[l] = lcopy[i]; i += 1
        else:
            L[l] = mcopy[j]; j += 1
        l += 1

#   Ελέγχουμε αν οι [left,one3], [two3+1,right] έχουν εναπομείνονται στοιχεία
#   μετά τη συγχώνευση

    while i < len(lcopy) and k < len(rcopy):
        if lcopy[i] <= rcopy[k]:
            L[l] = lcopy[i]; i += 1
        else:
            L[l] = rcopy[k]; k += 1
        l += 1

#   Ελέγχουμε αν οι [one3+1,two3], [two3+1,right] έχουν εναπομείνονται στοιχεία
#   μετά τη συγχώνευση

    while j < len(mcopy) and k < len(rcopy):
        if mcopy[j] <= rcopy[k]:
            L[l] = mcopy[j]; j += 1
        else:
            L[l] = rcopy[k]; k += 1
        l += 1

#   Συγχωνεύουμε τυχόν εναπομείνοντα στοιχεία

    while i < len(lcopy):
        L[l] = lcopy[i]; i += 1; l += 1

    while j < len(mcopy):
        L[l] = mcopy[j]; j += 1; l += 1

    while k < len(rcopy):
        L[l] = rcopy[k]; k += 1; l += 1

# Η συγχωνευτική ταξινόμηση με 3 λίστες

def mergesort3(L, left, right):
    if left < right:
        one3 = left +   (right - left) // 3;
        two3 = left + 2*(right - left) // 3;
        mergesort3(L, left, one3)
        mergesort3(L, one3+1, two3)
        mergesort3(L, two3+1, right)
        merge3way(L, left, one3, two3, right)

# Συγχώνευση δύο ήδη ταξινομημένων λιστών

def merge(L, left, middle, right):
    lcopy = L[left:middle + 1]
    rcopy = L[middle + 1:right + 1]

    i = j = 0; k = left

    while i < len(lcopy) and j < len(rcopy):
        if lcopy[i] <= rcopy[j]:
            L[k] = lcopy[i]; i += 1
        else:
            L[k] = rcopy[j]; j += 1
        k += 1

    while i < len(lcopy):
        L[k] = lcopy[i]; i += 1; k += 1

    while j < len(rcopy):
        L[k] = rcopy[j]; j += 1; k += 1

# Η ταξινόμηση με συγχώνευση

def mergesort(L, left, right):
    if left < right:
        middle = (left + right) // 2
        mergesort(L, left, middle)
        mergesort(L, middle + 1, right)
        merge(L, left, middle, right)

# Σύγκριση της mergesort και της mergesort3

import random
import time

N = 100000
L1 = [random.randrange(100000) for i in range(N)]
L2 = L1.copy()

start = time.perf_counter()
mergesort(L1, 0, len(L1) - 1)
stop  = time.perf_counter()
print('mergesort took {:.3f} seconds to sort {:d} numbers'.format(stop-start, N))

start = time.perf_counter()
mergesort3(L2, 0, len(L2) - 1)
stop  = time.perf_counter()
print('mergesort3 took {:.3f} seconds to sort {:d} numbers'.format(stop-start, N))

# data[].attributes.headline
5ο φυλλάδιο ασκήσεων

# data[].attributes.subject
5ο φυλλάδιο ασκήσεων

# data[].attributes.body
Πρόβλημα 3

# data[].attributes.attachment_link.readable_text
# Φυλλάδιο 5 - Πρόβλημα 3

#                       i                j
# +-------+--------------+--------------+---------------+
# | pivot | elem < pivot | elem > pivot | unpartitioned |
# +-------+--------------+--------------+---------------+

def partition(L, left, right):
    x = L[left]
    i = left+1
    for j in range(left+1, right+1):
        if L[j] <= x:
            L[i], L[j] = L[j], L[i]
            i += 1
    L[i-1], L[left] = L[left], L[i-1]
    return i-1

# Η qquicksort όπως και πριν

def quicksort(L, left, right):
    if left < right:
        q = partition(L, left, right)
        quicksort(L, left, q-1)
        quicksort(L, q+1, right)

# Test

import random
L = [random.randint(1, 100) for i in range(100)]
LL = L[:]

# Ταξιμομούμε με την quicksort και συγκρίνουμε με την ενσωματωμένη
# συνάρτηση ταξινόμησης

quicksort(L, 0, len(L) - 1)
print(sorted(LL) == L)

# data[].attributes.headline
5ο φυλλάδιο ασκήσεων

# data[].attributes.subject
5ο φυλλάδιο ασκήσεων

# data[].attributes.body
Πρόβλημα 4

# data[].attributes.attachment_link.readable_text
# Φυλλάδιο 5 - Πρόβλημα 4

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

# Διαμερίζουμε τη λίστα στις λίστες [left, q-1] και [q+1, right]
# και επιστρέφουμε την τιμή του οδηγού (pivot)

def partition(L, left, right):
    x = L[right]
    i = left - 1
    for j in range(left, right):
        if L[j] <= x:
            i += 1
            L[i], L[j] = L[j], L[i]
    L[i+1], L[right] = L[right], L[i+1]
    return i+1

# Ταξινόμηση με την quicksort

def quicksort(L, left, right):
    if left < right:
        q = partition(L, left, right)
        quicksort(L, left, q-1)
        quicksort(L, q+1, right)

# Επιλογή ενός τυχαίου στοιχείου ως οδηγού

def partition_random_pivot(L, left, right):
    pivot = random.randint(left, right)
    L[pivot], L[right] = L[right], L[pivot]
    x = L[right]
    i = left - 1
    for j in range(left, right):
        if L[j] <= x:
            i += 1
            L[i], L[j] = L[j], L[i]
    L[i+1], L[right] = L[right], L[i+1]
    return i+1

def quicksort_random_pivot(L, left, right):
    if left < right:
        q = partition_random_pivot(L, left, right)
        quicksort_random_pivot(L, left, q-1)
        quicksort_random_pivot(L, q+1, right)

# Test

import random
import time

N = 900 # Do not try anything higher, it will cause a RecursionError!

L1 = list(range(N))
L2 = L1.copy()

start = time.perf_counter()
quicksort(L1, 0, len(L1) - 1)
stop  = time.perf_counter()
print('quicksort took {:.3f} seconds to sort {:d} numbers'.format(stop-start, N))

start = time.perf_counter()
quicksort_random_pivot(L2, 0, len(L2) - 1)
stop  = time.perf_counter()
print('quicksort with random pivot took {:.3f} seconds to sort {:d} numbers'.format(stop-start, N))

# data[].attributes.headline
5ο φυλλάδιο ασκήσεων

# data[].attributes.subject
5ο φυλλάδιο ασκήσεων

# data[].attributes.body
Πρόβλημα 5

# data[].attributes.attachment_link.readable_text
# Φυλλάδιο 5 - Πρόβλημα 5

# Ιδέα:
#
# - Διαμοιράζουμε τους δείκτες [left,right], όπως και στην quicksort
#   στα [left, q] και [q+1, right]
#
# - Αν n = q - left + 1 είναι ίσο με k τότε, το k-στο μικρότερο στοιχείο
#   βρίσκεται στη θέση q
#
# - Διαφορετικά, αν n  > k τότε αναζητούμε το k-στο μικρότερο στοιχείο
#   ανάμεσα στα στοιχεία με δείκτες [left, q]
#
#    left       k      q                             right
#     *---------*------*------------------------------*
#
#     <------ n ------>
#
# - Διαφορετικά, αναζητούμε το (k-n)-στο μικρότερο στοιχείο
#   ανάμεσα στα στοιχεία με δείκτες [q+1, right]
#
#    left              q             k               right
#     *----------------*-------------*----------------*
#
#     <------ n ------><---- k-n ---->

def partition(L, left, right):
    x = L[right]
    i = left - 1
    for j in range(left, right):
        if L[j] <= x:
            i += 1
            L[i], L[j] = L[j], L[i]
    L[i+1], L[right] = L[right], L[i+1]
    return i+1

# kth smallest element

def kthSmallest(L, k, left, right):
    if left == right:
        return L[left]

    q = partition(L, left, right)
    n = q - left + 1

    if n == k:
        return L[q]
    elif n > k:
        return kthSmallest(L, k, left, q-1)
    else:
        return kthSmallest(L, k-n, q+1, right)

# Test 

import random

L = [random.randint(1, 100) for i in range(10)]
LL = L[:]
print('Original list')
print(L)

k = 7
kth = kthSmallest(L, k, 0, len(L)-1)

if k == 1:
    suffix = 'st'
elif k == 2:
    suffix = 'nd'
elif k == 3:
    suffix = 'rd'
else:
    suffix = 'th'

M = sorted(LL)
if M[k-1] == kth:
    print('The {}-{} smallest elemet is {}'.format(k, suffix, kth))

# data[].attributes.headline
5ο φυλλάδιο ασκήσεων

# data[].attributes.subject
5ο φυλλάδιο ασκήσεων

# data[].attributes.body
Πρόβλημα 6

# data[].attributes.attachment_link.readable_text
# Φυλλάδιο 5 - Πρόβλημα 6

# Ιδέα:
#
# - Κατασκευάζουμε τη λίστα count η οποία περιέχει τη συχνότητα εμφάνισης κάθε
#   στοιχείου στη λίστα
#
# - Αν το στοιχείο j είναι στη λίστα τότε η ταξινομημένη λίστα θα περιέχει αυτό
#   το στοιχείο ακριβώς count[j] φορές

def countSort(L, maxval):
    n = maxval+1
    count = [0] * n
    for e in L: count[e] += 1

    i = 0
    for j in range(1,n):
        for k in range(count[j]):
            L[i] = j;  i += 1

# Test

import random

N = 240
L = [random.randint(1, 100) for i in range(240)]
LL = L[:]

# Ταξινόμηση με την countSort και σύγκριση με την ενσωματωμένη
# συνάρτησης ταξινόμησης

countSort(L, 100)
print(sorted(LL) == L)

# data[].attributes.body
Η mergesort

# data[].attributes.attachment_link.readable_text
# Margesort

# Συγχώνευση δύο ήδη ταξινομημένων λιστών

def merge(L, left, middle, right):
    lcopy = L[left:middle + 1]
    rcopy = L[middle + 1:right + 1]

    i = j = 0; k = left

    while i < len(lcopy) and j < len(rcopy):
        if lcopy[i] <= rcopy[j]:
            L[k] = lcopy[i]; i += 1
        else:
            L[k] = rcopy[j]; j += 1
        k += 1

    while i < len(lcopy):
        L[k] = lcopy[i]; i += 1; k += 1

    while j < len(rcopy):
        L[k] = rcopy[j]; j += 1; k += 1

# Η ταξινόμηση με συγχώνευση

def mergesort(L, left, right):
    if left < right:
        middle = (left + right) // 2
        mergesort(L, left, middle)
        mergesort(L, middle + 1, right)
        merge(L, left, middle, right)

# Test

import random
L = [random.randint(1, 100) for i in range(100)]
LL = L[:]

# Sort and compare with built-in sort function

mergesort(L, 0, len(L) - 1)
print(sorted(LL) == L)

# data[].attributes.body
Η quicksort

# data[].attributes.attachment_link.readable_text
# Διαμερίζουμε τη λίστα στις λίστες [left, q-1] και [q+1, right]
# και επιστρέφουμε την τιμή του οδηγού (pivot)

def partition(L, left, right):
    x = L[right]
    i = left - 1
    for j in range(left, right):
        if L[j] <= x:
            i += 1
            L[i], L[j] = L[j], L[i]
    L[i+1], L[right] = L[right], L[i+1]
    return i+1

# Ταξινόμηση με την quicksort

def quicksort(L, left, right):
    if left < right:
        q = partition(L, left, right)
        quicksort(L, left, q-1)
        quicksort(L, q+1, right)

# Test

import random
L = [random.randint(1, 100) for i in range(100)]
LL = L[:]

# Ταξινομούμε με την quicksort και συγκρίνουμε με την ενσωματωμένη
# συνάρτηση ταξινόμησης

quicksort(L, 0, len(L) - 1)
print(sorted(LL) == L)

# data[].attributes.headline
6ο φυλλάδιο ασκήσεων

# data[].attributes.subject
6ο φυλλάδιο ασκήσεων

# data[].attributes.body
Κλάσεις και αντικείμενα

# data[].attributes.headline
6ο φυλλάδιο ασκήσεων

# data[].attributes.subject
6ο φυλλάδιο ασκήσεων

# data[].attributes.body
Πρόβλημα 1

# data[].attributes.attachment_link.readable_text
# Φυλλάδιο 6 - Πρόβλημα 1

# Tο RGB (Red Green Blue) είναι ένα μοντέλο χρώματος στο οποίο προστίθενται
# κόκκινο, πράσινο και μπλε χρώμα με διάφορους τρόπους για την παραγωγή μιας
# ευρείας γκάμας χρωμάτων.
# Η μηδενική ένταση για κάθε συνιστώσα δίνει το μαύρο, ενώ η πλήρης ένταση, 255,
# για κάθε συνιστώσ δίνει το λευκό.

class Color:
    def __init__(self, red, green, blue):
        self.red = red
        self.green = green
        self.blue = blue
    def __repr__(self):
        return 'RGB({},{},{})'.format(self.red, self.green, self.blue)
    def brightness(self):
        cmax = max(self.red, self.green, self.blue)
        cmin = min(self.red, self.green, self.blue)
        return 0.5 * (cmin + cmax) / 256

# Test

purple = Color(128, 0, 128)
print('The brightness of {} is {}'.format(purple, purple.brightness()))

# data[].attributes.headline
6ο φυλλάδιο ασκήσεων

# data[].attributes.subject
6ο φυλλάδιο ασκήσεων

# data[].attributes.body
Πρόβλημα 2

# data[].attributes.attachment_link.readable_text
# Φυλλάδιο 6 - Πρόβλημα 2

class BankAccount:
    def __init__(self, name, surname, afm):
        self.name = name
        self.surname = surname
        self.afm = afm
        self.balance = 0
    def __repr__(self):
        return 'Account of {}, {}\n'.format(self.surname, self.name) + \
               'Balance: {}'.format(self.balance)
    def deposit(self, amount):
        self.balance += amount
    def withdrawal(self, amount):
        if self.balance >= amount:
            self.balance -= amount
    def getBalance(self):
        return self.balance

# Test

account = BankAccount('John', 'Smith', '123456789')

account.deposit(100)
account.deposit(200)
account.deposit(300)

account.withdrawal(400)

print(account)

# data[].attributes.headline
6ο φυλλάδιο ασκήσεων

# data[].attributes.subject
6ο φυλλάδιο ασκήσεων

# data[].attributes.body
Πρόβλημα 3

# data[].attributes.attachment_link.readable_text
# Φυλλάδιο 6 - Πρόβλημα 3

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __repr__(self):
        return '({}, {})'.format(self.x, self.y)
    def reflect_x(self):
        return Point(self.x, -self.y)
    def reflect_y(self):
        return Point(-self.x, self.y)
    def reflect_origin(self):
        return Point(-self.x, -self.y)

p = Point(1,3)
print("Point:", p)
print("Reflection across x:", p.reflect_x())
print("Reflection across y:", p.reflect_y())
print("Reflection across the origin:", p.reflect_origin())

# data[].attributes.headline
6ο φυλλάδιο ασκήσεων

# data[].attributes.subject
6ο φυλλάδιο ασκήσεων

# data[].attributes.body
Πρόβλημα 4

# data[].attributes.attachment_link.readable_text
# Φυλλάδιο 6 - Πρόβλημα 4
#
# Αναπαραστούμε ένα παραλληλόγραμμο δίνοντας τις συντεταγμένες ενός σημείου του, αυτό με
# την ελάχιστη x- και y- συντεταγμένη (το κάτω-αριστερά σημείο), και το πλάτος και ύψος του.
#
#            width
#     +----------------+
#     |                |
#     |                | height
#     |                |
#     +----------------+
#  (x0,y0)

class Rectangle:
    def __init__(self, x0, y0, width, height):
        self.x0 = x0
        self.y0 = y0
        self.width = width
        self.height = height
    def area(self):
        return self.width * self.height
    def contains(self, x, y):
        return (self.x0 <= x <= self.x0+self.width ) and \
               (self.y0 <= y <= self.y0+self.height)

# Test

R = Rectangle(1, 2, 3.4, 2)
print(R.area())
print(R.contains(4, 4))

# data[].attributes.headline
6ο φυλλάδιο ασκήσεων

# data[].attributes.subject
6ο φυλλάδιο ασκήσεων

# data[].attributes.body
Πρόβλημα 5

# data[].attributes.attachment_link.readable_text
# Φυλλάδιο 5 - Πρόβλημα 5

import math

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __repr__(self):
        return '({},{})'.format(self.x, self.y)

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)
    def __sub__(self, other):
        return Vector(self.x - other.x, self.y - other.y)
    def __mul__(self, val):
        return Vector(self.x * val, self.y * val)
    def __rmul__(self, val):
        return Vector(self.x * val, self.y * val)

    def dot(self, other):
        return self.x * other.x + self.y * other.y


x = Vector(-1,2)
y = Vector(1, 1)
r = 2.0

print('The sum of {} and {} is {}'.format(x, y, x+y))
print('The difference of {} and {} is {}'.format(x, y, x-y))
print('{} times {} is equal to {}'.format(x, r, x*r))
print('{} times {} is equal to {}'.format(r, x, r*x))
print('The dot product of {} and {} is equal to {}'.format(x, y, x.dot(y)))

# data[].attributes.headline
6ο φυλλάδιο ασκήσεων

# data[].attributes.subject
6ο φυλλάδιο ασκήσεων

# data[].attributes.body
Πρόβλημα 6

# data[].attributes.attachment_link.readable_text
# Φυλλάδιο 6 - Πρόβλημα 6

class Frac:
    def __init__(self, num, den):
        if num == 0: den = 1
        if den < 0:
            num = -num
            den = -den
        n = abs(num);  d = abs(den)
        while d:
            n, d = d, n % d
        self.num = num // n
        self.den = den // n

    def __repr__(self):
        if self.den == 1:
            return '{}'.format(self.num)
        else:
            return '{}/{}'.format(self.num, self.den)

    def __add__(self, other):
        a = self.num;  b = self.den;  c = other.num;  d = other.den
        return Frac(a*d+b*c, b*d)
    def __sub__(self, other):
        a = self.num;  b = self.den;  c = other.num;  d = other.den
        return Frac(a*d-b*c, b*d)
    def __mul__(self, other):
        a = self.num;  b = self.den;  c = other.num;  d = other.den
        return Frac(a*c, b*d)
    def __truediv__(self, other):
        a = self.num;  b = self.den;  c = other.num;  d = other.den
        return Frac(a*d, b*c)

    def __neg__(self):
        return Frac(-self.num, self.den)

# Test

f = Frac(-12,64)
g = Frac(7, 32)

print('The sum of {} and {} is {}'.format(f, g, f+g))
print('The differnce of {} and {} is {}'.format(f, g, f-g))
print('The product of {} and {} is {}'.format(f, g, f*g))
print('The quotient of {} and {} is {}'.format(f, g, f/g))
print('The negative of {} is {}'.format(f, -f))

# data[].attributes.headline
6ο φυλλάδιο ασκήσεων

# data[].attributes.subject
6ο φυλλάδιο ασκήσεων

# data[].attributes.body
Πρόβλημα 7

# data[].attributes.attachment_link.readable_text
# Φυλλάδιο 6 - Πρόβλημα 7

class Queue:
    def __init__(self):
        self.list = []
    def __repr__(self):
        return str(self.list)

    def enqueue(self, x):
        self.list.append(x)
    def dequeue(self):
        if len(self.list) == 0:
            return None
        else:
            result = self.list[0]
            del self.list[0]
            return result

    def __len__(self):
        return len(self.list)

# Test

import random

queue = Queue()
print('State of queue: {}'.format(queue))
print('The length of {} is {}'.format(queue, len(queue)))
print()

for i in range(10):
    queue.enqueue(random.randint(1,100))

print('State of queue: {}'.format(queue))
print('The length of {} is {}'.format(queue, len(queue)))
print()

while len(queue) != 0:
    x = queue.dequeue()
    print("Dequeuing {} -> {}".format(x, queue))

# data[].attributes.headline
6ο φυλλάδιο ασκήσεων

# data[].attributes.subject
6ο φυλλάδιο ασκήσεων

# data[].attributes.body
Πρόβλημα 8

# data[].attributes.attachment_link.readable_text
# Φυλλάδιο 6 - Πρόβήμα 8

class Course:
    def __init__(self, name, code, year, instructors):
        self.name = name
        self.code = code
        self.year = year
        self.instructors = instructors
        self.students = []

    def __repr__(self):
        return '{} {}'.format(self.code, self.name) + '\n' + \
               'Έτος: {}'.format(self.year) + '\n' + \
               'Διδάσκοντες: {}'.format(self.instructors) + '\n' + \
               'Φοιτητές: {}'. format(self.students) + '\n'

    def enroll(self, student):
        self.students.append(student)

    def grading(self, projects, midterm, final):
        self.projects = projects
        self.midterm = midterm
        self.final = final

    def courseGrades(self, grades):
        for student in sorted(grades):
            grade = self.projects * grades[student]['projects'] + \
                    self.midterm * grades[student]['midterm'] + \
                    self.final * grades[student]['final']
            print('{} -> {}'.format(student, grade))

# Test

python2 = Course('Γλώσσα Προγραμματισμού ΙΙ', 'ΜΕΜ-107', 2020, ['Πλεξουσάκης', 'Λιλής'])
python2.grading(0.15, 0.25, 0.6)

students = ['Μαρία', 'Γιάννης', 'Ελένη', 'Κώστας']
for student in students:
    python2.enroll(student)

grades = {
        'Μαρία': {'projects':8, 'midterm':9, 'final':6 },
        'Γιάννης': {'projects':7, 'midterm':6, 'final':7 },
        'Ελένη': {'projects':5, 'midterm':8, 'final':8 },
        'Κώστας': {'projects':8, 'midterm':4, 'final':5 }
}

print(python2)
python2.courseGrades(grades)

# data[].attributes.headline
6ο φυλλάδιο ασκήσεων

# data[].attributes.subject
6ο φυλλάδιο ασκήσεων

# data[].attributes.body
Πρόβλημα 9

# data[].attributes.attachment_link.readable_text
# Φυλλάδιο 6 - Πρόβλημα 9

class Book:
    def __init__(self, name, author):
        self.name = name
        self.author = author

    def __repr__(self):
        return '"{}" by {}'.format(self.name, self.author)

class BookInfo:
    def __init__(self, book, available):
        self.book = book;
        self.available = available

class Library:
    def __init__(self, name):
        self.name = name
        self.books = {}

    def add_book(self, book):
        self.books[book.name] = BookInfo(book, True)

    def borrow_book(self, name):
        if name in self.books and self.books[name].available:
            self.books[name].available = False
            return self.books[name].book
        else:
            return None

    def return_book(self, book):
        self.books[book.name].available = True

    def available_books(self):
        result = []
        for bookInfo in self.books.values():
            if bookInfo.available:
                result.append(str(bookInfo.book))
        return result

    def show_books(self):
        result = Library.available_books(self)
        return ', '.join(result)

# Test

lib = Library('Πανεπιστημίου Κρήτης')
lib.add_book(Book('Ξεκινώντας με Python', 'Tony Gaddis'))
lib.add_book(Book('ΕΙΣΑΓΩΓΗ ΣΤΟΥΣ ΥΠΟΛΟΓΙΣΤΕΣ ΜΕ ΤΗ ΓΛΩΣΣΑ PYTHON', 'Νίκος Αβούρης'))
lib.add_book(Book('Υπολογισμοί και Προγραμματισμός με τη Python', 'J. Guttag'))

bk = lib.borrow_book('Ξεκινώντας με Python')
print(lib.show_books())

# data[].attributes.headline
6ο φυλλάδιο ασκήσεων

# data[].attributes.subject
6ο φυλλάδιο ασκήσεων

# data[].attributes.body
Πρόβλημα 10

# data[].attributes.attachment_link.readable_text
# Φυλλάδιο 6 - Πρόβλημα 10

# Ένα αντικείμενο κλάσης Matrix είναι μια λίστα από λίστες, για παράδειγμα
#
# Μ = [ [1, 2], [3, 4] ]
#
# αναπαριστά τον 2x2 πίνακα  [ 1  2 ]
#                            [ 3  4 ]

class Matrix:
    def __init__(self, data):
        self.M = len(data)          # Αριθμός γραμμών
        self.N = len(data[0])       # Αριθμός στηλών
        self.data = data

    def __repr__(self):
        return str(self.data)

    def __add__(self, other):
        if self.M != other.M or self.N != other.N:
            return None
        m = []
        for i in range(self.M):
            row = []
            for j in range(self.N):
                row.append(self.data[i][j] + other.data[i][j])
            m.append(row)
        return Matrix(m)

    def __neg__(self):
        m = [[-self.data[i][j] for j in range(self.N)] for i in range(self.M)]
        return Matrix(m)

    def __sub__(self, other):
        return self+(-other)

    def __mul__(self, other):
        if self.N != other.M:
            return None
        m = [[0 for row in range(other.N)] for col in range(self.M)]
        for i in range(self.M):
            for j in range(other.N):
                for k in range(self.N):
                    m[i][j] += self.data[i][k] * other.data[k][j]
        return Matrix(m)

    def is_identity(self):
        if self.M != self.N:
            return False
        for i in range(self.M):
            for j in range(self.N):
                if i == j and self.data[i][j] != 1 or i != j and self.data[i][j] != 0:
                    return False
        return True

    def is_diagonal(self):
        for i in range(self.M):
            for j in range(self.N):
                if i != j and self.data[i][j] != 0:
                    return False
        return True

# Test

X = Matrix([
    [1, 2, 3],
    [4, 5, 6]
])
Y = Matrix([
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
])

print("X =", X)
print("Y =", Y)

print("X * Y =", X*Y)

Z = Matrix([
    [1, 0, 0],
    [0, 2, 0]
])
W = Matrix([
    [1, 0, 0],
    [0, 1, 0],
    [0, 0, 1]
])

print('W =', W)
print("W identity?", W.is_identity())

print('X + Z =', X+Z)
print('Y - W =', Y-W)

# data[].attributes.attachment_link.readable_text
# Φυλλάδιο 6 - Πρόβλημα 2

class BankAccount:
    def __init__(self, name, surname, afm):
        self.name = name
        self.surname = surname
        self.afm = afm
        self.balance = 0
    def __repr__(self):
        return 'Account of {}, {}\n'.format(self.surname, self.name) + \
               'Balance: {}'.format(self.balance)
    def deposit(self, amount):
        self.balance += amount
    def withdrawal(self, amount):
        if self.balance >= amount:
            self.balance -= amount
    def getBalance(self):
        return self.balance

# Test

account = BankAccount('John', 'Smith', '123456789')

account.deposit(100)
account.deposit(200)
account.deposit(300)

account.withdrawal(400)

print(account)

# data[].attributes.headline
7ο φυλλάδιο ασκήσεων

# data[].attributes.subject
7ο φυλλάδιο ασκήσεων

# data[].attributes.body
Κληρονομικότητα

# data[].attributes.headline
7ο φυλλάδιο ασκήσεων

# data[].attributes.subject
7ο φυλλάδιο ασκήσεων

# data[].attributes.body
Πρόβλημα 1

# data[].attributes.attachment_link.readable_text
# Φυλλάδιο 7 - Πρόβλημα 1

import math

class Shape:
    def __init__(self,x,y):
        self.x=x
        self.y=y

    def getXYLoc(self):
        return (self.x,self.y)
        
class Circle(Shape):
    def __init__(self,x,y,r):
        super().__init__(self,x,y)
        self.radius=r

    def area(self):
        return math.pi*self.radius**2
    def circumference(self):
        return 2*math.pi*self.radius
                
class Square(Shape):
    def __init__(self,x,y,s):
        super().__init__(self,x,y)
        self.side=s 

    def area(self):
         return self.side**2
    def circumference(self):
        return 4*self.side
        
s1=Shape(1,1)
print('Shape', s1.getXYLoc())

s2=Circle(2,2,1)
print('Circle', s2.getXYLoc(), s2.radius, s2.area())
              
s3=Square(5,-5,3)
print('Square', s3.getXYLoc(), s3.side, s3.area())

shapes=[s2,s3]
for s in shapes:
    print(s.area(), s.circumference())

# data[].attributes.headline
7ο φυλλάδιο ασκήσεων

# data[].attributes.subject
7ο φυλλάδιο ασκήσεων

# data[].attributes.body
Πρόβλημα 2

# data[].attributes.attachment_link.readable_text
# Φυλλάδιο 7 - Πρόβλημα 2

class SchoolMember:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def tell(self):
        print('Name: {0}  Age: {1}'.format(self.name, self.age))

class Teacher(SchoolMember):
    def __init__(self, name, age, salary):
        SchoolMember.__init__(self, name, age)
        self.salary = salary

    def tell(self):
        SchoolMember.tell(self)
        print('Salary: {:d}'.format(self.salary))

class Student(SchoolMember):
    def __init__(self, name, age, marks):
        SchoolMember.__init__(self, name, age)
        self.marks = marks

    def tell(self):
        SchoolMember.tell(self)
        print('Marks: {:d}'.format(self.marks))

t = Teacher('Dr. Papadopoulou', 40, 20000)
s = Student('Ms. Papadaki', 20, 10)

members = [t, s]
for member in members:
    member.tell() # works for both Teachers and Students

# data[].attributes.headline
7ο φυλλάδιο ασκήσεων

# data[].attributes.subject
7ο φυλλάδιο ασκήσεων

# data[].attributes.body
Πρόβλημα 3

# data[].attributes.attachment_link.readable_text
# Φυλλάδιο 7 - Πρόβλημα 3

class Person:
    def __init__(self, name, surname):
        self.name = name
        self.surname = surname
    def __repr__(self):
        return '{} {}'.format(self.name, self.surname)

class Employee(Person):
    def __init__(self, name, surname, salary):
        Person.__init__(self, name, surname)
        self.salary = salary

    def __repr__(self):
        return '{}, salary: {:d}'.format(Person.__repr__(self), self.salary)

    def raiseSalary(self, percent):
        self.salary = int(self.salary * (1 + percent))

class Manager(Employee):
    def __init__(self, name, surname, salary, bonus):
        Employee.__init__(self, name, surname, salary)
        self.bonus = bonus

    def __repr__(self):
        return '{}, bonus: {:d}'.format(Employee.__repr__(self), self.bonus)

    def raiseSalary(self, percent):
        Employee.raiseSalary(self, percent)
        self.salary += self.bonus

employee = Employee('Giorgos', 'Papadopoulos', 1000)
manager = Manager('Maria', 'Papadaki', 2000, 100)
for p in [employee, manager]:
    print('Before raise:', p)
    p.raiseSalary(0.1)
    print('After raise 10%:', p)

# data[].attributes.headline
7ο φυλλάδιο ασκήσεων

# data[].attributes.subject
7ο φυλλάδιο ασκήσεων

# data[].attributes.body
Πρόβλημα 4

# data[].attributes.attachment_link.readable_text
# Φυλλάδιο 7 - Πρόβλημα 4

class Pet:
    def __init__(self,name,species):
        self.name=name
        self.species=species
    def __repr__(self):
        return '{} is a {}'.format(self.name, self.species)

    def getName(self):
        return self.name        
    def getSpecies(self):
        return self.species
    def speak(self):
        pass

class Dog(Pet):
    def __init__(self,name,chaces_cats):
        Pet.__init__(self,name,'Dog')
        self.chaces_cats=chaces_cats
    def chacesCats(self):
        return self.chaces_cats
    def speak(self):
        print('woof')

class Cat(Pet):
    def __init__(self,name,hates_dogs):
        Pet.__init__(self,name,'Cat')
        self.hates_dogs=hates_dogs    
    def hatesDogs(self):
        return self.hates_dogs 
    def speak(self):
        print('meow')

polly=Pet('Polly','Parrot')
print(polly)
print(polly.getName())
print(polly.getSpecies())
kanelos=Dog('Kanelos',True)
fluffy=Cat('Fluffy',False)
print(fluffy.hatesDogs())
for pet in [polly, kanelos, fluffy]:
    pet.speak()

# data[].attributes.headline
7ο φυλλάδιο ασκήσεων

# data[].attributes.subject
7ο φυλλάδιο ασκήσεων

# data[].attributes.body
Πρόβλημα 5

# data[].attributes.attachment_link.readable_text

# Φυλλάδιο 7 - Πρόβλημα 5

class Vehicle:
    def __init__(self, kind, color, speed, wheels, seats):
        self.kind = kind
        self.color = color
        self.speed = speed
        self.wheels = wheels
        self.seats = seats
    def __str__(self):
        return '%s:color:%s, speed:%d, wheels:%d, seats:%d' % \
               (self.kind, self.color, self.speed, self.wheels, self.seats)
    def accelerate(self):
        print('Accelerating')
    def brake(self):
        print('Braking')

class Motorcycle(Vehicle):
    def __init__(self, color, speed):
        Vehicle.__init__(self, 'motorcycle', color, speed, 2, 2)
    def front_brake(self):
        print('Motorcycle: Using the front brake')

class Car(Vehicle):
    def __init__(self, kind, color, speed, seats):
        Vehicle.__init__(self, kind, color, speed, 4, seats)
  
class Jeep(Car):
    def __init__(self, color, speed):
        Car.__init__(self, 'jeep', color, speed, 5)
    def four_wheel_drive(self):
        print('Jeep: 4WD enabled')
    
class RacingCar(Car):
    def __init__(self, color, speed):
        Car.__init__(self, 'racing_car', color, speed, 1)
    def turbo(self):
        print('RacingCar: Turbo enabled')

motorcycle = Motorcycle('black', 150)
jeep = Jeep('blue', 100)
ferrari = RacingCar('red', 300)
vehicles = [motorcycle, jeep, ferrari]
for vehicle in vehicles:
    print(vehicle)
motorcycle.front_brake()
jeep.four_wheel_drive()
ferrari.turbo()

# data[].attributes.headline
7ο φυλλάδιο ασκήσεων

# data[].attributes.subject
7ο φυλλάδιο ασκήσεων

# data[].attributes.body
Πρόβλημα 6