# Περισσότερα για τους Καταλόγους

Μέχρι τώρα έχουμε εξετάσει τους καταλόγους και ξέρουμε πως μπορούμε να τους χρησιμοποιήσουμε. Με αυτήν την υποδομή λοιπόν θα μπορέσουμε να επεκταθούμε με περισσότερη λεπτομέρεια στους καταλόγους. Κατ' αρχήν θα δούμε με ποιούς τρόπους θα έχουμε πρόσβαση στα στοιχεία των καταλόγων και μετά το πως μπορούμε να τα αντιγράψουμε.

Μερικά παραδείγματα που χρησιμοποιούν την εύρεση ενός συγκεκριμένου στοιχείου ενός καταλόγου:

    >>> list = ['zero','one','two','three','four','five']
    >>> list[0]
    'zero'
    >>> list[4]
    'four'
    >>> list[5]
    'five'

Τα παραδείγματα αυτά πρέπει να σας είναι γνώριμα. Αν θέλετε να δείτε το πρώτο στοιχείο του καταλόγου ψάχνετε για το στοιχείο με index 0. Το επόμενο στοιχείο έχει index 1 και ούτω καθ' εξής μέχρι το τέλος του καταλόγους. Τι γίνεται όμως αν θέλουμε το τελευταίο στοιχείο του; Ένας τρόπος είναι να χρησιμοποιήσουμε την συνάρτηση `len` ως εξής:

`list[len(list)-1]`.

Ο τρόπος αυτός είναι αποτελεσματικός καθώς η `len` επιστρέφει το τελευταίο αριθμό που βρίσκει στο ευρετήριο του καταλόγου συν ένα. Το πρωτελευταίο στοιχείο θα είναι `list[len(list)-2]`. Υπάρχει και πιο εύκολος τρόπος. Στην Python το τελευταίο στοιχείο είναι πάντα το index -1. Αντίστοιχα το πρωτελευταίο είναι το index -2 κλπ. Να μερικά παραδείγματα ακόμη:

    >>> list[len(list)-1]
    'five'
    >>> list[len(list)-2]
    'four'
    >>> list[-1]
    'five'
    >>> list[-2]
    'four'
    >>> list[-6]
    'zero'

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

Ένας άλλος τρόπος να δούμε τμηματικά τους καταλόγους είναι να τους ``κόψουμε ...σε φέτες''. Το ακόλουθο παράδειγμα εξηγεί πως λειτουργεί αυτή η τεχνική:

    >>> list = [0,'Fred',2,'S.P.A.M.','Stocking',42,"Jack","Jill"]
    >>> list[0]
    0
    >>> list[7]
    'Jill'
    >>> list[0:8]
    [0, 'Fred', 2, 'S.P.A.M.', 'Stocking', 42, 'Jack', 'Jill']
    >>> list[2:4]
    [2, 'S.P.A.M.']
    >>> list[4:7]
    ['Stocking', 42, 'Jack']
    >>> list[1:5]
    ['Fred', 2, 'S.P.A.M.', 'Stocking']

Οι φέτες (slices) χρησιμοποιούνται για να μας επιστρέψουν μέρος ενός καταλόγου. Η σύνταξη έχει την εξής μορφή: `list[first_index:following_index]`. Η φέτα είναι ένα μέρος των στοιχείων του καταλόγου που ξεκινούν από σημείο του ευρετηρίου `first_index` μέχρι το σημείο `following_index`. Μπορούμε να χρησιμοποιήσουμε και τους δύο τύπους εύρεσης στοιχείων:

    >>> list[-4:-2]
    ['Stocking', 42]
    >>> list[-4]
    'Stocking'
    >>> list[-4:6]
    ['Stocking', 42]

Ένα χρήσιμο κόλπο που μπορούμε να κάνουμε με τις φέτες είναι να μην καθορίσουμε κάποιο όριο στο ευρετήριο. Αν το `first_index` δεν καθορίζεται εννοείται η αρχή του καταλόγου. Αντίστοιχα αν δεν καθορίζεται το `following_index` εννοείται το τέλος του καταλόγου. Να και τα ανάλογα παραδείγματα:

    >>> list[:2]
    [0, 'Fred']
    >>> list[-2:]
    ['Jack', 'Jill']
    >>> list[:3]
    [0, 'Fred', 2]
    >>> list[:-5]
    [0, 'Fred', 2]

Και ένα πρόγραμμα - παράδειγμα (Αντιγράψτε και επικολλήστε στον καθορισμό του ποιήματος, αν θέλετε):

    poem = ["<B>","Jack","and","Jill","</B>","went","up","the","hill","to","<B>",\
    "fetch","a","pail","of","</B>","water.","Jack","fell","<B>","down","and",\
    "broke","</B>","his","crown","and","<B>","Jill","came","</B>","tumbling",\
    "after"]

    def get_bolds(list):
            true = 1
            false = 0
            ## is_bold tells whether or not the we are currently looking at
            ## a bold section of text.
            is_bold = false
            ## start_block is the index of the start of either an unbolded
            ## segment of text or a bolded segment.
            start_block = 0
            for index in range(len(list)):
                    ##Handle a starting of bold text
                    if list[index] == "<B>":
                            if is_bold:
                                    print "Error:  Extra Bold"
                            ##print "Not Bold:",list[start_block:index]
                            is_bold = true
                            start_block = index+1
                    ##Handle end of bold text
                    if list[index] == "</B>":
                            if not is_bold:
                                    print "Error: Extra Close Bold"
                            print "Bold [",start_block,":",index,"] ",\
                            list[start_block:index]
                            is_bold = false
                            start_block = index+1

    get_bolds(poem)

με αποτέλεσμα:

    Bold [ 1 : 4 ]  ['Jack', 'and', 'Jill']
    Bold [ 11 : 15 ]  ['fetch', 'a', 'pail', 'of']
    Bold [ 20 : 23 ]  ['down', 'and', 'broke']
    Bold [ 28 : 30 ]  ['Jill', 'came']

Η συνάρτηση `get_bold` δέχεται έναν κατάλογο που στοιχεία του είναι λέξεις και token's. The tokens που ψάχνει είναι το `<B>` με το οποίο ξεκινάει το έντονο κείμενο και το`<\B>` με το οποίο το σταματάει. Η συνάρτηση `get_bold` διατρέχει τον κατάλογο και ψάχνει για τα tokens έναρξης και τέλους.

Η επόμενη λειτουργία των καταλόγων είναι η αντιγραφή τους. Δοκιμάστε κάτι απλό, όπως:

    >>> a = [1,2,3]
    >>> b = a
    >>> print b
    [1, 2, 3]
    >>> b[1] = 10
    >>> print b
    [1, 10, 3]
    >>> print a
    [1, 10, 3]

Μάλλον εκπλήσεστε αφού η αλλαγή του `b` συνεπάγεται επίσης την αλλαγή του `a` l. Αυτό που συνέβει είναι ότι η δήλωση `b = a` ορίζει το `b` συναρτήσει του `a`. Αυτό σημαίνει πως το `b` είναι ουσιαστικά ένα άλλο όνομα για το `a`. Οπότε οι όποιες μεταβολές στο `b` συμβαίνουν και στο`a` . Ωστόσο κάποιες δηλώσεις δεν δημιουργούν δύο ονόματα για τον ίδιο κατάλογο:

    >>> a = [1,2,3]
    >>> b = a*2
    >>> print a
    [1, 2, 3]
    >>> print b
    [1, 2, 3, 1, 2, 3]
    >>> a[1] = 10
    >>> print a
    [1, 10, 3]
    >>> print b
    [1, 2, 3, 1, 2, 3]

Σ' αυτήν την περίπτωση το `b` δεν συναρτάται του `a` αφού η δήλωση `a*2` δημιουργεί έναν νέο κατάλογο. έπειτα η δήλψση `b = a*2` ορίζει το `b` συναρτήσει του `a*2` και όχι του `a`. Όλοι οι καθρισμοί δημιουργούν αναφορές. Όταν δίνουμε έναν κατάλογο σε μια συνάρτηση ως μεταβλητή πάλι δημιουργούμε μια αναφορά. Τις περισσότερες φορές δεν χρειάζεται να ανησυχούμε για το αν δημιουργούνται αναφορές αλλά για το αν δημιουργούνται αντίγραφα. Όταν, ωστόσο, χρειάστούμε να κάνουμε αλλαγές σε έναν κατάλογο χωρίς να αλλάξουμε το όνομά του πρέπει να βεβαιωθούμε ότι έχουμε δημιουργήσει ένα αντίγραφο.

Υπάρχουν διάφοροι τρόποι για να δημιουργήσουμε ένα αντίγραφο μιας λίστας. Ο ευκολότερος (και αυτός που λειτουργεί τις περισσότερες φορές) είναι να χρησιμοποιήσουμε μια φέτα όλου του καταλόγου που είναι όμως ένας νέος κατάλογος:

    >>> a = [1,2,3]
    >>> b = a[:]
    >>> b[1] = 10
    >>> print a
    [1, 2, 3]
    >>> print b
    [1, 10, 3]

Με την φέτα `[:]` δημιουργούμε ένα αντίγραφο του καταλόγου. Ωστόσο αντιγράφεται μόνο ο ``εξωτερικός'' κατάλογος. Κάθε υποκατάλογος που βρίσκεται μέσα σ'αυτόν αναφέρεται στον αρχικό κατάλογο. Έτσι, όταν ο κατάλογος περιέχει άλλους καταλόγους ως στοιχεία θα πρέπει να αντιγραφούν κι αυτοί. Θα μπορούσαμε κάτι τέτοιο να το κάνουμε ``με το χέρι'' αλλά ευτυχώς η Python περιέχει το κατάλληλο module. Χρησιμοποιούμε την λειτουργία `deepcopy` του module `copy` :

    >>> import copy
    >>> a = [[1,2,3],[4,5,6]]
    >>> b = a[:]
    >>> c = copy.deepcopy(a)
    >>> b[0][1] = 10
    >>> c[1][1] = 12
    >>> print a
    [[1, 10, 3], [4, 5, 6]]
    >>> print b
    [[1, 10, 3], [4, 5, 6]]
    >>> print c
    [[1, 2, 3], [4, 12, 6]]

Πρώτα απ' όλα παρατηρούμε ότι το `a` είναι ένας πίνακας πινάκων. Ύστερα βλέπουμε ότι όταν τρέχει η δήλωση `b[0][1] = 10` τόσο το `a` όσο και το `b` αλλάζουν , αντίθετα από το `c` . Αυτό συμβαίνει επειδή οι υποπίνακες αναφέρονται σταθερά όταν χρησιμοποιούμε την φέτα. Πάντως με το `deepcopy` `c` αντιγράφονται πλήρως.

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

Μέχρι τώρα θα αναρωτιέστε σε τι χρησιμεύουν οι αναφορές. Ο κύριος λόγος είναι η ταχύτητα. Είναι πολύ γρηγορότερο να κάνουμε μια αναφορά σε έναν κατάλογο με χιλιάδες στοιχεία παρά να τον αντιγράψουμε. Ένας άλλος λόγος είναι ότι μας ετιτρέπεται να αλλάξουμε ένα μητρώο ή έναν κατάλογο μέσω μιας συνάρτησης. Απλά θυμηθήτε τις αναφορές αν αντιμετωπίσετε κάποιο περίεργο πρόβλημα με τα δεοδομένα να αλλάζουν ενώ δεν θα 'πρεπε.