5. Δομές Δεδομένων
******************

Αυτό το κεφάλαιο περιγράφει ορισμένα πράγματα τα οποία έχετε μάθει ήδη
με περισσότερες λεπτομέρειες και προσθέτει επίσης μερικά νέα.


5.1. Περισσότερα για τις Λίστες
===============================

Ο τύπος δεδομένων λίστα έχει μερικές ακόμη μεθόδους. Εδώ είναι όλες οι
μέθοδοι των αντικειμένων λίστας:

list.append(x)

   Προσθέτει ένα στοιχείο στο τέλος της λίστας.  Παρόμοιο με
   "a[len(a):] = [x]".

list.extend(iterable)

   Επεκτείνει τη λίστα προσθέτοντας όλα τα στοιχεία από το iterable.
   Παρόμοιο με "a[len(a):] = iterable".

list.insert(i, x)

   Εισάγει ένα στοιχείο σε μια δεδομένη θέση.  Το πρώτο όρισμα είναι
   το index του στοιχείου πριν από το οποίο θα εισαχθεί, επομένως
   "a.insert(0, x)" εισάγεται στο μπροστινό μέρος της λίστας, και το
   "a.insert(len(a), x)" ισοδυναμεί με "a.append(x)".

list.remove(x)

   Καταργεί το πρώτο στοιχείο από τη λίστα του οποίου η τιμή είναι ίση
   με *x*.  Κάνει raise ένα "ValueError" εάν δεν υπάρχει τέτοιο
   στοιχείο.

list.pop([i])

   Καταργεί το στοιχείο στη δεδομένη θέση στη λίστα, και το
   επιστρέφει.  Εάν δεν έχει καθοριστεί ευρετήριο, το "a.pop()"
   αφαιρεί και επιστρέφει το τελευταίο στοιχείο στη λίστα. Κάνει raise
   ένα "IndexError" εάν η λίστα είναι κενή ή το ευρετήριο βρίσκεται
   εκτός του εύρους της λίστας.

list.clear()

   Αφαιρεί όλα τα στοιχεία από τη λίστα.  Παρόμοιο με "del a[:]".

list.index(x[, start[, end]])

   Επιστρέφει τον μηδενικό δείκτη της πρώτης εμφάνισης του *x* στη
   λίστα. Κάνει raise μια "ValueError" εάν δεν υπάρχει τέτοιο
   στοιχείο.

   Τα προαιρετικά ορίσματα *start* και *end* ερμηνεύονται όπως στη
   σημειογραφία slice και χρησιμοποιούνται για τον περιορισμό της
   αναζήτησης σε μια συγκεκριμένη υποακολουθία της λίστας.  Ο
   επιστρεφόμενος δείκτης υπολογίζεται σε σχέση με την αρχή της
   πλήρους ακολουθίας αντί για το όρισμα *start*.

list.count(x)

   Επιστρέφει τον αριθμό των φορών που εμφανίζεται το *x* στη λίστα.

list.sort(*, key=None, reverse=False)

   Ταξινομεί τα στοιχεία της λίστας στη θέση τους (τα ορίσματα μπορούν
   να χρησιμοποιηθούν για προσαρμογή ταξινόμησης, βλ. "sorted()" για
   την εξήγησή τους).

list.reverse()

   Αντιστρέφει τα στοιχεία της λίστας στη θέση τους.

list.copy()

   Επιστρέφει ένα shallow (ρηχό) αντίγραφο της λίστας. Παρόμοιο με
   "a[:]".

Ένα παράδειγμα που χρησιμοποιεί τις περισσότερες από τις μεθόδους της
λίστας:

   >>> fruits = ['orange', 'apple', 'pear', 'banana', 'kiwi', 'apple', 'banana']
   >>> fruits.count('apple')
   2
   >>> fruits.count('tangerine')
   0
   >>> fruits.index('banana')
   3
   >>> fruits.index('banana', 4)  # Find next banana starting at position 4
   6
   >>> fruits.reverse()
   >>> fruits
   ['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange']
   >>> fruits.append('grape')
   >>> fruits
   ['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange', 'grape']
   >>> fruits.sort()
   >>> fruits
   ['apple', 'apple', 'banana', 'banana', 'grape', 'kiwi', 'orange', 'pear']
   >>> fruits.pop()
   'pear'

Μπορεί να έχετε παρατηρήσει ότι μέθοδοι όπως "insert", "remove" or
"sort" που τροποποιούν μόνο τη λίστα δεν έχουν εκτυπωμένη τιμή
επιστροφής -- επιστρέφουν το προεπιλεγμένο (default) "None". [1]  Αυτή
είναι μια αρχή σχεδιασμού για όλες τις μεταβλητές δομές δεδομένων στην
Python.

Ένα άλλο πράγμα που μπορεί να παρατηρήσετε είναι ότι δεν μπορούν να
ταξινομηθούν ή να συγκριθούν όλα τα δεδομένα.  Για παράδειγμα, "[None,
'hello', 10]" δεν ταξινομούνται γιατί ακέραιοι αριθμοί δεν μπορούν να
συγκριθούν με συμβολοσειρές (strings) και το "None" δεν μπορεί να
συγκριθεί με άλλους τύπους.  Επίσης, υπάρχουν ορισμένοι τύποι που δεν
έχουν καθορισμένη σχέση διάταξης.  Για παράδειγμα, το "3+4j < 5+7j"
δεν είναι έγκυρη σύγκριση.


5.1.1. Χρήστη Λιστών ως Στοίβες (Stacks)
----------------------------------------

Οι μέθοδοι λίστας καθιστούν πιο εύκολη τη χρήση μιας λίστα ως στοίβας
(stack), όπου το τελευταίο στοιχείο που προστέθηκε είναι το πρώτο
στοιχείο που ανακτήθηκε ("last-in, first-out").  Για να προσθέσετε ένα
στοιχείο στην κορυφή της στοίβας, χρησιμοποιήστε την "append()".  Για
να ανακτήσετε ένα στοιχείο από την κορυφή της στοίβας, χρησιμοποιήστε
την "pop()" χωρίς κάποιο σαφές index.  Για παράδειγμα:

   >>> stack = [3, 4, 5]
   >>> stack.append(6)
   >>> stack.append(7)
   >>> stack
   [3, 4, 5, 6, 7]
   >>> stack.pop()
   7
   >>> stack
   [3, 4, 5, 6]
   >>> stack.pop()
   6
   >>> stack.pop()
   5
   >>> stack
   [3, 4]


5.1.2. Χρήση λιστών ως Ουρές (Queues)
-------------------------------------

Είναι επίσης δυνατό να χρησιμοποιηθεί μια λίστα ως ουρά (queue), όπου
το πρώτο στοιχείο που προστίθεται είναι το πρώτο στοιχείο που
ανακτάται ("first-in, first-out") ∙ ωστόσο, οι λίστες δεν είναι
αποτελεσματικές για αυτόν τον σκοπό.  Ενώ το να προσθέσεις και να
αφαιρέσεις (στοιχεία) στο τέλος της λίστας είναι γρήγορο, κάνοντας
αυτές τις προσθέσεις και τις αφαιρέσεις (στοιχείων) στην αρχή της
λίστα είναι αργό (επειδή όλα τα στοιχεία πρέπει να μετατοπιστούν κατά
ένα).

Για να εφαρμόσετε μια ουρά (queue), χρησιμοποιήστε την
"collections.deque" η οποία σχεδιάστηκε για να έχει γρήγορες
προσθέσεις και αφαιρέσεις και από τα δύο άκρα.  Για παράδειγμα:΅:

   >>> from collections import deque
   >>> queue = deque(["Eric", "John", "Michael"])
   >>> queue.append("Terry")           # Terry arrives
   >>> queue.append("Graham")          # Graham arrives
   >>> queue.popleft()                 # The first to arrive now leaves
   'Eric'
   >>> queue.popleft()                 # The second to arrive now leaves
   'John'
   >>> queue                           # Remaining queue in order of arrival
   deque(['Michael', 'Terry', 'Graham'])


5.1.3. Comprehensions Λίστας
----------------------------

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

Για παράδειγμα, ας υποθέσουμε ότι θέλουμε να δημιουργήσουμε μια λίστα
τετραγώνων όπως:

   >>> squares = []
   >>> for x in range(10):
   ...     squares.append(x**2)
   ...
   >>> squares
   [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Λάβετε υπόψη ότι αυτό δημιουργεί (ή αντικαθιστά) μια μεταβλητή με το
όνομα "x" που εξακολουθεί να υπάρχει μετά την ολοκλήρωση της loop.
Μπορούμε να υπολογίσουμε τη λίστα των τετραγώνων χωρίς παρενέργειες
χρησιμοποιώντας:

   squares = list(map(lambda x: x**2, range(10)))

ή, ισοδύναμα:

   squares = [x**2 for x in range(10)]

που είναι πιο συνοπτικό και ευανάγνωστο.

Ένα comprehension λίστας αποτελείται από αγκύλες που περιέχουν μια
έκφραση ακολουθούμενη από μια πρόταση "for", στη συνέχεια μηδέν ή
περισσότερες προτάσεις "for" ή "if".  Το αποτέλεσμα θα είναι μια νέα
λίστα που προκύπτει από την αξιολόγηση της έκφρασης στο πλαίσιο των
προτάσεων "for" και "if" που την ακολουθούν. Για παράδειγμα, αυτή η
λίστα συνδυάζει τα στοιχεία δύο λιστών εάν δεν είναι ίσες:

   >>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
   [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

και ισοδυναμεί με:

   >>> combs = []
   >>> for x in [1,2,3]:
   ...     for y in [3,1,4]:
   ...         if x != y:
   ...             combs.append((x, y))
   ...
   >>> combs
   [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

Σημειώστε πώς η σειρά των δηλώσεων "for" και "if" είναι ίδια και στα
δύο αποσπάσματα.

Εάν η έκφραση είναι πλειάδα (π.χ. το "(x, y)" στο προηγούμενο
παράδειγμα), πρέπει να μπει σε παρένθεση.

   >>> vec = [-4, -2, 0, 2, 4]
   >>> # create a new list with the values doubled
   >>> [x*2 for x in vec]
   [-8, -4, 0, 4, 8]
   >>> # filter the list to exclude negative numbers
   >>> [x for x in vec if x >= 0]
   [0, 2, 4]
   >>> # apply a function to all the elements
   >>> [abs(x) for x in vec]
   [4, 2, 0, 2, 4]
   >>> # call a method on each element
   >>> freshfruit = ['  banana', '  loganberry ', 'passion fruit  ']
   >>> [weapon.strip() for weapon in freshfruit]
   ['banana', 'loganberry', 'passion fruit']
   >>> # create a list of 2-tuples like (number, square)
   >>> [(x, x**2) for x in range(6)]
   [(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]
   >>> # the tuple must be parenthesized, otherwise an error is raised
   >>> [x, x**2 for x in range(6)]
     File "<stdin>", line 1
       [x, x**2 for x in range(6)]
        ^^^^^^^
   SyntaxError: did you forget parentheses around the comprehension target?
   >>> # flatten a list using a listcomp with two 'for'
   >>> vec = [[1,2,3], [4,5,6], [7,8,9]]
   >>> [num for elem in vec for num in elem]
   [1, 2, 3, 4, 5, 6, 7, 8, 9]

Τα comprehensions λίστας μπορεί να περιέχουν σύνθετες εκφράσεις και
ένθετες συναρτήσεις:

   >>> from math import pi
   >>> [str(round(pi, i)) for i in range(1, 6)]
   ['3.1', '3.14', '3.142', '3.1416', '3.14159']


5.1.4. Comprehensions Ένθετων Λιστών
------------------------------------

Η αρχική έκφραση σε ένα comprehension λίστας μπορεί να είναι
οποιαδήποτε αυθαίρετη έκφραση, συμπεριλαμβανομένης ενός άλλου
comprehension λίστας.

Σκεφτείτε το ακόλουθο παράδειγμα μιας μήτρας 3x4 που υλοποιήθηκε ως
μια λίστα 3 λιστών μήκους 4:

   >>> matrix = [
   ...     [1, 2, 3, 4],
   ...     [5, 6, 7, 8],
   ...     [9, 10, 11, 12],
   ... ]

Το ακόλουθο comprehension λίστας θα μεταφέρει γραμμές και στήλες:

   >>> [[row[i] for row in matrix] for i in range(4)]
   [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

Όπως είδαμε στην προηγούμενη ενότητα, το comprehension της εσωτερικής
λίστας αξιολογείται στο πλαίσιο του "for" που την ακολουθεί, επομένως
αυτό το παράδειγμα είναι ισοδύναμο με:

   >>> transposed = []
   >>> for i in range(4):
   ...     transposed.append([row[i] for row in matrix])
   ...
   >>> transposed
   [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

το οποίο, με τη σειρά του, είναι το ίδιο με:

   >>> transposed = []
   >>> for i in range(4):
   ...     # the following 3 lines implement the nested listcomp
   ...     transposed_row = []
   ...     for row in matrix:
   ...         transposed_row.append(row[i])
   ...     transposed.append(transposed_row)
   ...
   >>> transposed
   [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

Στον πραγματικό κόσμο, θα πρέπει να προτιμάτε τις ενσωματωμένες
(built-in) συναρτήσεις από τις σύνθετες εντολές ροής. Η συνάρτηση
"zip()" θα έκανε εξαιρετική δουλειά για αυτήν την περίπτωση χρήσης:

   >>> list(zip(*matrix))
   [(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]

Δείτε Unpacking Λίστες Ορισμάτων για λεπτομέρειες σχετικά με τον
αστερίσκο σε αυτήν τη γραμμή.


5.2. Η δήλωση "del"
===================

Υπάρχει ένας τρόπος να αφαιρέσετε ένα στοιχείο από μια λίστα με το
ευρετήριο του αντί για την τιμή του: η δήλωση "del".  Αυτό διαφέρει
από τη μέθοδο "pop()" που επιστρέφει μια τιμή.  Η δήλωση "del" μπορεί
επίσης να χρησιμοποιηθεί για την αφαίρεση τμημάτων από μια λίστα ή για
να καθαρίσει ολόκληρη τη λίστα (που κάναμε νωρίτερα με την εκχώρηση
μιας κενής λίστας στο τμήμα).  Για παράδειγμα:

   >>> a = [-1, 1, 66.25, 333, 333, 1234.5]
   >>> del a[0]
   >>> a
   [1, 66.25, 333, 333, 1234.5]
   >>> del a[2:4]
   >>> a
   [1, 66.25, 1234.5]
   >>> del a[:]
   >>> a
   []

Το "del" μπορεί επίσης να χρησιμοποιηθεί για τη διαγραφή ολόκληρων
μεταβλητών:

   >>> del a

Η αναφορά στο όνομα "a" στο εξής είναι ένα σφάλμα (τουλάχιστον μέχρι
να του εκχωρηθεί άλλη τιμή). Θα βρούμε άλλες χρήσεις για το "del"
αργότερα.


5.3. Πλειάδες (Tuples) και Ακολουθίες
=====================================

Είδαμε ότι οι λίστες και οι συμβολοσειρές (strings) έχουνε πολλές
κοινές ιδιότητες, όπως λειτουργίες indexing και slicing.  Είναι δύο
παραδείγματα τύπων δεδομένων *sequence* (δείτε Τύποι Ακολουθίας
(Sequence) --- list, tuple, range).  Δεδομένου ότι η Python είναι μια
εξελισσόμενη γλώσσα, άλλοι τύποι δεδομένων ακολουθίας μπορούν να
προστεθούν.  Υπάρχει επίσης ένας άλλος τυπικός τύπος δεδομένων
ακολουθίας type: the *πλειάδα* (*tuple*).

Μια πλειάδα (tuple) αποτελείται από έναν αριθμό τιμών που χωρίζονται
με κόμματα, για παράδειγμα:

   >>> t = 12345, 54321, 'hello!'
   >>> t[0]
   12345
   >>> t
   (12345, 54321, 'hello!')
   >>> # Tuples may be nested:
   >>> u = t, (1, 2, 3, 4, 5)
   >>> u
   ((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))
   >>> # Tuples are immutable:
   >>> t[0] = 88888
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: 'tuple' object does not support item assignment
   >>> # but they can contain mutable objects:
   >>> v = ([1, 2, 3], [3, 2, 1])
   >>> v
   ([1, 2, 3], [3, 2, 1])

Όπως βλέπετε, οι πλειάδες (tuples) στην έξοδο περικλείονται πάντα σε
παρενθέσεις, έτσι ώστε οι ένθετε πλειάδες (tuples) να ερμηνεύονται
σωστά∙ μπορούν να εισαχθούν με ή χωρίς περιβάλλουσες παρενθέσεις, αν
και συχνά οι παρενθέσεις είναι απαραίτητες ούτως ή άλλως (αν η πλειάδα
είναι μέρος μιας μεγαλύτερης έκφρασης).  Δεν είναι δυνατή η
αντιστοίχιση σε μεμονωμένα στοιχεία μιας πλειάδας, ωστόσο είναι δυνατό
να δημιουργηθούν πλειάδες (tuples) που περιέχουν μεταβλητά
αντικείμενα, όπως λίστες.

Αν και οι πλειάδες (tuples) μπορεί να φαίνονται παρόμοιες με λίστες,
χρησιμοποιούνται συχνά σε διαφορετικές καταστάσεις και για
διαφορετικούς σκοπούς. Οι πλειάδες (tuples) είναι *immutable*, και
συνήθως περιέχουν μια ετερογενή ακολουθία στοιχείων στα οποία η
πρόσβαση γίνεται μέσω unpacking (δείτε παρακάτω σε αυτήν την ενότητα)
ή το indexing (ή ακόμα και κατά χαρακτηριστικό στην περίπτωση
"namedtuples"). Οι λίστες είναι *mutable*, και τα στοιχεία τους είναι
συνήθως ομοιογενή και προσπελάζονται με επανάληψη στη λίστα.

Ένα ειδικό πρόβλημα είναι η κατασκευή πλειάδων (tuples) που περιέχουν
0 ή 1 στοιχεία: η σύνταξη έχει κάποιες επιπλέον ιδιορρυθμίες για να
τις προσαρμόσει.  Οι κενές πλειάδες κατασκευάζονται από ένα κενό
ζευγάρι παρενθέσεων, μια πλειάδα (tuple) με ένα στοιχείο δημιουργείται
ακολουθώντας μια τιμή με κόμμα (δεν αρκεί να περικλείεται μια μόνο
τιμή σε παρενθέσεις). Άσχημο, αλλά αποτελεσματικό.  Για παράδειγμα:

   >>> empty = ()
   >>> singleton = 'hello',    # <-- note trailing comma
   >>> len(empty)
   0
   >>> len(singleton)
   1
   >>> singleton
   ('hello',)

Η δήλωση "t = 12345, 54321, 'hello!'" είναι ένα παράδειγμα *tuple
packing*: οι τιμές "12345", "54321" και "'hello!'" είναι συσκευασμένες
μαζί σε μια πλειάδα (tuple). Η αντίστροφη λειτουργία είναι επίσης
εφικτή:

   >>> x, y, z = t

Αυτό ονομάζεται, αρκετά σωστά, *sequence unpacking* και λειτουργεί για
οποιαδήποτε ακολουθία στη δεξιά πλευρά.  Το sequence unpacking απαιτεί
να υπάρχουν τόσες μεταβλητές στην αριστερή πλευρά του συμβόλου
ισότητας όσα στοιχεία υπάρχουν στην ακολουθία.  Σημείωση ότι η
πολλαπλή ανάθεση είναι στην πραγματικότητα απλώς ένας συνδυασμός tuple
packing και sequence unpacking.


5.4. Σύνολα (Sets)
==================

Η Python περιλαμβάνει επίσης έναν τύπο δεδομένων για sets.  Ένα set
είναι μια μη ταξινομημένη συλλογή χωρίς διπλότυπα στοιχεία.  Οι
βασικές χρήσεις περιλαμβάνουν τη δοκιμή ιδιότητας μέλους και την
εξάλειψη διπλότυπων εγγραφών.  Τα αντικείμενα συνόλου υποστηρίζουν
επίσης μαθηματικές πράξεις όπως ένωση, τομή, διαφορά και συμμετρική
διαφορά.

Τα άγκιστρα ή η συνάρτηση "set()" μπορούν να χρησιμοποιηθούν για τη
δημιουργία συνόλων.  Σημείωση: για να δημιουργήσετε ένα κενό σύνολο
πρέπει να χρησιμοποιήσετε το "set()", όχι το "{}" ∙ το τελευταίο
δημιουργεί ένα κενό λεξικό, μια δομή δεδομένων που θα συζητήσουμε στην
επόμενη ενότητα.

Ακολουθεί μια σύντομη επίδειξη:

   >>> basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
   >>> print(basket)                      # show that duplicates have been removed
   {'orange', 'banana', 'pear', 'apple'}
   >>> 'orange' in basket                 # fast membership testing
   True
   >>> 'crabgrass' in basket
   False

   >>> # Demonstrate set operations on unique letters from two words
   >>>
   >>> a = set('abracadabra')
   >>> b = set('alacazam')
   >>> a                                  # unique letters in a
   {'a', 'r', 'b', 'c', 'd'}
   >>> a - b                              # letters in a but not in b
   {'r', 'd', 'b'}
   >>> a | b                              # letters in a or b or both
   {'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}
   >>> a & b                              # letters in both a and b
   {'a', 'c'}
   >>> a ^ b                              # letters in a or b but not both
   {'r', 'd', 'b', 'm', 'z', 'l'}

Ομοίως με list comprehensions, υποστηρίζονται επίσης τα comprehensions
των συνόλων:

   >>> a = {x for x in 'abracadabra' if x not in 'abc'}
   >>> a
   {'r', 'd'}


5.5. Λεξικά (Dictionaries)
==========================

Ένα άλλος χρήσιμος τύπος δεδομένων που είναι ενσωματωμένος στην Python
είναι το *dictionary* (βλ. Τύποι αντιστοίχισης --- dict). Τα λεξικά
βρίσκονται μερικές φορές σε άλλες γλώσσες ως "συσχετιστικές μνήμες" ή
"συσχετιστικοί πίνακες". Σε αντίθεση με τις ακολουθίες, οι οποίες
είναι με indexes με ένα εύρος αριθμών, τα λεξικά γίνονται index με
*keys*, που μπορεί να είναι οποιοσδήποτε αμετάβλητος τύπος∙ οι
συμβολοσειρές και οι αριθμοί μπορούν πάντα να είναι κλειδιά.  Οι
πλειάδες (tuples) μπορούν να χρησιμοποιηθούν ως κλειδιά εάν περιέχουν
μόνο συμβολοσειρές, αριθμούς ή πλειάδες (tuples)∙ εάν μια πλειάδα
περιέχει οποιοδήποτε μεταβλητό αντικείμενο είτε άμεσα είτε έμμεσα, δεν
μπορεί να χρησιμοποιηθεί ως κλειδί. Δεν μπορείτε να χρησιμοποιήσετε
λίστες ως κλειδιά, καθώς οι λίστες μπορούν να τροποποιηθούν επιτόπου
χρησιμοποιώντας αναθέσεις index, αναθέσεις slice, ή μεθόδους όπως
"append()" και "extend()".

Είναι καλύτερο να σκεφτείτε ένα λεξικό ως ένα σύνολο ζευγών *key:
value*, με την προϋπόθεση ότι τα κλειδιά είναι μοναδικά (σε ένα
λεξικό). Ένα ζεύγος αγκύλων δημιουργεί ένα κενό λεξικό: "{}". Η
τοποθέτηση μιας λίστας ζευγών key:value στο λεξικό, αυτός είναι επίσης
ο τρόπος με τον οποίο γράφονται τα λεξικά στην έξοδο.

Οι κύριες λειτουργίες σε ένα λεξικό είναι η αποθήκευση μιας τιμής με
κάποιο κλειδί και η εξαγωγή της τιμής που δίνεται στο κλειδί.  Είναι
επίσης δυνατή η διαγραφή ενός ζεύγους key:value με "del". Εάν
αποθηκεύετε χρησιμοποιώντας ένα κλειδί που βρίσκεται ήδη σε χρήση, η
παλιά τιμή που σχετίζεται με αυτό το κλειδί έχει ξεχαστεί.

Η εξαγωγή μιας τιμής για ένα μη υπάρχον κλειδί με δεικτοδότηση
("d[key]") προκαλεί ένα "KeyError". Για να αποφύγετε αυτό το σφάλμα
όταν προσπαθείτε να αποκτήσετε πρόσβαση σε ένα πιθανώς μη υπάρχον
κλειδί, χρησιμοποιήστε αντίθετα τη μέθοδο "get()", η οποία επιστρέφει
"None" (ή μια καθορισμένη προεπιλεγμένη τιμή) εάν το κλειδί δεν
βρίσκεται στο λεξικό.

Η εκτέλεση του "list(d)" σε ένα λεξικό επιστρέφει μια λίστα με όλα τα
κλειδιά που χρησιμοποιούνται στο λεξικό, με σειρά εισαγωγής (αν θέλετε
να ταξινομηθεί, απλώς χρησιμοποιήστε το "sorted(d)"). Για να ελέγξετε
εάν υπάρχει ένα μεμονωμένο κλειδί στο λεξικό, χρησιμοποιήστε τη
λέξη-κλειδί "in".

Ακολουθεί ένα μικρό παράδειγμα χρησιμοποιώντας ένα λεξικό:

   >>> tel = {'jack': 4098, 'sape': 4139}
   >>> tel['guido'] = 4127
   >>> tel
   {'jack': 4098, 'sape': 4139, 'guido': 4127}
   >>> tel['jack']
   4098
   >>> tel['irv']
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   KeyError: 'irv'
   >>> print(tel.get('irv'))
   None
   >>> del tel['sape']
   >>> tel['irv'] = 4127
   >>> tel
   {'jack': 4098, 'guido': 4127, 'irv': 4127}
   >>> list(tel)
   ['jack', 'guido', 'irv']
   >>> sorted(tel)
   ['guido', 'irv', 'jack']
   >>> 'guido' in tel
   True
   >>> 'jack' not in tel
   False

Ο constructor "dict()" δημιουργεί λεξικά απευθείας από ακολουθίες
ζευγών key-value:

   >>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
   {'sape': 4139, 'guido': 4127, 'jack': 4098}

Επιπλέον, τα comprehensions λεξικών μπορούν να χρησιμοποιηθούν για τη
δημιουργία λεξικών από αυθαίρετες εκφράσεις κλειδιού και τιμών:

   >>> {x: x**2 for x in (2, 4, 6)}
   {2: 4, 4: 16, 6: 36}

Όταν τα κλειδιά είναι απλές συμβολοσειρές, μερικές φορές είναι πιο
εύκολο να ορίσετε ζεύγη χρησιμοποιώντας ορίσματα λέξεων-κλειδιών:

   >>> dict(sape=4139, guido=4127, jack=4098)
   {'sape': 4139, 'guido': 4127, 'jack': 4098}


5.6. Τεχνικές Looping
=====================

Κατά το looping μέσω λεξικών, το κλειδί και η αντίστοιχη τιμή μπορούν
να ανακτηθούν ταυτόχρονα χρησιμοποιώντας τη μέθοδο "items()".

   >>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}
   >>> for k, v in knights.items():
   ...     print(k, v)
   ...
   gallahad the pure
   robin the brave

Κατά το looping μέσω ακολουθίας, ο δείκτης θέσης και η αντίστοιχη τιμή
μπορούν να ανακτηθούν ταυτόχρονα χρησιμοποιώντας τη συνάρτηση
"enumerate()".

   >>> for i, v in enumerate(['tic', 'tac', 'toe']):
   ...     print(i, v)
   ...
   0 tic
   1 tac
   2 toe

Για να κάνετε loop σε δύο ή περισσότερες ακολουθίες ταυτόχρονα, οι
καταχωρίσεις μπορούν να αντιστοιχιστούν με τη συνάρτηση "zip()".

   >>> questions = ['name', 'quest', 'favorite color']
   >>> answers = ['lancelot', 'the holy grail', 'blue']
   >>> for q, a in zip(questions, answers):
   ...     print('What is your {0}?  It is {1}.'.format(q, a))
   ...
   What is your name?  It is lancelot.
   What is your quest?  It is the holy grail.
   What is your favorite color?  It is blue.

Για να κάνετε loop σε μια ακολουθία αντίστροφα, καθορίστε πρώτα την
ακολουθία προς τα εμπρός και μετά καλέστε τη συνάρτηση "reversed()".

   >>> for i in reversed(range(1, 10, 2)):
   ...     print(i)
   ...
   9
   7
   5
   3
   1

Για να κάνετε loop σε μια ακολουθία με ταξινομημένη σειρά,
χρησιμοποιήστε τη συνάρτηση "sorted()", η οποία επιστρέφει μια νέα
ταξινομημένη λίστα αφήνοντας την πηγή αναλλοίωτη.

   >>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
   >>> for i in sorted(basket):
   ...     print(i)
   ...
   apple
   apple
   banana
   orange
   orange
   pear

Η χρήση του "set()" σε μια ακολουθία εξαλείφει τα διπλά στοιχεία. Η
χρήση του "sorted()" σε συνδυασμό με το "set()" σε μια ακολουθία είναι
ένας ιδιωματικός τρόπος για να κάνετε loop πάνω από μοναδικά στοιχεία
της ακολουθίας σε ταξινομημένη σειρά.

   >>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
   >>> for f in sorted(set(basket)):
   ...     print(f)
   ...
   apple
   banana
   orange
   pear

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

   >>> import math
   >>> raw_data = [56.2, float('NaN'), 51.7, 55.3, 52.5, float('NaN'), 47.8]
   >>> filtered_data = []
   >>> for value in raw_data:
   ...     if not math.isnan(value):
   ...         filtered_data.append(value)
   ...
   >>> filtered_data
   [56.2, 51.7, 55.3, 52.5, 47.8]


5.7. Περισσότερα για τις συνθήκες
=================================

Οι συνθήκες που χρησιμοποιούνται στις δηλώσεις "while" και "if"
μπορούν να περιέχουν οποιουσδήποτε τελεστές, όχι μόνο συγκρίσεις.

Οι τελεστές σύγκρισης "in" και "not in" είναι δοκιμές ιδιότητας μέλους
που καθορίζουν εάν μια τιμή βρίσκεται (ή όχι σε) ένα container.  Οι
τελεστές "is" και "is not" συγκρίνουν εάν δύο αντικείμενα είναι
πραγματικά το ίδιο αντικείμενο.  Όλοι οι τελεστές σύγκρισης έχουν την
ίδια προτεραιότητα, η οποία είναι χαμηλότερη από αυτή όλων των
αριθμητικών τελεστών.

Οι συγκρίσεις μπορούν να είναι αλυσιδωτές.  Για παράδειγμα, "a < b ==
c" ελέγχει εάν το "a" είναι μικρότερο από "b" και επιπλέον το "b"
ισούται με "c".

Οι συγκρίσεις μπορούν να συνδυαστούν χρησιμοποιώντας τους λογικούς
τελεστές "and" και "or", και το αποτέλεσμα μιας σύγκρισης (ή
οποιασδήποτε άλλης λογικής έκφρασης) μπορεί να ακυρωθεί με "not".
Αυτοί έχουν χαμηλότερες προτεραιότητες μεταξύ των τελεστών σύγκρισης,
το "not" έχει την υψηλότερη προτεραιότητα και το "or" τη χαμηλότερη,
έτσι ώστε το "A and not B or C" ισοδυναμεί με "(A and (not B)) or C".
Όπως πάντα, οι παρενθέσεις μπορούν να χρησιμοποιηθούν για να εκφράσουν
την επιθυμητή σύνθεση.

Οι λογικοί τελεστές "and" και "or" είναι οι λεγόμενοι τελεστές *short-
circuit*: τα ορίσματα τους αξιολογούνται από αριστερά προς τα δεξιά
και η αξιολόγηση σταματά μόλις καθοριστεί το αποτέλεσμα.  Για
παράδειγμα, εάν το "A" and "C" είναι αληθές, αλλά το "B" είναι ψευδές,
το "A and B and C" δεν αξιολογεί την έκφραση "C".  Όταν
χρησιμοποιείται ως γενική τιμή και όχι ως λογική, η τιμή επιστροφής
ενός short-circuit τελεστή είναι το τελευταίο αξιολογημένο όρισμα.

Είναι δυνατό να αντιστοιχίσετε το αποτέλεσμα μιας σύγκρισης ή άλλη
δυαδική έκφρασης σε μια μεταβλητή.  Για παράδειγμα,

   >>> string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
   >>> non_null = string1 or string2 or string3
   >>> non_null
   'Trondheim'

Σημειώστε ότι στην Python, σε αντίθεση με την C, η ανάθεση εντός των
εκφράσεων πρέπει να γίνεται ρητά με τον τελεστή walrus operator ":=".
Αυτό αποφεύγει μια κοινή κατηγορία προβλημάτων που συναντώνται στα
προγράμματα C: πληκτρολογώντας "=" σε μια έκφραση όταν προοριζόταν το
"==".


5.8. Σύγκριση ακολουθιών και άλλων τύπων
========================================

Τα αντικείμενα ακολουθίας μπορούν συνήθως να συγκριθούν με άλλα
αντικείμενα με τον ίδιο τύπο ακολουθίας. Η σύγκριση χρησιμοποιεί
*lexicographical* σειρά: πρώτα συγκρίνονται τα δύο πρώτα στοιχεία και
αν διαφέρουν αυτό καθορίζει το αποτέλεσμα της σύγκρισης∙ εάν είναι
ίσα, τα επόμενα δύο στοιχεία συγκρίνονται και ούτω καθεξής, έως ότου
εξαντληθεί η μία από τις δύο ακολουθίες. Εάν δύο στοιχεία προς
σύγκριση είναι τα ίδια ακολουθίες του ίδιου τύπου, η λεξικογραφική
σύγκριση πραγματοποιείται αναδρομικά.  Εάν όλα τα στοιχεία δύο
ακολουθιών συγκρίνονται ίσα, οι ακολουθίες θεωρούνται ίσες. Εάν η μια
ακολουθία είναι αρχική υποακολουθία της άλλης, η μικρότερη ακολουθία
είναι η μικρότερη (ελάχιστη).  Η λεξικογραφική ταξινόμηση
συμβολοσειρών χρησιμοποιεί τον αριθμό κωδικού σημείου Unicode για να
ταξινομήσει μεμονωμένους χαρακτήρες. Μερικά παραδείγματα συγκρίσεων
μεταξύ ακολουθιών του ίδιου τύπου:

   (1, 2, 3)              < (1, 2, 4)
   [1, 2, 3]              < [1, 2, 4]
   'ABC' < 'C' < 'Pascal' < 'Python'
   (1, 2, 3, 4)           < (1, 2, 4)
   (1, 2)                 < (1, 2, -1)
   (1, 2, 3)             == (1.0, 2.0, 3.0)
   (1, 2, ('aa', 'ab'))   < (1, 2, ('abc', 'a'), 4)

Λάβετε υπόψη ότι η σύγκριση αντικειμένων διαφορετικών τύπων με "<" ή
">" είναι νόμιμη υπό τον όρο ότι τα αντικείμενα διαθέτουν κατάλληλες
μεθόδους σύγκρισης.  Για παράδειγμα, οι μικτές αριθμητικοί τύποι
συγκρίνονται σύμφωνα με την αριθμητική τους τιμή, οπότε το 0 ισούται
με 0.0, κλπ.  Διαφορετικά, αντί να παρέχει μια αυθαίρετη παραγγελία, ο
διερμηνέας θα κάνει raise μια εξαίρεση "TypeError".

-[ Υποσημειώσεις ]-

[1] Άλλες γλώσσες ενδέχεται να επιστρέψουν το μεταλλαγμένο
    αντικείμενο, το οποίο επιτρέπει την αλυσιδωτή εκτέλεση μεθόδων,
    όπως "d->insert("a")->remove("b")->sort();".
