Archivi tag: array

Da una tabella CSV agli array

Una volta capito come leggere con Processing i dati contenuti in un file CSV, il passaggio successivo è rendere questi dati leggibili e modificabili facilmente all’interno del programma convertendoli in variabili e array.

Ripartiamo dal nostro esempio precedente utilizzando sempre lo stesso data set:

/*
 * Leggere file CSV
 * Federico Pepe, 25.03.2018
 * http://blog.federicopepe.com/processing
 */

Table csv;

void setup() {
  csv = loadTable("data.csv", "header");

  println("Numero righe: " + csv.getRowCount());
  println("Numero colonne: " + csv.getColumnCount());
  
  for(int i = 0; i < csv.getRowCount(); i++) {
    println(csv.getFloat(i, 2));
  }
  
  noLoop();
}

void draw() {
}

Prima di procedere, assicuriamoci che il programma funzioni cliccando su run: nella console dovrebbero comparire i dati contenuti nella colonna relativa al 2009.

Utilizzare i metodi corretti per leggere i dati

Un aspetto importante da tenere presente è di utilizzare i metodi corretti per accedere ai dati. Spulciando nel reference di Table noterete che esistono diverse funzioni come .getFloat().getInt().getString().

Se sostituiamo nell’esempio precedente la riga println(csv.getFloat(i, 2)); con println(csv.getString(i, 2)); Processing non genererà nessun errore e continuerà a far girare il nostro programma ma, ora, quei valori sono considerati stringhe (quindi testo) e non più numeri.

Tutti questi metodi accettano due parametri: il primo indica la riga della tabella, il secondo la colonna. Per quest’ultimo possiamo usare sia un numero, partendo, come sempre a contare da 0, oppure una stringa contenente il nome della colonna.

Il codice, può essere sostituito con: println(csv.getFloat(i, "2009"));

Se sostituite sempre la stessa riga con println(csv.getFloat(i, "Descrizione")); la console vi restituirà tutti valori NaN ovvero Not a Number. Come dicevo, il programma continuerà a funzionare ma non in modo corretto.

Dal tabella CSV all’array

Passare tutti i dati in un array può essere molto comodo per utilizzare alcune funzioni specifiche di calcolo, come, ad esempio, min()max() che restituiscono, rispettivamente, il valore minimo e massimo di un array.

Anche se in questo momento siamo ancora lontani dall’idea di creare una visualizzazione di dati, dovremmo comunque cominciare a pensare a come utilizzeremo questi numeri.

Per come è stato strutturato il file CSV, ciascuna colonna rappresenta un anno con valori di vario tipo: temperatura minima, temperatura massima, eccetera; ma se noi volessimo rappresentare la variazione di uno stesso valore nel tempo dovremmo lavorare orizzontalmente e non verticalmente.

Sfruttiamo questo esempio per capire come passare i dati dal CSV a un array:

Creiamo un array di tipo float chiamato tempMin nel quale inseriremo tutti i valori di temperatura minima e modifichiamo il nostro ciclo for per girare non più sul numero di righe ma su quello delle colonne. Impostiamo l’inizio del ciclo for a 1 per saltare la prima colonna.

/*
 * Da una tabella CSV agli array
 * Federico Pepe, 01.04.2018
 * http://blog.federicopepe.com/processing
 */

Table csv;

float tempMin[];

void setup() {
  csv = loadTable("data.csv", "header");

  println("Numero righe: " + csv.getRowCount());
  println("Numero colonne: " + csv.getColumnCount());
  
  tempMin = new float[csv.getColumnCount()];
    
  for(int i = 1; i < csv.getColumnCount(); i++) {
    tempMin[i] = csv.getFloat(0, i);
  }
  
  printArray(tempMin);
  
  noLoop();
}

void draw() {
}

La dimensione dell’array è uguale al numero di colonne all’interno del file tempMin = new float[csv.getColumnCount()]; e inseriamo all’interno dell’array i valori float provenienti dalla riga 0, perché stiamo ignorando l’header, e di ciascuna colonna: tempMin[i] = csv.getFloat(0, i);

Dal risultato in console notiamo subito un problema: il primo valore dell’array è 0.0 perché, effettivamente, l’array contiene un valore in più, quello della colonna Descrizione.

Abbiamo due possibilità per risolvere il problema:

Modificare la grandezza dell’array sottraendo 1: tempMin = new float[csv.getColumnCount()-1]; e modificando l’inserimento dei valori nell’array sempre spostando l’indice indietro di 1 tempMin[i-1] = csv.getFloat(0, i);.

Questa soluzione funziona ma non è molto elegante, meglio cambiare il codice come segue: inizializziamo l’array con grandezza pari a 0: tempMin = new float[0]; e poi utilizziamo la funzione append() che espande l’array di un elemento e aggiunge il dato nella nuova posizione tempMin = append(tempMin, csv.getFloat(0, i));

Il codice completo

/*
 * Da una tabella CSV agli array
 * Federico Pepe, 01.04.2018
 * http://blog.federicopepe.com/processing
 */

Table csv;

float tempMin[];

void setup() {
  csv = loadTable("data.csv", "header");

  println("Numero righe: " + csv.getRowCount());
  println("Numero colonne: " + csv.getColumnCount());
  
  tempMin = new float[0];
    
  for(int i = 1; i < csv.getColumnCount(); i++) {
    tempMin = append(tempMin, csv.getFloat(0, i));
  }
  
  printArray(tempMin);
  
  noLoop();
}

void draw() {
}

Ora l’array è corretto e contiene esattamente tutti i valori previsti. Come dicevo, ora possiamo sfruttare l’array per ottenere il valore minimo e quello massimo molto semplicemente:

println("Il valore minimo è: " + min(tempMin));
println("Il valore massimo è: " + max(tempMin));

Array bidimensionali in Processing

In programmazione gli Array sono utili per archiviare in modo ordinato una serie di informazioni. Per chi avesse bisogno di un recap, questo è l’articolo in cui ho introdotto il concetto di array e il loro utilizzo in Processing.

A volte può capitare di avere a che fare con informazioni che non possono essere rappresentate in un’unica dimensione. Per questo motivo abbiamo bisogno di una struttura di dati multidimensionale: un array bidimensionale, ovvero un array di array, ci permette di lavorare con facilità su dati di questo tipo.

Per fare un esempio, proviamo a pensare di rappresentare i quadrati neri e bianchi di una scacchiera utilizzando il valore 0 per i primi e 1 per i secondi.

Partiamo da un array monodimensionale:

int[] scacchiera = { 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1 };

Con questo array ho rappresentato tutti e 64 i tasselli ma non è chiara la struttura a righe e colonne tipica della scacchiera. Ecco che, invece, inserendo ciascuna riga in un array separato le cose migliorano sensibilmente:

int[][] scacchiera = { 
 {1, 0, 1, 0, 1, 0, 1, 0},
 {0, 1, 0, 1, 0, 1, 0, 1},
 {1, 0, 1, 0, 1, 0, 1, 0},
 {0, 1, 0, 1, 0, 1, 0, 1},
 {1, 0, 1, 0, 1, 0, 1, 0},
 {0, 1, 0, 1, 0, 1, 0, 1},
 {1, 0, 1, 0, 1, 0, 1, 0},
 {0, 1, 0, 1, 0, 1, 0, 1} };

Abbiamo costruito il nostro primo array bidimensionale.

Dichiarazione e inizializzazione

Le regole da rispettare in merito alla dichiarazione e inizializzazione dell’array bidimensionale rimangono identiche rispetto a quanto già visto in passato: è necessario indicare il tipo di dato, il nome dell’array ed è fondamentale stabilire una grandezza iniziale.

Sempre prendendo come riferimento l’esempio della scacchiera, inizializziamo l’array bidimensionale con 8 valori per le righe e 8 per le colonne:

int[][] scacchiera = new int[8][8];

Array bidimensionali e nested loop

I loop annidati sono il modo migliore per accedere a tutti i dati presenti in un array bidimensionale. Proviamo a disegnare la nostra scacchiera:

/*
 * Array bidimensionali e scacchi
 * Federico Pepe, 19.02.2017
 * http://blog.federicopepe.com/processing
*/

int[][] scacchiera = { 
  {1, 0, 1, 0, 1, 0, 1, 0}, 
  {0, 1, 0, 1, 0, 1, 0, 1}, 
  {1, 0, 1, 0, 1, 0, 1, 0}, 
  {0, 1, 0, 1, 0, 1, 0, 1}, 
  {1, 0, 1, 0, 1, 0, 1, 0}, 
  {0, 1, 0, 1, 0, 1, 0, 1}, 
  {1, 0, 1, 0, 1, 0, 1, 0}, 
  {0, 1, 0, 1, 0, 1, 0, 1} };

void setup() {
  size(480, 480);
  
  int rows = 8;
  int cols = 8;
  
  for (int i = 0; i < rows; i++) {
    for(int j = 0; j < cols; j++) {
      if(scacchiera[i][j] == 0) {
        fill(0);
      } else {
        fill(255);
      }
      rect(i*60, j*60, 60, 60);
    }
  }
}

void draw() {
}

All'inizio del programma troviamo l'array bidimensionale che contiene i valori su cui dobbiamo lavorare. All'interno della funzione setup() abbiamo impostato la grandezza della finestra e abbiamo creato due variabili per avere il numero di righe (rows) e il numero di colonne (cols).

Utilizzando due loop andiamo a vedere tutti i valori presenti all'interno dell'array: se il valore trovato è uguale a 0 la casella sarà nera, altrimenti bianca. Completiamo il programma andando effettivamente a disegnare la singola casella.

Esercizio di difficoltà media

In questo script ci sono diversi parametri "hard-coded". Siete in grado di modificare lo script affinché, ad esempio, modificando i valori all'interno di size() mi venga disegnata sempre una scacchiera di 8x8 che occupi l'intera grandezza della finestra (anche quando non è quadrata)?

Esercizio di difficoltà alta

Partendo dalla soluzione all'esercizio di difficoltà media, sareste in grado di modificarlo ulteriormente per fare in modo che la griglia non sia necessariamente 8x8?

Array di oggetti: Bouncing Ball, parte 4

È finalmente arrivato il momento di mettere insieme quanto imparato nell’ultimo mese, programmazione ad oggettiarray, e di concludere il nostro esercizio Bouncing Ball. La domanda a cui oggi daremo, finalmente, risposta è sempre la stessa: come faccio a creare decine o centinaia di oggetti?

Un array di oggetti

Riprendiamo il codice dell’esercizio Bouncing Ball dove l’avevamo lasciato. Il primo passo è dichiarare un array di oggetti di tipo Ball.

La prima riga del nostro programma serviva per dichiarare una variabile di tipo Ball (il nostro oggetto) di nome myBall.

Ball myBall;

Sostituiamo questa riga di codice con un array di oggetti: per mantenere il mio codice chiaro e leggibile modifico anche il nome della variabile da myBall (che identifica un oggetto solo) a myBalls, utilizzando il plurale.

Ball[] myBalls;

Nella funzione setup() dove, precedentemente, inizializzavo l’oggetto ora devo dichiarare grandezza del mio array. Decido di avere a disposizione 100 oggetti:

myBalls = new Ball[100];

Non mi resta che inizializzare tutti gli oggetti richiamando il constructor all’interno della nostra classe, per sveltire, utilizzo un ciclo for:

for(int i = 0; i < myBalls.length; i++) {
 myBalls[i] = new Ball(random(width), random(height), random(0.5 ,5), random(0.5, 5));
 }

Debugging di un array di oggetti

Con l’ultima porzione di codice abbiamo riempito il nostro array occupando tutte e 100 le caselle con 100 oggetti differenti assegnando a ciascuno di essi, grazie alle funzioni random, la posizione X e Y e la velocità sull’asse orizzontale e verticale.

Se volessimo essere sicuri di averlo fatto nel modo corretto, potremmo pensare di utilizzare, come abbiamo sempre fatto finora, la funzione println(myBalls);

Debugging an Array in Processing

Come potete vedere nell’immagine, il testo riportato nella console non è per niente chiaro. Trattandosi di un array, proviamo a sostituire println con printArray:

Processing: using printArray

Un risultato leggermente migliore: vediamo che ci sono effettivamente 100 oggetti, numerati da 0 a 99, ma facciamo ancora fatica a capire se il contenuto del nostro array è corretto.

Accedere alle variabili interne agli oggetti

Nei nostri esercizi relativi agli oggetti abbiamo imparato a usare la dot notation per richiamare i metodi all’interno degli oggetti. La riga di codice myBall.display() serviva per richiamare la funzione display() contenuta nell’oggetto creato precedentemente.

Utilizzando lo stesso metodo possiamo accedere anche alle variabili interne all’oggetto: scrivendo, ad esempio, myBall.ellipseX; verrà restituito il valore di ellipseX.

Dal momento che, però, non stiamo lavorando con un singolo oggetto ma con un array abbiamo due possibilità:

  1. accedere ai dati di un oggetto specifico: println(myBalls[34].ellipseX);
  2. utilizzare la variabile contatore nel nostro ciclo for per visualizzarli tutti

Aggiungiamo questa riga di codice all’interno del nostro ciclo for:

println("["+i+"] - ellipseX: " + myBalls[i].ellipseX);

Ed ecco che il risultato è finalmente comprensibile:

Processing accessing variables in objects

Bouncing Balls, parte 4

Concludiamo modificando il codice contenuto in draw() per vedere tutte e cento le nostre sfere rimbalzare sullo schermo.

Ball[] myBalls;

void setup() {
  size(700, 500);
  myBalls = new Ball[100];
  for(int i = 0; i < myBalls.length; i++) {
    myBalls[i] = new Ball(random(width), random(height), random(0.5 ,5), random(0.5, 5));
  }  
}

void draw() {
  background(0);
  for(int i = 0; i < myBalls.length; i++) {
    myBalls[i].display();
    myBalls[i].move();
    myBalls[i].checkEdges();
  } 
}

Non ho incluso il codice della classe Ball perché non è stata toccata nemmeno una riga di codice. Se avete fatto tutto correttamente, il risultato sarà il seguente:

Bouncing Ball Final

Array: dichiarazione, inizializzazione e utilizzo

Avevo concluso l’ultimo post relativo alla programmazione orientata agli oggetti con una domanda: se volessimo creare decine o centinaia di oggetti? Il copia incolla non sarebbe certamente la scelta più consona.

Abbiamo capito già da tempo che uno dei principi di base della programmazione è scrivere meno righe di codice possibile per ottenere il risultato sperato. Introduciamo oggi il concetto di array.

Cos’è un Array?

Nel post dedicato alle variabili abbiamo parlato di come dichiararle e inizializzarle ma non ci siamo mai soffermati su una questione molto importante: una variabile può contenere un solo valore.

È vero che questo valore può variare in base all’andamento del nostro programma ma non è possibile, ad esempio, che una variabile di tipo integer possa valere contemporaneamente 5 e 10.

Gli array risolvono questo problema perché rappresentano una lista di valori: all’interno di un array io posso inserire una serie di valori, purché siano tutti dello stesso data type. Un altro importante vantaggio è che ciascun elemento ha un ordine preciso all’interno della lista che è identificato da un indice univoco.

È fondamentale ricordare che l’indice parte sempre da di conseguenza se sto utilizzando un array che contiene 10 elementi, l’ultimo valore avrà un indice pari a 9.

Ricapitolo per chiarezza i punti chiave:

  • Un array rappresenta una lista di elementi
  • Gli elementi all’interno dell’array devono essere tutti dello stesso tipo
  • Gli elementi all’interno dell’array hanno un ordine preciso
  • Ciascun elemento è identificato da un indice univoco che parte sempre da 0

Dichiarazione e creazione di un array

La sintassi per dichiarare un array è molto semplice: rispetto a quella utilizzata per le variabili, dobbiamo solo aggiungere delle parentesi quadre:

int[] myArray;

Con questa riga di codice ho dichiarato un array di nome myArray che conterrà al suo interno una lista di valori di tipo integer. Non ho ancora detto al mio programma quanti elementi conterrà questo array e, di conseguenza, la lista è ancora vuota.

Passiamo all’inizializzazione di myArray:

int[] myArray = new int[10];

Ho creato un’istanza di un nuovo array e ho gli ho assegnato una dimensione: la lista sarà composta da 10 elementi di tipo integer.

La grandezza dell’array può essere hard-coded come nell’esempio qui sopra, oppure può essere una variabile (di tipo integer) oppure un’espressione matematica che dia come risultato un integer:

// Un array di integer con grandezza hard-coded
int[] myArray = new int[10];

// Un array di float con grandezza passata tramite variabile (integer)
int sizeOfArray = 5;
float[] mySecondArray = new float[sizeOfArray];

// Un array di oggetti "Car" con grandezza passata tramite funzione
Car[] cars = new Car[2+5];

Inserire valori in un array

Non ci resta che cominciare a riempire il nostro array di informazioni.

Una prima opzione è quella di inserire le variabili specificando la posizione indice di riferimento. Questa scelta, ovviamente, non è la più funzionale.

// Dichiaro e inizializzo il mio array che conterrà 5 elementi di tipo integer
int[] myArray = new int[5];

myArray[0] = 5;   // Il primo elemento (indice = 0) sarà: 5
myArray[1] = 10;  // Il secondo elemento (indice = 1) sarà: 10
myArray[2] = 3;   // Il terzo elemento (indice = 2) sarà: 3
myArray[3] = 0;   // Il quarto elemento (indice = 3) sarà: 0
myArray[4] = 340; // Il quinto elemento (indice = 4) sarà: 340

Una seconda opzione è quella di inserire tutti i valori, separati da virgole, all’interno di parentesi graffe:

// Dichiaro un array di tipo integer di nome myArray con all'interno i seguenti valori: 5, 10, 3, 0, 340

int[] myArray = {5, 10, 3, 0, 340};

Le due porzioni di codice qui sopra rappresentato, di fatto, la stessa identica lista.

Operazioni matematiche con gli array

Per capire se tutto è chiaro, proviamo ad analizzare un problema: se volessimo creare una lista di 500 elementi contenenti numeri random compresi tra 0 e 255?

Per prima cosa non dobbiamo dimenticare che la funzione random() restituisce valori di tipo float. Avendo già deciso che la nostra lista sarà composta da 500 elementi, possiamo anche inizializzare il nostro array:

float valori[] = new float[500];

Ora dobbiamo inserire tutti i valori; farlo uno ad uno sarebbe un massacro ma, per fortuna, abbiamo già studiato i cicli while e for il cui scopo è effettuare operazioni ripetitive.

Per comodità utilizzerò un ciclo for: posso usare la variabile del ciclo per indicare l’indice dell’array nel quale inserire il valore random:

float valori[] = new float[500];

for(int i = 0; i < 500; i++) {
  valori[i] = random(255);
}

printArray(valori);

Array e ciclo for

Come si può notare nell’immagine, grazie alla funzione printArray nella console visualizzo tutti i valori e il relativo indice all’interno dell’array.

Ovviamente è possibile accedere singolarmente a qualsiasi elemento indicando l’indice:

println("Il valore all'indice 306 è: "+ valori[306]);

Debugging degli array e .length

Un errore in cui si può incorrere frequentemente quando si lavora con gli array è: ArrayIndexOutOfBoundsExceptions. Tale problema si verifica quando chiediamo al nostro programma di accedere a un valore ad un indice non presente nell’array.

Se modifichiamo il codice del ciclo for nel precedente esempio in questo modo:

for(int i = 0; i <= 500; i++)

riscontreremo proprio questo tipo di errore perché il ciclo si ripeterà finché il valore di i sarà uguale a 500. Il problema è che la grandezza dell’array che ho dichiarato all’inizio è pari a 500 ma, partendo da 0, l’ultimo valore dell’indice è 499.

Per ovviare a questo problema possiamo usare una proprietà degli array: .length. In pratica, anziché dover ricordarsi quant’è la lunghezza di ogni array e, prossimamente, vedremo che questa potrebbe anche variare, diciamo al programma di capire da sé quant’è la lunghezza della nostra lista e di comportarsi di conseguenza.

Ecco che il codice finito potrebbe essere:

float valori[] = new float[500];

for(int i = 0; i < valori.length; i++) {
  valori[i] = random(255);
}

printArray(valori);