Ruby language
   

Gli array                

In Ruby un array è una sequenza di oggetti che possono essere acceduti tramite la loro posizione o indice. La prima cosa che potrebbe attirare l'attenzione è quella parola "oggetti". Chi viene dai linguaggi a tipizzazione statica sa che, di norma, gli elementi di un array devono essere dello stesso tipo. Non è possibile ad esempio mischiare caratteri con numeri e così via e a volte bisogna ricorrere a degli escamotage per simulare questa libertà di definizione dei singoli item (ad esempio definendo un array di oggetti il che però implica certe penalizzazioni a run time). Ruby è un linguaggio dinamico e quindi in questo senso vi è grande libertà. Tale libertà è tanto ampia che permette non solo il fatto che gli elementi possono non essere tutti dello stesso tipo ma è anche consentito che gli stessi siano cambiati a piacimento e l'array stesso può vedere modificate le proprie dimensioni senza alcun problema. Gli array in Ruby hanno indice iniziale 0 mentre l'ultimo sarà evidentemente n-1 dove n è il numero degli elementi che lo compongono. Questo tipo di struttura è molto duttile e potente e, spesso in congiunzione con le hash, che vedremo altrove, permette di creare e gestire facilmente complessi sistemi di dati. Li troverete in azione anche in altri relativi a Ruby, come ad esempio vedremo quando parleremo dei metodi. In questo paragrafo tratteremo per quanto possibile in maniera approfondita gli array ma solo l'uso quotidiano vi potrà dare una formazione completa su questo potente strumento dalle sfaccettature così numerose che è impossibile descrivere tutte in questo paragrafo.

Formalmente si ha:

[elemento-1, elemento-2, ... , elemento-n]

ovvero abbiamo una sequenza di elementi compresa all'interno di una coppia di parentesi quadrate con ciascun elemento separato dagli altri tramite virgola.

ar1 = ["uno", 2, 3.0, [4]]

Questo è un array perfettamente valido che contiene una stringa, un intero, un float e un altro array annidato. Ma potrebbero starci dentro anche altri tipi di elementi ivi comprese funzioni o istanze di classi create dall'utente ecc.... il tutto con la massima libertà.

irb(main):070:0> ar1 = ["uno", 2, 3.0, [4]]
=> ["uno", 2, 3.0, [4]]
irb(main):071:0> ar1.class
=> Array
irb(main):072:0> ar1[1].class
=> Fixnum
irb(main):073:0> ar1[3].class
=> Array

Il metodo class ci permette di risalire alla natura di un qualsiasi oggetto. In questo semplice esempio avrete già notato una cosa interessante:
l'accesso al singolo elemento di un array avviene tramite l'operatore
[], come per le stringhe.

La creazione di un array può avvenire in vari modi. La più comune è quella appena vista, con le parentesi quadrate che racchiudono una lista di elementi da noi predefiniti e noto. Tuttavia spesso non conosciamo quali e quanti elementi immettere; è allora possibile usare l'operatore new per creare una istanza della classe Array, in pure stile object oriented:

ar1 = Array.new

che crea un array vuoto. La stessa cosa si ottiene scrivendo:

ar1 = []

Se vogliamo è possibile predefinire anche una dimensione iniziale, sapendo che tutti gli elementi saranno nil.

irb(main):011:0> ar1 = Array.new(3)
=> [nil, nil, nil]

oppure con il formato Array.new(n, elemento):

irb(main):016:0> x = Array.new(5,0)
=> [0, 0, 0, 0, 0]

Quest'ultima forma molto comoda per evitare di avere dei valori nil creando una inizializzazione. Quando gli elementi sono tanti ritengo che apprezzerete questo sistema. Al posto dell'intero si può mettere quel che si vuole naturalmente anche un altro array, per fare un esempio appena più complesso.

irb(main):017:0> y = Array.new(5, [1,1])
=> [[1, 1], [1, 1], [1, 1], [1, 1], [1, 1]]

L'indicizzazione come detto parte da 0 e arriva ad n-1 con n uguale al numero di elementi. Sono ammessi peraltro come indici anche i numeri negativi con -1 che si presume sia l'ultimo elemento (da destra a sinistra, dal momento che costruiamo l'array analogamente a come si legge) -2 il penultimo e così via.

irb(main):007:0> ar3 = [0,1,2,3,4]
=> [0, 1, 2, 3, 4]
irb(main):008:0> ar3[1]
=> 1
irb(main):009:0> ar3[-1]
=> 4

Insomma una situazione del tutto analoga a quella vista per le stringhe. Un'altra analogia che gli array hanno con le strignhe è la possibilità di avere un'altra notazione. In pratica è possibile usare %w o %W come prefisso per l'array; in questo caso gli elementi possono essere indicati senza alcun separatore a parte uno spazio; attenzione che tutti gli item sono convertiti, se possibile,  in formato stringa:

irb(main):012:0> a = %w[1 2 3 4]
=> ["1", "2", "3", "4"]
irb(main):015:0> d = %w[1 2 3+3]
=> ["1", "2", "3+3"]

personalmente preferisco la notazione classica e quella userò nel prosieguo.

Abbiamo visto quindi come creare gli array e come accedere ad un singolo elemento in esso contenuto. Ora è il momento di approfondire questo discorso. Teniamo presente che, come per le stringhe, anche gli array hanno una grande quantità di metodi che permettono di manipolarli in ogno modo. Qui presenteremo quelli che, a mio avviso, sono quelli più utili per l'uso quotidiano. Per alcuni di essi ricordo che esiste il corripondente nomemetodo! (ovvero col punto esclamativo a suffisso) che ha effetti distruttivi sull'array su cui è applicato.

Prendiamo un semplice array:

ar = [0,1,2,3,4,5]

La lunghezza di un  array, intesa come numero di elementi che lo compone si ottiene, scommetto che lo immaginate, attraverso il metodo length. Se vi piace di più potete usare anche il sinonimo size.

irb(main):007:0> ar1.length
=> 6

Sapere la lunghezza di un array è importante anche perchè consente di effettuare operazioni in sicurezza. Ad esempio come si potrebbe accedere all'ultimo elemento di un array se non sappiamo quanto è lungo? Leggete quanto segue.
L'accesso al singolo elemento, ovviamente, è un semplice esercizio basato sugli indici e può essere eseguito anche utilizzando indici negativi, per quanto non sia certo il metodo standard. Il già citato protagonista è l'operatore [], già autentica star quando si è parlato di stringhe.

irb(main):003:0> ar1[0]
=> 0
irb(main):004:0> ar1[-1]
=> 5

insomma in questo è la stessa cosa che vale per le stringhe e che abbiamo visto nel paragrafo ad esse dedicato. Andare al di là del range esistente degli indici ci farà ottenere il solito nil come risposta. In relazione al quanto visto per la rilevazione della lunghezza di un array accedere all'ultimo elemento di un array di lunghezza ignota può essere fatto semplicemente in un'unica passata come segue:

ar[ar1.size -1]

il -1 uno è necessario in quanto gli indici, come detto, iniziano da 0, non da 1. Ok, lo so, esiste anche il metodo last, che restituisce (è readonly, cioè non può essere utilizzato per eliminare) l'ultimo elemento ma in questo modo abbiamo visto come usare lenght (o size) per qualcosa che non sia solo misurare l'array :-). Di contro esiste anche il metodo first che restituisce il primo elemento dell'array.

L'operatore [] è però utilizzabile, come intuirete, anche con un'indicizzazione un po' più sofisticata. In particolare nei due modi seguenti:

[indice di partenza, numero di elementi]
[indice di partenza .. indice finale]

irb(main):022:0> aa = [0,1,2,3,4,5,6,7]
=> [0, 1, 2, 3, 4, 5, 6, 7]
irb(main):023:0> bb = aa[1,4]
=> [1, 2, 3, 4]
irb(main):024:0> cc = aa[1 .. 4]
=> [1, 2, 3, 4]

In questo modo abbiamo creato due nuovi array che contengono una parte degli elementi dell'array aa.
Attenzione che l'uso distratto di questo operatore può dare origine a dei piccoli (o grandi...) disastri; supponendo di usare ancora l'array aa come definito qui sopra potrebbe succedere una cosa di questo tipo:

 irb(main):025:0> aa[1,4] = 9
=> 4
irb(main):026:0> aa
=> [0, 9, 5, 6, 7]

come vedete i 4 elementi dall'indice 1 al 4 sono stati sostituiti dal numero 9.

Conseguentemente all'esempio precedente è facile intuire che, in generale, per rimpiazzare un valore con un altro basta effettuare una assegnazione nella posizione che vogliamo con l'operatore = come nell'esempio che segue:

irb(main):011:0> ar1[2] = 9
irb(main):012:0> ar1
=> [0, 1, 9, 3, 4, 5]

Se assegniamo un valore con un indice superiore e non contiguo ecco cosa otteniamo:

irb(main):013:0> ar1[9] = 9
=> 9
=> [0, 1, 9, 3, 4, 5, nil, nil, nil, 9]

L'array si è automaticamente ridimensionato al fine di alloggiare il nuovo valore nella posizione desiderata; ecco un bell'esempio di dinamismo. Se invece andate oltre gli indici permessi ma dalla parte opposta l'elemento verrà messo in prima posizione senza sovrapporsi a quello già esistente ma facendolo shiftare in seconda posizione.

irb(main):015:0> ar1[-10] = 7
=> 7
irb(main):016:0> ar1
=> [7, 1, 9, 3, 4, 5, nil, nil, nil, 9]

Accodare un elemento ad un array è una cosa molto semplice, lo vediamo con esempio che parte da un array vuoto usando l'operatore <<:

 irb(main):025:0> arr = []
=> []
irb(main):025:0> arr << "a"
=> ["a"]
irb(main):026:0> arr << "b"
=> ["a", "b"]
irb(main):027:0> arr << "c"
=> ["a", "b", "c"]

Teniamo presente che è possibile creare delle vere e proprie catene con questo operatore:

irb(main):030:0> arr1 = [1,2]
=> [1, 2]
irb(main):031:0> arr1 << 3 << 4 << [5,6,7]
=> [1, 2, 3, 4, [5, 6, 7]]

legato a questo, forse più generale come valenza, abbiamo comunque il metodo "parlante" concat:

irb(main):076:0> a1 = [0,1]
=> [0, 1]
irb(main):077:0> a2 = [2,3]
=> [2, 3]
irb(main):078:0> a1.concat(a2)
=> [0, 1, 2, 3]
irb(main):079:0> a1
=> [0, 1, 2, 3]

Analogo è il metodo push, che fa le stesse cose.
Invece, per togliere un elemento dalla coda si può ricorrere al metodo pop:

irb(main):028:0> arr.pop
=> "c"
irb(main):029:0> arr
=> ["a", "b"]

Per inserire un elemento all'inizio o in qualsiasi altra posizione il mio consiglio è quello di ricorrere al comodo metodo insert:

irb(main):034:0> a = [1,2,3,4]
=> [1, 2, 3, 4]
irb(main):035:0> a.insert(0,0)
=> [0, 1, 2, 3, 4]
irb(main):036:0> a.insert(3,33)
=> [0, 1, 2, 33, 3, 4]

Come si vede il primo intervento di insert permette di porre il valore 0 (che ovviamente potrebbe essere qualsiasi altra cosa vogliate, ricordiamocelo) all'indice 0 con conseguente spostamento di 1 all'indice successivo. La seconda chiamata di insert mette il numero 33 all'indce 3 dove prima c'era il numero 3 che è finito all'indice 4. Se però volete qualche cosa che sia specifico per la prima posizione di un array (insert è generico) allora potete usare unshift per inserire un elemento all'inizio e shift per toglierlo.

irb(main):037:0> wre =[1,2,3,4]
=> [1, 2, 3, 4]
irb(main):038:0> wre.unshift(12)
=> [12, 1, 2, 3, 4]
irb(main):039:0> wre.shift
=> 12
irb(main):040:0> wre
=> [1, 2, 3, 4]

Per trovare un elemento abbiamo il metodo index.

irb(main):037:0> ar = [0,1,2,3,4,5]
=> [0, 1, 2, 3, 4, 5]
irb(main):038:0> ar.index(5)
=> 5
irb(main):039:0> ar.index(8)
=> nil

Come è evidente dal semplice esempio esso ci restituisce l'indice dell'elemento cercato se è presente oppure nil in caso esso non ci sia. Il numero 8 infatti non è presente nelle'array ar. Va specificato che  index restituisce l'indice della prima occorrenza dell'elemento cercato; quindi potrebbero essrcene altri aventi indice più grande. In maniera simile e per capire quanto elementi uguali ci sono nell'array si potrebbe usare count che, ovviamente restituisce il numero di elementi uguali a quello cercato.

irb(main):040:0> b = [1,2,3,3,3,4,5]
=> [1, 2, 3, 3, 3, 4, 5]
irb(main):041:0> b.index(3)
=> 2
irb(main):042:0> b.count(3)
=> 3

Segnalo anche il metodo take che vi restituisce i primi n (nell'esempio seguente sono 2) elementi da voi indicati.

irb(main):080:0> ["a", "b", "c", "d"].take(2)
=> ["a", "b"]

Anche la cancellazione di uno o più elementi è una cosa molto semplice. La funzione più semplice è delete_at(indice) che, come indica chiaramente il nome, cancella l'elemento, e solo quello, presente ad un certo indice da noi stabilito. In questo modo si può cancellare un solo elemento.

irb(main):047:0> arr = [0,1,2,3,3,3,4,4,5]
=> [0, 1, 2, 3, 3, 3, 4, 4, 5]
irb(main):048:0> arr.delete_at(4)
=> 3
irb(main):049:0> arr
=> [0, 1, 2, 3, 3, 4, 4, 5]

il solito "nil" sarà il risultato se cercare di cancellare un elemento ad un indice fuori range.

Possiamo invece cancellare tutti gli elementi uguali tra di loro in un array abbiamo a disposizione il semplice delete(elemento):

irb(main):051:0> bb = [1,2,3,3,5,6,7,8,3,4,3,3,9]
=> [1, 2, 3, 3, 5, 6, 7, 8, 3, 4, 3, 3, 9]
irb(main):052:0> bb.delete(3)
=> 3
irb(main):053:0> bb
==> [1, 2, 5, 6, 7, 8, 4, 9]

Come si vede sono scomparsi tutti i "3" dall'array bb. Anche in questo caso otterrete "nil" se farete riferimento ad un elemento non esistente.

Il metodo clear permette invece di fare piazza pulita all'interno dell'array:

[1,2,3,4].clear
=> []

Un'altra istruzione tipo utility è max che restituisce l'elemento massimo di un array; evidentemente molto comodo sugli elementi numerici funziona comunque su tutti i tipi di array dal momento che per il metodo si tratta pur sempre di un confronto tra oggetti:

 irb(main):001:0> x = [1,2,3,4]
=> [1, 2, 3, 4]
irb(main):002:0> x.max
=> 4
irb(main):003:0> y = ["pippo", "poppo", "rrrrr"]
=> ["pippo", "poppo", "rrrrr"]
irb(main):004:0> y.max
=> "rrrrr"

Creare invece una copia dell'array è molto facile con l'operatore = ma attenzione:

irb(main):087:0> ar2 = ar1
=> [0, 1, 2, 3]
irb(main):088:0> ar2
=> [0, 1, 2, 3]
irb(main):089:0> ar1[0] = 9
=> 9
irb(main):090:0> ar1
=> [9, 1, 2, 3]
irb(main):091:0> ar2
=> [9, 1, 2, 3]

avete capito? Come avevamo accennato per le stringhe con il nostro = abbiamo si creato una copia ma essa vive di vita non indipendente tanto è che un cambiamento indotto in ar1 ha avuto effetti anche in ar2. ar1 ed ar2 puntano alla stessa zona di memoria. Si ripropone il solito problema deep copy vs shallow copy (quest'ultima è quella che si è verificata). Lo stesso accade lavorando su ar2; i frutti della elaborazione si riverseranno anche su ar1. Per risolvere il problema senza dover ricorrere a cicli e loop di copia si può usare il metodo dup (generico, non è riferito solo agli array), come avevamo già visto parlando delle stringhe e l'esempio seguente ne dimostra la validità:

irb(main):094:0> arr1 = [1,2,3,4]
=> [1, 2, 3, 4]
irb(main):095:0> arr2 = arr1.dup
=> [1, 2, 3, 4]
irb(main):096:0> arr1
=> [1, 2, 3, 4]
irb(main):097:0> arr2
=> [1, 2, 3, 4]
irb(main):098:0> arr1[0] = 55
=> 55
irb(main):099:0> arr1
=> [55, 2, 3, 4]
irb(main):100:0> arr2
=> [1, 2, 3, 4]

Se volete ottenere lo stesso risultato con qualcosa più array oriented allora potete anche scrivere:

arr2 = Array.new(arr1)

provate e vedrete che è la stessa cosa, in pratica.

Interessante è l'uso di + e - per compiere operazioni sugli array. Il primo simbolo appende il secondo al primo, il "meno" sottrae gli elementi comuni. Per chiarire:

irb(main):001:0> a = [0,1,2,3]
=> [0, 1, 2, 3]
irb(main):002:0> b = [4,5,6]
=> [4, 5, 6]
irb(main):003:0> c = a + b
=> [0, 1, 2, 3, 4, 5, 6]
irb(main):004:0> d = [1, 6]
=> [1, 6]
irb(main):005:0> e = c - d
=> [0, 2, 3, 4, 5]

Se cercate di sottrarre elementi che non sono presenti nell'array semplicemente non succede nulla.

Segnalo ancora due utili metodi: sort e reverse che, rispettivamente, ordinano in modo crescente gli elementi di un array (sort) e invertono l'ordine degli stessi gli stessi (reverse). Nell'esempio usiamo le versioni col ! finale che hanno effetto sull'array in questione cambiandolo definitvamente:

irb(main):081:0> d = [2,5,3,7,9,0,6,4,1,2]
=> [2, 5, 3, 7, 9, 0, 6, 4, 1, 2]
irb(main):084:0> d.sort!
=> [0, 1, 2, 2, 3, 4, 5, 6, 7, 9]
irb(main):085:0> d.reverse!
=> [9, 7, 6, 5, 4, 3, 2, 2, 1, 0]

Tra le features a corredo forse non fondamentali ma senz'altro utili abbiamo anche il metodo join che in presenza di un array composto di sole stringhe permette di riunirle tutte in una sola. L'eventuale elemento in cui reversiamo il risultato di join è infatti proprio una stringa.

irb(main):043:0> ars = ["Hello", "," , " World", "!"]
=> ["Hello", ",", " World", "!"]
irb(main):044:0> s = ars.join
=> "Hello, World!"
irb(main):045:0> s.class
=> String

Vi ricordate il metodo split applicato sulle stringhe? Esso fa la cosa opposta spezzando la stringa su cui è applicato in un certo numero di elementi di un array.

ARRAY MULTIDIMENSIONALI

Questione di lana caprina secondo me.... Il discorso andrebbe approfondito ma, fermo restando che è sempre possibile costruire un array di array

ar = [[1,2,3],[4,5,6]]
ar[0][0]          #1
ar[1][0]          #4

che non è la stessa cosa, a mio avviso mentre secondo altri si, possiamo risolvere la questione in due modi:

1) attraverso la classe matrice, installata tramite la libreria standard

2) installando la gemma narray

Rimando alla documentazione relativa per gli approfondimenti del caso.

ITERAZIONI SUGLI ARRAY

Discorso ovviamente molto interessante ed altrettanto importante. Lo approfondiamo in un capitolo apposito.