FORTRAN & Αντικειμενοστραφής Προγραμματισμός ΣΝΜΜ 2016 M7 Δομές δεδομένων: Πίνακες Δρ. Γεώργιος Παπαλάμπρου Επικ. Καθηγητής ΕΜΠ Εργαστήριο Ναυτικής Μηχανολογίας george.papalambrou@lme.ntua.gr ΕΜΠ/ΣΝΜΜ Εργαστήριο Ναυτικής Μηχανολογίας
Περιεχόμενα ύλης 16/5 23/5 25/5 Γραπτή εξέταση 13/6/2016, 15.00 (και επιστροφή homework)
Περιεχόμενα Πίνακες 1 διάστασης 1. Βασικά χαρακτηριστικά 2. Implied DΟ 3. Είσοδος/Έξοδος στοιχείων πινάκων 4. Χρήση πινάκων σε υπολογισμούς 5. Δυναμική εκχώρηση μνήμης 6. Πράξεις με πίνακες: ταξινόμηση, εύρεση ελάχιστων, γραμμική αναζήτηση, δυαδική αναζήτηση,. Περισσότερα στοιχεία στο βιβλίο Metcalf et al., παρ. 2.10 Arrays of intrinsic type.
1. Βασικά χαρακτηριστικά πινάκων 1.1 Εισαγωγή Οι πίνακες είναι σύνθετες δοµές δεδοµένων με δυνατότητα αποθήκευσης µεγάλης οµάδας πληροφοριών στη µνήµη του υπολογιστή µε ένα ξεχωριστό όνοµα και ένα δείκτη που προσδιορίζει τη θέση του στοιχείου µέσα στην οµάδα. 1.2 Ορισμός πίνακα Ένας πίνακας είναι μια συλλογή δεδομένων του ίδιου τύπου. Τα στοιχεία πίνακα έχουν δείκτη (Index), πχ όπως x1, x2,..., xn στα μαθηματικά. Για να δηλώσουμε έναν πίνακα που ονομάζεται a με 10 στοιχεία πραγματικούς, θέτουμε τη χαρακτηριστική δήλωση τύπου dimension ως εξής: real, dimension(10) :: a Τα διαδοχικά στοιχεία του πίνακα a είναι: a(1), a(2), a(10). 1.3 Χαρακτηριστικά ενός πίνακα Ένας μονοδιάστατος πίνακας έχει τα ακόλουθα σημαντικά χαρακτηριστικά: Όνομα (name) Τύπος (type), αυτός είναι ο τύπος όλων των στοιχείων του πίνακα Εύρος (extent), αυτό είναι το εύρος των δεικτών
Οι δείκτες είναι ακέραιοι εντός του εύρους. Ο μεγαλύτερος και ο μικρότερος δείκτης αναφέρονται ως το κατώτερο όριο και το ανώτατο όριο, αντίστοιχα και δηλώνονται ως smaller-integer : larger-integer Έτσι, αν οι δείκτες πίνακα είναι στο εύρος από 0 ως 11, το extent είναι 0:11 ενώ αν οι δείκτες είναι στο εύρος από -3 και 21, το extent είναι -3 : 21. 1.4 Δήλωση πίνακα Η σύνταξη για την δήλωση πίνακα είναι η ακόλουθη type, DIMENSION( extent ) :: name-1, name-2,..., name-n όπου type είναι ο τύπος του πίνακα με όνομα name-1, name-2, name-n, DIMENSION είναι μια λέξη-κλειδί που απαιτείται, και extent το εύρος των δεικτών του πίνακα. Για παράδειγμα έχουμε τις ακόλουθες δηλώσεις: REAL, DIMENSION(-1:1) :: a, Sum INTEGER, DIMENSION(0:100) :: InputData Τα στοιχεία των πινάκων a και Sum είναι πραγματικά και οι δείκτες είναι στο εύρος του -1 και 1. Τα στοιχεία του πίνακα InputData είναι ακέραιοι και οι δείκτες είναι στο εύρος από 0 ως 100.
Οι δείκτες σε ένα εύρος μπορεί να είναι παράμετροι στο πρόγραμμα, όπως παρακάτω. INTEGER, PARAMETER :: MaximumSize = 100 LOGICAL, DIMENSION(1:MaximumSize) :: AnswerSheet INTEGER, PARAMETER :: LowerBound = -10 INTEGER, PARAMETER :: UpperBound = 10 REAL, DIMENSION(LowerBound:UpperBound) :: Score, Mark Στο παραπάνω, το εύρος του πίνακα AnswerSheet είναι 1 και 100, ενώ το εύρος των πινακων Score, Mark είναι -10 και 10.
1.5 Στοιχεία Πινάκων Ένα στοιχείο ενός πίνακα έχει την ακόλουθη μορφή: array-name ( integer-expression ) όπου array-name είναι το όνομα του πίνακα και integer-expression είναι μια έκφραση της οποίας το τελικό αποτέλεσμα είναι ένας ακέραιος. Το αποτέλεσμα του integer-expression, το οποίο πρέπει να είναι ένας ακέραιος στην περιοχή του εύρους, δίνει τον δείκτη του επιθυμητού στοιχείου του πίνακα. Παραδείγματα Στον πίνακα a τα στοιχεία είναι τα a(-1), a(0), a(1). Στον πίνακα InputData τα στοιχεία είναι InputData(0), InputData(1),..., InputData(100). Αν οι ακέραιες μεταβλητές i, j έχουν τιμές 3 και 8, τότε InputData(j-i) είναι ίσο με InputData(5), το InputData(i*j) είναι ίσο με InputData(24), and το InputData(j/i) είναι ίσο με InputData(2). REAL, DIMENSION(-1:1) :: a, Sum INTEGER, DIMENSION(0:100) :: InputData
2. Implied DΟ 2.1 Εισαγωγή Παρουσιάζεται παρακάτω η χρήση δήλωσης ενός υπονοούμενου DO (implied DO), με κύρια εφαρμογή την εισαγωγή στιχείων πίνακα, όπως περιγράφεται στην επόμενη ενότητα (3). Τα implied DO loops παρέχουν ένα γρήγορο τρόπο καταχώρισης πολλών στοιχείων. Αυτά τα στοιχεία, ανάλογα με το σημείο στο πρόγραμμα όπου χρησιμοποιείται implied DO, μπορεί να είναι μεταβλητές, εκφράσεις, ή ακόμα και implied DO. 2.2 Σύνταξη Η σύνταξη ενός υπονοούμενου DO είναι η ακόλουθη: ( item-1, item-2,..., item-n, DO-var = initial, final, step ) ( item-1, item-2,..., item-n, DO-var = initial, final ) Ξεκινά με ένα (, που ακολουθείται από ένα σύνολο στοιχείων, χωρισμένα με κόμμα, ακολουθείται από μια μεταβλητή DO, ένα σύμβολο ισότητας, μια αρχική τιμή, τελική τιμή και ένα μέγεθος βήματος και τελειώνει με ). Σαν ένα τυπικό βρόχο DO, αν το μέγεθος βήματος είναι 1, μπορεί να παραληφθεί.
2. Implied DΟ - συνέχεια Η έννοια υπονοούμενου DO είναι απλή : Για κάθε πιθανή τιμή της μεταβλητής DO, όλα τα στοιχεία (δηλαδή, το στοιχείο - 1, το στοιχείο - 2,..., στοιχείο - n ) αναφέρονται μία φορά και τα γειτονικά στοιχεία διαχωρίζονται με κόμματα. Παραδείγματα 1. Στη συνέχεια, η μεταβλητή DO i μπορεί να έχει τιμές -1, 0, 1, και 2. Το στοιχείο που πρέπει να παρουσιαστεί είναι το i. ( i, i = -1, 2 ) Για i = -1, το στοιχείο που παρατίθεται είναι -1. Για i = 0, το στοιχείο που παρατίθεται είναι 0. Για i = 1, το στοιχείο που παρατίθεται είναι 1. Τέλος, για i = 2, το στοιχείο που παρατίθεται είναι 2. Ο συνδυασμός αυτών των τεσσάρων περιπτώσεων μαζί δίνει -1, 0, 1, 2 2. Στη συνέχεια, η μεταβλητή DO i μπορεί να έχει τιμές 1, 4, 7 και 10. Τα στοιχεία είναι i και i * i. ( i, i*i, i = 1, 10, 3 ) Για i = 1, τα στοιχεία που παρατίθενται είναι 1 και 1 = 1 * 1. Για i = 4, τα στοιχεία που παρατίθενται είναι 4 και 16. Για i = 7, τα στοιχεία που παρατίθενται είναι 7 και 49. Τέλος, για i = 10, τα στοιχεία που παρατίθενται είναι 10 και 100.
2. Implied DΟ - συνέχεια Ο συνδυασμός αυτών των τεσσάρων περιπτώσεων μαζί δίνει 1, 1, 4, 16, 7, 49, 10, 100 3. Στη συνέχεια, η μεταβλητή DO i και τα στοιχεία είναι a(i) και b(i + 1). ( a(i), b(i+1), i = 1, 3 ) Για i = 1, τα στοιχεία που παρατίθενται είναι a(1) και b(2). Για i = 2, οι τιμές είναι a(2) και b(3). Για i = 3, τα στοιχεία είναι a(3) και b(4). Συνολικά υπάρχουν έξι στοιχεία που παρατίθενται, καθώς το i πηγαίνει από 1 έως 3: a(1), b(2), a(2), b(3), a(3), b(4)
3. Είσοδος/Έξοδος στοιχείων πινάκων 1.1 Είσοδος στοιχείων Ο ευκολότερος τρόπος ανάγνωσης δεδομένων σε ένα πίνακα θα μπορούσε να είναι ο ακόλουθος: INTEGER, DIMENSION(1:10) INTEGER :: x :: n, i READ(*,*) n DO i = 1, n READ(*,*) x(i) END DO Στην περίπτωση αυτή, αν η είσοδος για το n είναι 5, η δήλωση READ (*,*) στην DO-loop εκτελείται 5 φορές. Κάθε φορά που η READ(*,*) εκτελείται, διαβάζει σε μία γραμμή και λαμβάνει την πρώτη ακέραια τιμή της γραμμής αυτής. Ως εκ τούτου, εκτός από την είσοδο για το n, απαιτούνται 5 γραμμές εισόδου.
3. Είσοδος/Έξοδος στοιχείων πινάκων Ωστόσο το υπονούμενο DΟ (implied DO) μπορεί να απλοποιήσει την εισαγωγή σε μεγάλο βαθμό. Εξετάστε το ακόλουθο παράδειγμα: INTEGER, DIMENSION(1:10) :: x INTEGER :: n, i READ(*,*) n READ(*,*) (x(i), i=1, n) Εάν η τιμή του n είναι 5, η implied DO στο READ (*,*) έχει αναπτυχθεί σε x(1), x(2), x(3), x(4), x(5) Ως εκ τούτου, η δήλωση READ (*,*) είναι στην πραγματικότητα ισοδύναμη με INTEGER, DIMENSION(1:10) :: x INTEGER :: n, i READ(*,*) x(1), x(2), x(3), x(4), x(5)
3. Είσοδος/Έξοδος στοιχείων πινάκων Ποιά είναι η διαφορά; Υπενθυμίζεται ότι η εκτέλεση ενός READ (*,*) απαιτεί τουλάχιστον μία γραμμή εισόδου. Ως αποτέλεσμα, η ανωτέρω READ (*,*) με υπονούμενο DO μπορεί να διαβάσει και τους 5 αριθμούς σε μια ενιαία γραμμή εισόδου, αντί για πέντε ξεχωριστές γραμμές. Επιπλέον, αυτή η READ (*,*) μπορεί επίσης να διαβάσει στην είσοδο πέντε ξεχωριστές γραμμές, αφού η Fortran θα ψάξει αυτόματα για την επόμενη τιμή εισόδου. Εξετάστε τα ακόλουθα δύο αρχεία εισόδου: File #1 File #2 ------- ------- 5 5 10 10 30 20 40 50 30 20 40 50
3. Είσοδος/Έξοδος στοιχείων πινάκων Και τα ακόλουθα 2 READ(*,*): READ #1 READ #2 ======= ======= INTEGER,DIMENSION(1:10) :: x INTEGER,DIMENSION(1:10) :: x INTEGER :: n, i INTEGER :: n, i READ(*,*) n READ(*,*) n DO i = 1, n READ(*,*) (x(i), i=1, n) READ(*,*) x(i) END DO To READ #1 μπορεί να διαβάσει σίγουρα στο Αρχείο # 1 σωστά. Το αποτέλεσμα είναι n x(1) x(2) x(3) x(4) x(5) 5 10 30 20 40 50
3. Είσοδος/Έξοδος στοιχείων πινάκων Χρησιμοποιώντας το READ # 2 για να διαβάσετε το αρχείο # 1 θα πάρετε το ίδιο αποτέλεσμα. Αλλά η READ # 1 δεν θα είναι σε θέση να διαβάσει το αρχείο # 2. Για το READ # 1, n μεταβλητή n εξακολουθεί να λαμβάνει 5. Στη συνέχεια, για να διαβάσετε την τιμή του x(1), διαβάζεται μια νέα γραμμή, η οποία περιέχει και τους 5 ακεραίους. Δεδομένου ότι η x(1) χρειάζεται μόνο μια τιμή, λαμβάνεται το 10 και όλες οι υπόλοιπες τιμές αγνοούνται. Όταν ο βρόχος DO πηγαίνει πίσω για να διαβάσει τιμή για το x(2), δεν υπάρχει τιμή εισόδου στην είσοδο και εμφανίζεται ένα σφάλμα!
3. Είσοδος/Έξοδος στοιχείων πινάκων 1.2 Έξοδος στοιχείων Εφόσον ξέρετε τον τρόπο χρήσης του implied DO για την είσοδο, μπορείτε να το εφαρμόσετε για την έξοδο. Υπάρχει μια διαφορά όμως: Για READ (*,*) τα στοιχεία στο implied DO πρέπει να είναι μεταβλητές, δεδομένου ότι μόνο οι μεταβλητές μπορούν να χρησιμοποιηθούν σε ένα READ (*,*). Για WRITE (*,*), τα στοιχεία μπορούν να είναι σταθερές, μεταβλητές ή εκφράσεις. Τα αποτελέσματα των εκφράσεων αξιολογούνται φυσικά πρώτα. Εξετάστε το ακόλουθο παράδειγμα: INTEGER, DIMENSION(1:10) :: x, y INTEGER :: n = 5, i DO i = 1, 5 WRITE(*,*) x(i), y(i) END DO WRITE(*,*) (x(i), y(i), i=1, n)
3. Είσοδος/Έξοδος στοιχείων πινάκων 1.2 Έξοδος στοιχείων Η πρώτη WRITE (*,*) εκτελείται πέντε φορές. Κάθε φορά γράφει δύο ακέραιους αριθμούς, ένα για x(i) και το άλλο για το y(i). Έτσι, η έξοδος είναι σε πέντε διαφορετικές γραμμές. Η δεύτερη WRITE (*,*) είναι ισοδύναμη με την INTEGER, DIMENSION(1:10) :: x, y INTEGER :: n = 5, i WRITE(*,*) x(1),y(1),x(2),y(2),x(3),y(3),x(4),y(4),x(5),y(5) Συνεπώς, οι δέκα τιμές εξόδου είναι στην ίδια γραμμή.
4. Χρήση πινάκων σε υπολογισμούς Παραδείγματα που απεικονίζουν τη χρήση των πινάκων σε υπολογισμούς παρουσιάζονται εδώ. Π1. Αρχικοποίηση πίνακα στο 0 INTEGER, PARAMETER :: LOWER = -100, UPPER = 100 INTEGER, DIMENSION(LOWER:UPPER) :: a INTEGER :: i DO i = LOWER, UPPER a(i) = 0 END DO Στον παραπάνω κώδικα, η DO μεταβλητή i λαμβάνει τιμές από -100 έως 100. Για κάθε τιμή του i, τα στοιχείατου πίνακα a(i) θέτονται στο μηδέν.
4. Χρήση πινάκων σε υπολογισμούς Π2. Ορισμός ενός στοιχείου πίνακα στο δείκτη του INTEGER, PARAMETER :: BOUND = 20 INTEGER, DIMENSION(1:BOUND) :: Array INTEGER :: i DO i = 1, BOUND Array(i) = i END DO Αυτός ο κωδικός θέτει το στοιχείο πίνακα Array(i) στο i. Έτσι, το Array(1) περιέχει 1, Array (2) περιέχει 2, κλπ
4. Χρήση πινάκων σε υπολογισμούς Π3. Υπολογισμός αθροίσματος των στοιχείων πίνακα m ως n, όπου m < = n, και m,n είναι ακέραιοι που εισάγονται REAL, PARAMETER :: MAX_SIZE = 100 REAL, DIMENSION(-MAX_SIZE:MAX_SIZE) :: DataArray REAL :: Sum INTEGER :: m, n, k READ(*,*) m, n Sum = 0.0 DO k = m, n Sum = Sum + DataArray(k) END DO Για κάθε τιμή του k στην περιοχή των m και n, η τιμή του DataArray(k) προστίθεται στον συσσωρευτή Sum.
4. Χρήση πινάκων σε υπολογισμούς Π4. Υπολογισμός του αθροίσματος των αντίστοιχων στοιχείων δύο πινάκων και αποθήκευση σε έναν τρίτο πίνακα REAL, PARAMETER :: LENGTH = 35 REAL, DIMENSION(1:LENGTH) :: A, B, C INTEGER :: Index DO Index = 1, LENGTH C(Index) = A(Index) + B(Index) END DO
4. Χρήση πινάκων σε υπολογισμούς Π5. Υπολογισμός εσωτερικού γινομένου δύο πινάκων Εσωτερικό γινόμενο δύο πινάκων είναι το άθροισμα όλων των γινομένων των αντίστοιχων στοιχείων. REAL, PARAMETER :: VECTOR_SIZE = 10 REAL, DIMENSION(1:VECTOR_SIZE) :: Vector1, Vector2 REAL :: InnerProduct INTEGER :: Elements_Used, n READ(*,*) Elements_Used InnerProduct = 0.0 DO n = 1, Elements_Used InnerProduct = InnerProduct + Vector1(n)*Vector2(n) END DO Στα παραπάνω, το Elements_Used διαβάζεται και δείχνει ότι μόνο τα πρώτα Elements_Used στοιχεία θα χρησιμοποιηθούν για το γινόμενο.
5. Δυναμική εκχώρηση μνήμης 5.1 Εισαγωγή Υπάρχει η περίπτωση τρόπου δήλωσης διαστάσεων σε Πίνακες, όπου οι διαστάσεις είναι γνωστές αλλά όχι και το μέγεθος τους. Τότε το μέγεθος καθορίζεται κατά τη διάρκεια εκτέλεσης του προγράμματος, με διαδικασία γνωστή ως Δυναμική εκχώρηση μνήμης (Dynamic memory allocation). Σε αυτή την περίπτωση δηλώνονται με τη χρήση της εντολής ALLOCATE όπως παρακάτω: INTEGER,ALLOCATABLE,DIMENSION(:,:,:)... ALLOCATE (array1(10,10,10))... DEALLOCATE(array1) :: array1 Μετά την χρήση του πίνακα η εκχώρηση μνήμης αποδεσμεύεται μέσω της εντολής deallocate(a). Περισσότερα στοιχεία στο βιβλίο Metcalf et al., παρ. 6.5, Allocation of data
5. Δυναμική εκχώρηση μνήμης Ακολουθεί σχετικό παράδειγμα με χρήση εκχώρησης μνήμης για 2 πίνακες pin1(), πηγή ΕΜΠ/ΗΜΜΗΥ. (πρόγραμμα Allocatable_array.f95 στην ιστοσελίδα μαθήματος),
6. Πράξεις με πίνακες: ανάστροφος, γραμμική αναζήτηση, δυαδική αναζήτηση, ταξινόμηση. 6.1 Εύρεση ανάστροφου πίνακα (reversing an array) Γράψτε ένα πρόγραμμα που αντιστρέφει τη σειρά των στοιχείων ενός δεδομένου πίνακα. (πρόγραμμα Reverse.f95 στην ιστοσελίδα μαθήματος) Για παράδειγμα, αν η δεδομένη διάταξη περιλαμβάνει πέντε στοιχεία: 3 5 7 2 4 μετά την αντιστροφή της σειράς των στοιχείων, το αποτέλεσμα είναι: 4 2 7 5 3
6.1 Εύρεση ανάστροφου πίνακα
6.1 Εύρεση ανάστροφου πίνακα Το παραπάνω πρόγραμμα χρησιμοποιεί ένα a() για να κρατήσει τον πίνακα εισαγωγής και n για τον πραγματικό αριθμό των στοιχείων. Η τιμή για το n και οι τιμές εισόδου διαβάζονται με τις δύο πρώτες READ (*,*). Σημειώστε ότι η είσοδος έχει μια μορφή ως εξής : 8 10 50 30 70 35 97 65 59 Η πρώτη γραμμή περιέχει την τιμή για το η και η δεύτερη γραμμή έχει τον αριθμό των στοιχείων του πίνακα.
6. Πράξεις με πίνακες: ανάστροφος, γραμμική αναζήτηση, δυαδική αναζήτηση, ταξινόμηση. 6.2 Ταξινόμηση πίνακα (sorting) Η ταξινόμηση είναι μια σημαντική εφαρμογή στον υπολογιστή. Υπάρχουν πολλές μέθοδοι διαλογής. Γράψτε ένα πρόγραμμα για να αναδιατάξετε τα στοιχεία μιας δεδομένης σειράς σε αύξουσα σειρά (πρόγραμμα Sorting.f95 στην ιστοσελίδα μαθήματος) Ας υποθέσουμε ότι ο πίνακας έχει n στοιχεία. Βρίσκουμε το μικρότερο στοιχείο και τη θέση του στον πίνακα και ανταλλάσουμε αυτό το στοιχείο με το πρώτο. Μετά από αυτό, το πρώτο στοιχείο είναι το μικρότερο. Στη συνέχεια βρίσκουμε το μικρότερο στοιχείο και τη θέση του όλων των στοιχείων στην περιοχή των 2 και n και αλλάζουμε αυτό με το δεύτερο. Μετά από αυτό, τα δύο πρώτα στοιχεία είναι ταξινομημένα. Επαναλαμβάνοντας την διαδικασία το σύνολο της διάταξης ταξινομείται.
6. Πράξεις με πίνακες: ανάστροφος, γραμμική αναζήτηση, δυαδική αναζήτηση, ταξινόμηση. 6.2 Ταξινόμηση πίνακα H function FindMinimum( ) λαμβάνει πίνακα x( ), μια αρχική θέση Start, και μια τελική θέση End, και επιστρέφει τη θέση του μικρότερου στοιχείου σε αυτό το εύρος. H υπορουτίνα Swap( ) ανταλλάσσει τις τιμές των ορισμάτων της. Η υπορουτίνα Sort( ) παίρνει έναν πίνακα x( ) μεγέθους Size. Για κάθε στοιχείο μεταξύ του πρώτου και αυτού δίπλα στο τελευταίο (δηλ, Size -1), η θέση του μικρότερου στοιχείου στην περιοχή από i και Size βρίσκεται χρησιμοποιώντας την FindMinimum (). Στη συνέχεια η υπορουτίνα Swap( ) καλείται για να ανταλλάξει τις τιμές του x (i) και x(location). Μετά από αυτό, τα στοιχεία στην περιοχή από 1 μέχρι i είναι ταξινομημένα.
6. Πράξεις με πίνακες: ανάστροφος, γραμμική αναζήτηση, δυαδική αναζήτηση, ταξινόμηση. 6.2 Ταξινόμηση πίνακα 1/3
6. Πράξεις με πίνακες: ανάστροφος, γραμμική αναζήτηση, δυαδική αναζήτηση, ταξινόμηση. 6.2 Ταξινόμηση πίνακα 2/3
6. Πράξεις με πίνακες: ανάστροφος, γραμμική αναζήτηση, δυαδική αναζήτηση, ταξινόμηση. 6.2 Ταξινόμηση πίνακα 3/3