04. Arrays

Μεταβλητές – πίνακες στοιχείων (arrays)

Oι πίνακες στην Perl είναι διατεταγμένες σειρές βαθμωτών μεταβλητών, είναι δηλαδή δομές δεδομένων που περιέχουν μια σειρά από βαθμωτές μεταβλητές με συγκεκριμένη αρίθμηση. Μπορούν να είναι πίνακες αριθμών (ακέραιων ή δεκαδικών), σειρών χαρακτήρων, αλφαριθμητικών στοιχείων. Αυτό που αλλάζει σε σχέση με τις βαθμωτές μεταβλητές είναι μόνο η δομή.

Εισαγωγή πίνακα

Oι πίνακες δηλώνονται με το όνομά τους στο οποίο προηγείται το σύμβολο του πίνακα που για την Perl ειναι το @. Έτσι όπως π.χ. το:

$month="March";

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

#! /usr/bin/perl use warnings;

@spring=(March, April, May);

Eναλλακτικά μπορούμε να χρησιμοποιήσουμε τον τελεστή qw() που σημαίνει quote on whitespace μόνο που σε αυτήν την περίπτωση αντί για κόμματα, τα στοιχεία του πίνακα θα πρέπει να διαχωρίζονται με κενό.

#! /usr/bin/perl use warnings;

@spring=qw(March April May);

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

#! /usr/bin/perl use warnings;

@range=(1..10);

print @range,"\n";

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

Δομή των πινάκων στην Perl

Οι πίνακες της Perl είναι μονοδιάστατες λίστες, δεν έχουν δηλαδή διαστάσεις. Στην περίπτωση που θέλουμε να αναπαραστήσουμε δεδομένα περισσότερων της μίας διαστάσεων θα πρέπει να χρησιμοποιήσουμε πίνακες μέσα σε πίνακες ή κάποια άλλη πιο προχωρημένη μορφή δεδομένων. Για τις ανάγκες ενός εισαγωγικού μαθήματος θα μείνουμε στη χρήση απλών λιστών μορφής:

@spring=(March, April, May)

@range=(1..10) @range2=(-10..10) # πίνακας ακέραιων αριθμών από -10 έως 10

Η εσωτερική δομή ενός πίνακα ορίζει την διαδοχή των στοιχείων του τα οποία καταλαμβάνουν αριθμημένες θέσεις (indices) που ξεκινούν με το πρώτο στοιχείο να έχει index=0 και το τελευταίο με index=μήκος πίνακα-1. Κάθε στοιχείο του πίνακα είναι μια βαθμωτή μεταβλητή και μπορεί να ανακληθεί ως τέτοια με τη χρήση του συμβόλου βαθμωτών μεταβλητών $. Έτσι αν θέλουμε από έναν πίνακα να τυπωθεί το πρώτο στοιχείο απλώς ζητάμε την εκτύπωση ως έξης:

#! /usr/bin/perl use warnings;

@spring=(March, April, May);

print $spring[0],"\n";

Οι παραπάνω εντολές δημιουργούν έναν πίνακα τριών στοιχείων και εκτυπώνουν στην οθόνη το πρώτο από αυτά. Αντίστοιχα το δεύτερο και τρίτο στοιχείο θα είναι: $spring[1] και $spring[2].

Μεγάλη προσοχή λοιπόν στα εξής:

  • Απόδοση τιμών. Πρέπει να θυμόμαστε οτι η αρίθμηση των στοιχείων μας ξεκινά από το 0.
  • Αναζήτηση στοιχείων. Τα αναζητούμε πάντα ως βαθμωτές μεταβλητες ($)
  • Σε περίπτωση που αναζητούμε παραπάνω από ένα στοιχεία, αυτά είναι και πάλι πίνακας. Μοναδικά στοιχεία αναζητώνται με την χρήση του συμβόλου $ αλλά υποσύνολα στοιχείων του πίνακα πρέπει να ανακληθούν ως @. Για παράδειγμα:

#! /usr/bin/perl use warnings;

@spring=(March, April, May);

print $spring[0],"\n";

print @spring[1,2],"\n";

print @spring[-1,2],"\n";

Οι πιο πάνω εντολές τυπώνουν με τη σειρά March, AprilMay και MayApril. Το τελευταίο συμβαίνει γιατί η Perl μεταφράζει τα στοιχεία ενός πίνακα και με αντίστροφη σειρά. Έτσι το -1 είναι το τελευταίο στοιχείο, το -2 το πρότελευταίο κ.ο.κ. Μεγαλύτερα υποσύνολα στοιχείων μπορούν να εισαχθούν με τον τελεστή "..". Συνδυασμός των στοιχείων μπορεί να μας δώσει όποιο υποσύνολο θέλουμε.

#! /usr/bin/perl use warnings;

@year=(January, February, March, April, May, June, July, August, September, October, November, December);

print @year[-1,2..5,7,8..11],"\n";

Ειδικά χαρακτηριστικά δομής πινάκων

Οι πίνακες στην Perl δεν χρειάζεται να προδηλωθούν και δεν έχουν όρια μήκους. Δεν υπάρχει δηλαδή η ανάγκη να δηλωθεί εξαρχής το μέγεθος ενός πίνακα. Η δήλωση σε περίπτωση που χρειάζεται μπορεί να γίνει με μια απλή εισαγωγή κενής παρένθεσης:

#! /usr/bin/perl use warnings;

@array=();

Ένα πολύ σημαντικό στοιχείο είναι ότι οι πίνακες της Perl δεν έχουν χαρακτηριστικά "αραιών" (sparse) δομών. Η δήλωση π.χ. του στοιχείου 10000;

#! /usr/bin/perl use warnings;

@array=();

$array[10000]="DNA";

κάνει αυτόματα τον πίνακα @array, μια λίστα 10001 στοιχείων εκ των οποίων τα 10000 πρώτα είναι μη-δηλωμένα (undefined) και το 10001 ίσο με το string "DNA". To χαρακτηριστικό αυτό θέλει ιδιαίτερη προσοχή καθώς ενδέχεται να επηρεάζει χαρακτηριστικα της μνήμης του υπολογιστή κατά την εκτέλεση προγραμμάτων. Το γεγονός ότι οι πίνακες δεν έχουν πάνω όριο μας επιτρέπει να προσθέτουμε στοιχεία πέραν του μεγέθους τους:

#! /usr/bin/perl use warnings;

@array=();

$array[10000]="DNA";

$array[20000]="protein";

σημαίνει τώρα πως ο πίνακας @array έχει τώρα μέγεθος 20001 στοιχείων. Μια χρήσιμη εντολή για την απελευθέρωση της μνήμης από έναν πίνακα είναι η διαγραφή του με την εντολή undef(). Είναι προφανές πως αυτή η εντολή πρέπει να χρησιμοποιείται με προσοχή:

#! /usr/bin/perl use warnings;

@array=();

$array[10000]="DNA";

$array[20000]="protein";

undef(@array); # o @array είναι τώρα άδειο

Πίνακες και βαθμωτές μεταβλητές

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

Κάθε πίνακας αποδίδει αυτόματα το μέγεθός του σε μια βαθμωτή μεταβλητή με την συνάρτηση scalar():

#! /usr/bin/perl use warnings;

@year=(January, February, March, April, May, June, July, August, September, October, November, December);

print scalar(@year),"\n";

Το ίδιο συμβαίνει αν απλώς αποδώσουμε τον πίνακα σε μια βαθμωτή μεταβλητή

@year=(January, February, March, April, May, June, July, August, September, October, November, December);

$sizeofyear=@year;

print $sizeofyear,"\n";

Προσοχή στη διαφορά:

$string = @array; # μήκος του @array

$string="@array"; # stringification: το $string είναι τα στοιχεία του $array σε ένα string

H τελευταία αυτή συνάρτηση είναι ιδιαίτερα χρήσιμη για την καλύτερη απεικόνιση του πίνακα κατά την εκτύπωση. Συγκρίνετε τις δύο εντολές print παρακάτω:

#! /usr/bin/perl use warnings;

@year=(January, February, March, April, May, June, July, August, September, October, November, December);

$string="@year";

print @year,"\n";

print $string,"\n";

Μερικές χρησιμες συναρτήσεις πινάκων

scalar

Όπως είπαμε και πιο πάνω η scalar() δίνει το μήκος του πίνακα σε αριθμό στοιχείων.

#! /usr/bin/perl use warnings;

@year=(January, February, March, April, May, June, July, August, September, October, November, December);

print scalar(@year),"\n";

reverse

Η reverse() αντιστρέφει τη σειρά των στοιχείων του πίνακα κάνοντας το τελευταίο πρώτο, το προτελευταίο δεύτερο κ.ο.κ.

#! /usr/bin/perl use warnings;

@year=(January, February, March, April, May, June, July, August, September, October, November, December);

@rev=reverse(@year);

print @rev,"\n";

sort

H συνάρτηση sort κατατάσει τα στοιχεία ενός πίνακα δημιουργώντας έναν νέο. Η σύνταξή της δηλαδή είναι από πίνακα σε πίνακα και είναι η εξής:

@sorted=sort(@array);

To σημαντικό στοιχείο εδώ είναι πως η κατάταξη γίνεται με αλφαβητική σειρά. Στην εντολή λοιπόν:

@array=(A,G,C,G,T,T,T,A,A);

@sorted=sort(@array);

print "@sorted","\n";

η σειρά είναι η αλφαβητική: Α A A C G G T T T.

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

@array=(A,G,C,8,G,T,T,T,1,A,A);

@sorted=sort(@array);

print "@sorted","\n";

θα αποδώσουν: 1 8 Α A A C G G T T T. Η κατάταξη όμως δεν είναι αυστηρά αριθμητική αλλά με βάση τη σειρά των ASCII χαρακτήρων. Έτσι οι εντολές:

@array=(A,G,C,8,G,T,T,T,1,A,A,10,2);

@sorted=sort(@array);

print "@sorted","\n";

θα αποδώσουν: 1 10 8 Α A A C G G T T T

Αν θέλουμε να πετύχουμε αριθμητική σειρά θα πρέπει να χρησιμοποιήσουμε τον τελεστή ($a <=> $b) που καθοδηγεί την κατάταξη κατ' αύξουσα:

@array=(1,8,10,2);

@sorted=sort{ $a <=> $b }(@array);

print "@sorted","\n";

ή αντιστρέφοντας τα $b και $a κατά φθίνουσα σειρά:

@array=(1,8,10,2);

@sorted=sort{ $b <=> $a }(@array);

print "@sorted","\n";

split/join

Το ζεύγος συναρτήσεων split/join είναι ιδιαίτερα σημαντικό καθώς επιτρέπουν την αλληλομετατροπή strings σε πίνακες (arrays). Συγκεκριμένα η εντολή split

μετατρέπει μια βαθμωτή μεταβλητη σε πίνακα, σπάζοντας εναν-εναν τους χαρακτήρες της σε στοιχεία του πίνακα, προσθέτοντας εναν διαχωριστή ανάμεσα τους. Στην περιπτωση που ο διαχωριστής ειναι το τιποτα ('') τότε ο πίνακας ειναι ισοδύναμος με τη μεταβλητή σε κομμάτια.

#! /usr/bin/perl

use warnings;

$string = "AGCTGACACACGT";

@array=split("",$string);

print $string,"\n";

print "@array","\n";

Oι δύο εντολές print επιστρέφουν τα ίδια δεδομένα αρχικά σε μορφής ενός string και στη συνέχεια "σπασμένα" σε μορφή πινάκων. Iδιαίτερη προσοχή στη σύνταξη του split που είναι:

@array=split("delimiter",$string);

Η χρήση ενός delimiter διαφορετικού από το "" θα κάνει το "σπάσιμο" του string στα στοιχεία που ισούνται με τον delimiter

#! /usr/bin/perl

use warnings;

$string = "AGCTGACACACGT";

@array=split("A",$string);

print $string,"\n";

print "@array","\n";

H split δηλαδή παίρνει σαν input ένα string και την αποδίδει σε πίνακα @array. To αντίθετο ισχύει για την συνάρτηση join() που μετατρέπει τα στοιχεία ενός πίνακα σε string παρεμβάλοντας ανάμεσα τους τον delimiter που δηλώνεται.

$string = join ("delimiter", @array)

Η χρήση του join() είναι ουσιαστικά η αντίστροφη του split()

#! /usr/bin/perl

use warnings;

@array=(A,G,C,T,T,G,A,C);

$string = join("-",@array);

print "@array","\n";

print $string,"\n";

Στο πιο πάνω παράδειγμα ο πίνακας @array "συγκολλείται" σε ένα string μέσω της συνάρτησης join() και ανάμεσα στα στοιχεία του παρεμβαλλεται ο χαρακτήρας "-" που χρησιμοποιείται σαν delimiter. Στην περίπτωση που θα θέλαμε απλώς να δημιουργηθεί ένα string με τα στοιχεία του array δεν θα είχαμε παρά να γράψουμε την εντολή join με κενό το delimiter

$string = join("",@array);

pop/push

Το ζευγάρι συναρτήσεων pop()/push() χρησιμοποιείται για την εξαγωγή και εισαγωγή μοναδικών στοιχείων σε πίνακα. Πιο συγκεκριμένα το pop() αφαιρεί από πίνακα το τελευταίο του στοιχείο αποδίδοντάς το σε βαθμωτή μεταβλητή:

$last = pop(@array);

Mε την εκτέλεση αυτής της εντολής το $last έχει γίνει ίσο με το τελευταίο στοιχείο του πίνακα και κυρίως ο πίνακας έχει απομειωθεί κατά ένα στοιχείο. Με αντίθετο τρόπο το push παίρνει μια βαθμωτή μεταβλητή και την προσθέτει στον πίνακα ως νέο τελευταίο στοιχείο, αυξάνοντας τον πίνακα κατά ένα στοιχείο:

$string="hey";

push @array,$string;

Iδιαίτερης προσοχής χρήζει η διαφορετική σύνταξη των δύο εντολών, παρά το γεγονός ότι οι λειτουργίες τους μοιάζουν. Η μεν pop() πρέπει να γραφτεί έτσι ώστε το αποτέλεσμά της να αποδίδεται σε μια βαθμωτή μεταβλητή, η δε push απλώς αναγράφεται καθώς δρα σε υπάρχοντα πίνακα. H pop() μπορεί να εκτελεστεί και μόνη της:

$last = pop(@array);

στην περίπτωση που απλώς θέλουμε να αφαιρεθεί ένα στοιχείο από τον πίνακα.

shift/unshift

Το ζευγάρι συναρτήσεων shift()/unshift() κάνει λειτουργίες απολύτως ανάλογες των pop/push με τη διαφορά ότι τις κάνει στην αρχή και όχι στο τέλος του πίνακα. Έτσι οι εντολές:

$first = shift(@array);

και

unshift @array,$string;

αφαιρούν (η πρώτη) και προσθέτουν (η δεύτερη) ένα στοιχείο στον πίνακα @array.

splice

Η συνάρτηση splice() είναι αρκετά χρήσιμη για την τροποποίηση πινάκων χωρίς να δρα υποχρεωτικά στην αρχή ή στο τέλος αλλά σε οποιαδήποτε θέση. Λειτουργεί από πίνακα σε πίνακα και η σύνταξή της είναι πιο περίπλοκη για αυτόν τον λόγο:

splice(@array,$offset,$delete,@somearray)

Στο παραπάνω υπόδειγμα ο @array τροποποιείται έτσι ώστε στη θέση $οffset έχει εισαχθεί (εγκιβωτιστεί) ένας νέος πίνακας @somearray σβήνοντας αντίστοιχα τόσα στοιχεία όσα προβλέπει η μεταβλητη $delete. Τα $offset και $delete πρέπει να είναι υποχρεωτικά ακέραιοι αριθμοί, ενώ στη θέση του @somearray μπορεί να είναι και μια απλή βαθμωτή μεταβλητή. Για παράδειγμα:

#! /usr/bin/perl

use warnings;

@array=(A,G,C,T,T,G,A,C);

@somearray=(P,Q,R);

splice(@array,1,0,@somearray);

print "@array","\n";

splice(@array,5,2,"nothing");

print "@array","\n";

Οι δύο εντολές splice διαφέρουν α) στη θέση στην οποία παρεμβάλλεται το στοιχείο β) στο κατα πόσο διαγράφονται στοιχεία του υπάρχοντος πίνακα @array και γ) στο κατά πόσο προστίθεται πίνακας ή βαθμωτή μεταβλητή. Ένα σημείο που πρέπει να κρατήσουμε είναι ότι το σημείο εισόδου είναι ο αριθμός που θα έχει το στοιχείο που εισάγεται στον νέο πίνακα. Έτσι η:

splice(@array,0,0,@somearray)

θα τοποθετήσει τα στοιχεία του @somearray στην αρχή του @array ενώ η:

splice(@array,1,0,@somearray)

θα τα τοποθετήσει στη δεύτερη θέση.