({{}})

({{, ({{"Lezione 4"}, {"Strutture dati"}, {Crescenzio Gallo - Università di Foggia
crescenzio.gallo@unifg.it}})}})

Note:

Il materiale visualizzato durante questo seminario è disponibile per il download all'indirizzo http://www.crescenziogallo.it/unifg/seminario-mathematica-2014/

Il materiale utilizzato è tratto dai webinar pubblicati da Adalta e prodotti dal dott. Roberto Cavaliere (Mathematica Technical Sales Manager, r.cavaliere@adalta.it)

12-26 Giugno 2014

Agenda

Introduzione

Mathematica è un linguaggio

Mathematica è un linguaggio funzionale e simbolico

Funzioni e data set

Manipolazione dei dati

Manipolazione basata sulla posizione dei dati

Manipolazione basata sulla qualità dei dati

Manipolazione basata sulla struttura dei dati

Alcune macro

Conclusioni

Introduzione: Mathematica è un linguaggio

Poichè siamo abituati ad usare Mathematica attraverso il front-end, capita spesso di pensare di non considerare la vera natura di Mathematica ossia quella di linguaggio di programmazione.
Già nel fare un semplice calcolo e renderlo parametrico e farlo computare al variare del parametro in effetti abbiamo sfruttato la capacità di programmazione del linguaggio Mathematica:

Expand[((((((z+1))^2)+1))^2)]

Manipulate[Expand[((((((z+1))^2)+1))^n)],{n,2,10,1}]

Introduzione: Mathematica è un linguaggio funzionale e simbolico

Il principio di base di Mathematica è “Everything is an expression”.

Ad eccezione delle entità atomiche (numeri, stringhe, simboli), tutto viene convertito internamente in espressioni o nidificazioni di espressioni.

La manipolazione delle espressioni permette a Mathematica di eseguire calcoli simbolici, di modificare interattivamente grafici o strutture dati, di animare o consentire all'utente di manipolare oggetti attraverso semplici interfacce.
Vediamo come viene rappresentato internamente un numero razionale:

FullForm[(1/3)]

TreeForm[(1/3)]

Un binomio:

FullForm[a+b x]

TreeForm[a+b x]

Un grafico:

gr=Graphics[{Blue, Circle[{0,0}]}]

FullForm[gr]

TreeForm[gr]

Introduzione: Mathematica è un linguaggio funzionale e simbolico

Le implicazioni pratiche.

Qualsiasi espressione può essere inclusa in qualsiasi altra espressione.

Esempio 1

{X,Y}={Graphics3D[{Red,Sphere[]}],Graphics3D[{Yellow,Cylinder[]}]}

Queste due forme grafiche sono internamente rappresentate come qualsiasi altra espressione simbolica, pertanto possono essere usate esattamente come simboli:

Solve[{X+Y==2,Y-X==1}]

FullForm[X]

FullForm[Y]

Esempio 2

Gli elementi di interfaccia utente sono a loro volta rappresentati mediante espressioni e pertanto possono anche comparire direttamente dentro le righe di codice:

Plot3D[Sin[x+Cos[y]],{x,-4,4},{y,-4,4},PlotStyle->{Swatch,Specularity[Swatch,]},Axes->]

Esempio 3

Manipolare l’espressione di un grafico dopo averlo realizzato, ad esempio per ribaltare l’asse delle x rispetto a quello delle y:

Plot[Cos[x]+x,{x,0,10}]

s=FullGraphics[Plot[Cos[x]+x,{x,0,10}]]

ReplaceAll[s,{x_Real,y_Real}->{-x,y}]

Esempio4

Possiamo eseguire operazioni su liste di oggetti di qualsiasi natura, ad esempio contare le ricorrenze di ciascun elemento grafico in una lista di 20 elementi:

spheres=RandomChoice[{,,},20]

Tally[spheres]

Introduzione: funzioni e data set

Nel paradigma funzionale il modo più naturale per eseguire operazioni è quello di definire funzioni che operano su data set. 
Come visto nella slide precedente, in Mathematica i data set non sono altro che generiche espressioni, sebbene in molti casi pratici si lavora su liste.

gr=Plot[Cos[x],{x,-2,2}]

esp1=-4+2 x-3 (x^2)+2 (x^3)

list1={a,b,c,d}

Length[gr]

Length[esp1]

Length[list1]

Part[gr,2]

Part[esp1,2]

Part[list1,2]

FullForm[gr]

FullForm[esp1]

FullForm[list1]

Una cosa cui bisogna fare sempre attenzione: Mathematica gestisce tutti gli oggetti come strutture dati ancor prima di riconoscergli significati teorici. Questo significa che dobbiamo fare attenzione noi al significato dei risultati e non pensare che Mathematica faccia alcun tipo di “ragionamento” sui nostri dati a meno che non glielo chiediamo esplicitamente.

A=RandomInteger[{1,5},{5,5}];
MatrixForm[A]

B=RandomInteger[{1,5},{5,5}];
MatrixForm[B]

MatrixForm[(A/B)]

La divisione tra matrici non è una operazione definita, ma in questo caso Mathematica sta facendo un’operazione lecita ossia opera con A e B come strutture dati.
Dunque, in Mathematica qualsiasi cosa può rappresentare un data set.

Introduzione: funzioni e data set

Applicare funzioni su liste.

In Mathematica difficilmente si ricorre ai cicli del tipo For, While o Table per due motivi principali:
- molte funzioni native hanno uno speciale attributo chiamato Listable che gli consente di operare direttamente su liste oltre che su singoli valori;
- si usano operatori quali Apply, Map, Replace per applicare funzioni su interi data set.

Esempio 1: il calcolo di una funzione trigonometrica sui valori di un array.

Tutte le funzioni trigonometriche sono Listable

Attributes[Sin]

a=RandomReal[{0,100},{100000}];

(* approccio procedurale *)
res=Table[Sin[a[[i]]],{i,1,Length[a]}];

(* uso dell'attributo Listable *)
res1=Sin[a];

res===res1

Anche Power è Listable

Attributes[Power]

a={2,3,4,5,6,7,8,9,10}

(x^a)

Esempio 2: sommare gli elementi di una lista.

a=RandomReal[{0,100},{100000}];

(* approccio procedurale *)
res=0;
Table[res=res+a[[i]],{i,1,Length[a]}];

(* approccio funzionale *)
res1=Apply[Plus,a];

res

res1

Plus@@a

Misuriamo i tempi

AbsoluteTiming[res1=Apply[Plus,a];]

AbsoluteTiming[
res=0;
Table[res=res+a[[i]],{i,1,Length[a]}];]

Manipolazione dei dati: basata sulla posizione dei dati

Vediamo praticamente alcune operazioni sui dati, rimarcando ancora una volta che le stesse operazioni si possono eseguire su generiche espressioni oltre che su liste.

Clear[a]

lista = {simbolo, 2,3,"stringa",(x^2)-4==0,a,w,π,{0,-4}}

esp=-4+2 x-3 (x^2)+2 (x^3)

Join[lista,Reverse[lista]]

Join[esp,Reverse[esp]]

Proviamo a cambiare la Head di esp e capire cosa ha fatto la Join nel caso di espressioni:

esp1=Apply[pippo,esp]

Join[esp1,Reverse[esp1]]

Le funzioni che seguono operano sulle espressioni in base alla posizione degli elementi da manipolare:

(* elimina i primi tre elementi *)
Column[{Drop[lista,3],Drop[esp,3]}]

(* prende i primi tre elementi *)
Column[{Take[lista,3],Take[esp,3]}]

(* elimina il terzo elemento a partire dalla fine *)
Column[{Delete[lista, -3],Delete[esp, -3]}]

(* inserisce un elemento al terzo posto *)
Column[{Insert[lista, pippo, 3],Insert[esp, pippo, 3]}]

Manipolazione dei dati: basata sulla qualità dei dati

Se non conosco a priori la posizione dei dati nella struttura, ma ne conosco alcune proprietà posso usare l’istruzione Select.

La chiamata standard è Select[espressione, criterio].

lista = {simbolo, 2,3,"stringa",(x^2)-4==0,a,w,π,{0,-4}}

Select[lista,IntegerQ]

Select[lista,StringQ]

Select[lista,ListQ]

Un esempio più articolato

lista=RandomReal[{-5,5},{10000}];

res=Select[lista,#>0&];

Length[res]

(* approccio procedurale *)
res1={};
Table[If[lista[[i]]>0,AppendTo[res1,lista[[i]]]],{i,Length[lista]}];

res==res1

Misuriamo i tempi

AbsoluteTiming[
(* approccio procedurale *)
res1={};
Table[If[lista[[i]]>0,AppendTo[res1,lista[[i]]]],{i,Length[lista]}];]

(* approccio funzionale *)
AbsoluteTiming[res=Select[lista,#>0&];]

Dunque, la Select fa uso di predicati del tipo ListQ, IntegerQ, ecc.

??System`*Q

Oppure utilizza funzioni pure per definire una condizione da verificare per ciascun elemento da selezionare.

Manipolazione dei dati: basata sulla struttura dei dati

Un altro modo di selezionare elementi da una struttura è dato da Cases, la quale a differenza di Select permette di specificare un pattern e non un criterio.

La chiamata standard è Cases[espressione, pattern].

lista = RandomInteger[{-10,10},{100}]

Cases[lista,Except[0]]

(* tutti i numeri pari > 2 *)
Cases[lista,_?(#>2&&EvenQ[#]&)]

lista=RandomInteger[{0,1},{100,2}];

Cases[lista,{x_,x_}]

La Cases permette anche di specificare cosa fare quando si trova un elemento che soddisfa il pattern:

Cases[lista,{x_,x_}:>x]

Proviamo con l’approccio procedurale:

res={};
Table[If[lista[[i,1]]===lista[[i,2]],AppendTo[res,lista[[i,1]]]],{i,Length[lista]}];

Misuriamo i tempi

lista=RandomInteger[{0,1},{10^4,2}];

AbsoluteTiming[res=Cases[lista,{x_,x_}:>x];]

AbsoluteTiming[
res1={};
Table[If[lista[[i,1]]===lista[[i,2]],AppendTo[res1,lista[[i,1]]]],{i,Length[lista]}];]

res===res1

Manipolazione dei dati: alcune macro

La comodità di Mathematica è anche quella che dispone di numerose macro funzioni che eseguono operazioni anche complesse sui data set e dunque sulle liste. Vediamo alcune.

TakeWhile e DropWhile

Supponiamo da avere una lista di 100 date a partire dalla data 20 Ottobre 2011 e vogliamo selezionare solo le date fino ad un certo giorno, diciamo fino al 1° Gennaio 2012

(* Numero di secondi trascorsi dal 1 gennaio 1900 *)
AbsoluteTime[DateList[]]

date=Table[Take[DateList[AbsoluteTime[DateList[{2011,10,20}]]+86400*i],3],{i,1,100}]

TakeWhile[date,DateDifference[#,{2012,1,1}]>0&]

Come fare se vogliamo, invece, le date dal 1° Gennaio in poi?

Soluzione 1: invertiamo la lista di partenza e usiamo ancora TakeWhile con un criterio analogo (uso ≤ al posto di >)

TakeWhile[Reverse[date],DateDifference[#,{2012,1,1}]≤0&]

Oppure mi costruisco una funzione chiamata DropWhile (che non esiste). Prima devo capire come usare LengthWhile

(* calcolo la lunghezza della lista di elementi che soddisfano il criterio, a partire dal primo *)
LengthWhile[{1,2,3,4,5,6},#<4&]

Quindi dato un criterio, posso invocare LengthWhile per capire quanti elementi soddisfano il criterio e poi tagliare tali elementi

Drop[{1,2,3,4,5,6},LengthWhile[{1,2,3,4,5,6},#<4&]]

Proviamo sulle date

Drop[date,LengthWhile[date,(DateDifference[#,{2012,1,1}]≥0)&]]

Definiamo la funzione DropWhile per usarla in altri casi

DropWhile[data_List,crit_]:=Drop[data,LengthWhile[data,crit]];

DropWhile[date,(DateDifference[#,{2012,1,1}]>=0)&]

BinCount

Conta il numero di elementi inclusi in intervalli assegnati

punti =RandomReal[{-5,5},{1000,2}];

punti[[1;;10]]

BinCounts[punti]//MatrixForm

ArrayPlot[BinCounts[punti]]

ListPlot3D[BinCounts[punti],InterpolationOrder->0,Filling->Axis,Mesh->None,ImageSize->Large]

Tally

SetDirectory[NotebookDirectory[]]

Conta le frequenze di ciascun elemento della lista

testo=Import["testo.txt"];

StringTake[testo,191]

(* conta la frequenza di ogni singola lettera *)
Tally[Characters[testo]]

SortBy

Nell’esempio di prima proviamo a contare le frequenze delle parole e non delle lettere (WordCharacter.. indica un carattere ripetuto una o più volte).

words=ToLowerCase[StringCases[testo,WordCharacter..]];

Length[words]

Length[Union[words]]

Sort invocato su coppie di elementi utilizza il primo valore per eseguire l’ordinamento

res=Take[Sort[Tally[words]],-50]

SortBy permette di specificare quale elemento considerare per ordinare le coppie

res=Take[SortBy[Tally[words],Last],-50]

GatherBy

GatherBy permette di raggruppare elementi per i quali il risultato di una funzione assegnata restituisce uno stesso valore.

Automaticamente seleziona i dispari ed i pari:

dati=RandomInteger[{-5,5},{10}];

GatherBy[dati,EvenQ]

Se i dati sono strutturati in liste di liste, sarebbe più difficile ancora raggrupparli in funzione di una caratteristica dei sottoelementi:

dati=RandomInteger[{-5,5},{10,3}]

Raggruppa le terne che hanno il terzo elemento dispari:

GatherBy[dati,EvenQ[Last[#]]&]

oppure il secondo:

GatherBy[dati,EvenQ[#[[2]]]&]

Conclusioni

Nonostante Mathematica sia un linguaggio funzionale, di fatto mette a disposizione set di funzioni dedicati ai vari approcci di programmazione

Functional Programming
Procedural Programming
Rule-Based Programming
Graph Programming
CUDA Programming

Dunque un’altra delle sua caratteristiche è che si presenta come un linguaggio pluriparadigmatico.