GLI ALGORITMI: ANALISI E RAPPRESENTAZIONE DI UN PROBLEMA Che cos'è un algoritmo Il computer è una macchina creata per svolgere compiti e aiutare a risolvere problemi di vario tipo e complessità. Un problema è un "ostacolo" che rende difficile raggiungere un determinato obiettivo o soddisfare una certa esigenza. Per risolvere un problema generalmente sono necessarie delle informazioni iniziali, dei dati da analizzare e un procedimento di elaborazione che permette di produrre un risultato e, quindi, giungere a una soluzione. Il modo corretto di affrontare un problema è quello di farne dapprima un'analisi, individuare i dati iniziali ed elaborare un procedimento che permetta di combinare i dati a disposizione in modo da produrne di nuovi che rappresentano, dove possibile, la soluzione della questione. dati input procedimento dati output Per esempio, se l'obiettivo a cui occorre arrivare è la realizzazione di un dolce, dapprima valutiamo la ricetta che abbiamo a disposizione, controlliamo di avere tutti gli ingredienti, eventualmente acquistiamo quelli mancanti e attuiamo un procedimento che permetta di ottenere l'impasto necessario fino a ottenere il dolce finito. In un computer, il procedimento che viene attuato per risolvere un problema è detto programma: questo si fonda a sua volta su uno o più algoritmi, cioè l'insieme di istruzioni che permettono di giungere all'obiettivo prefissato attraverso un numero finito di passi. Il termine algoritmo deriva dalla trascrizione latina del nome del matematico persiano alKhwarizmi, considerato uno dei primi matematici ad aver teorizzato l'utilizzo di questa procedura. Per saperne di più SI CONOSCE POCO DELLA VITA DI MUHAMMAD IBN MUSA AL-KHWARIZMI, MATEMATICO E SCIENZIATO DI ORIGINE ARABA NATO PROBABILMENTE A BABILONIA INTORNO AL 780 DOPO CRISTO. FU RESPONSABILE DELLA BIBLIOTECA DI BAGHDAD E FECE TRADURRE MOLTE OPERE MATEMATICHE INDIANE E DEL PERIODO GRECO-ELLENISTICO. VIENE CONSIDERATO TRA I PADRI DELL'ALGEBRA, PAROLA DERIVANTE DALL' ARABO AI-JABR, CHE SIGNIFICA "COMPLETARE". LO STESSO TERMINE ALGORITMO DERIVA DALLA LATINIZZAZIONE DEL SUO NOME. Ma come si giunge a individuare l'algoritmo che permette di risolvere il problema di partenza? Bisogna innanzitutto analizzare il problema, cercando di eliminare i dettagli inutili e le ambiguità presenti; fatto ciò, si individuano i reali obiettivi del problema, si stila un elenco dei vincoli che la soluzione deve soddisfare e infine si valuta quali sono i dati di input da elaborare e il risultato che ne deriva. Poniamo il caso di un obiettivo relativamente semplice: preparare un pentolino di crema pasticciera. Le ambiguità di una richiesta così formulata sono evidenti: un pentolino quanto grande? Quanta crema dobbiamo preparare? Per risolvere la prima ambiguità si può decidere la quantità di crema che si vuole preparare, per esempio 500 gr. I vincoli riguardano il tipo di crema da preparare: trattandosi di crema pasticciera sono richiesti determinati ingredienti, che la differenziano da qualsiasi altro tipo di crema. I dati di input sono quindi gli ingredienti (proporzionati per la quantità di crema da produrre), il risultato atteso è la crema. In alternativa, se gli ingredienti sono insufficienti a produrre la quantità di crema desiderata, si possono adattare i dati in ingresso per produrne un quantitativo minore. Una volta risolte queste prime problematiche si passa all'individuazione della strategia risolutiva, progettando possibili soluzioni e individuando quella più efficiente, cioè quella che porta al risultato nel minor tempo possibile e con il migliore utilizzo delle risorse a disposizione. Nel nostro caso possiamo scomporre l'obiettivo finale in istruzioni più semplici che andranno a costituire quella che è la "ricetta" per realizzare il dolce. La soluzione al problema "preparare 500 gr. di crema pasticciera" si presenta quindi come una serie di istruzioni che porteranno all'obiettivo finale (output). Le istruzioni per preparare la ricetta sono un numero finito di passaggi elementari non ulteriormente scomponibili; ogni passaggio ha un tempo d'esecuzione determinato e finito, non ambiguo, e produce un risultato univoco. Queste caratteristiche corrispondono alle proprietà che un algoritmo deve avere per definirsi tale: Atomicità I passi che lo compongono devono essere elementari, ovvero non ulteriormente scomponibili Finitezza Il numero delle istruzioni che lo compongono deve essere finito, così come i dati in ingresso Non ambiguità I passi risolutivi devono poter essere interpretati in modo chiaro e univoco da chi esegue l'algoritmo, sia esso un uomo o una macchina Effettività L'esecuzione deve portare a un risultato univoco Terminazione L'esecuzione di ogni istruzione deve avere un termine dopo un tempo finito Realizzazione I passi devono essere eseguibili materialmente Se tutte le proprietà dell'algoritmo sono rispettate, il procedimento potrà essere ripetuto più volte ottenendo lo stesso risultato. Riassumendo, i punti fondamentali sui quali si fonda un algoritmo sono: 1 I dati su cui deve operare 2 La sequenza esatta delle azioni da eseguire 3 Il controllo dell'ordine con cui le azioni devono essere eseguite 4 L'azione di arresto In ambito informatico, una volta analizzato il problema e definito l'algoritmo di risoluzione si passa alla codifica delle istruzioni e alla realizzazione del programma che permetterà di applicare lo stesso algoritmo a differenti dati in input. Come si rappresenta un algoritmo: i diagrammi di flusso È possibile rappresentare un algoritmo in diversi modi, grafici o testuali. Un esempio di rappresentazione testuale di un algoritmo è la ricetta della crema pasticciera che abbiamo visto prima. Un altro modo di rappresentare gli algoritmi prevede di utilizzare delle convenzioni grafiche determinate che prendono il nome di diagrammi di flusso (flow chart). Ogni elemento del diagramma ha un significato ben preciso. Qualsiasi problema, sia esso numerico o non numerico, può essere rappresentato come un diagramma di flusso: l'importante è che sia un problema risolvibile con una procedura. A ogni forma dei blocchi che compongono i flow chart corrisponde un preciso significato. Ogni blocco è obbligatoriamente collegato a un altro da una freccia. Alcuni esempi di diagrammi: Dall’algoritmo al programma Un qualsiasi programma software è di fatto una sequenza ordinata di istruzioni (cioè un algoritmo) che a partire da dei dati in ingresso (input) restituisce dei risultati in uscita (output), in seguito all'elaborazione da parte di un computer. Come si fa a trasformare un algoritmo in un programma per computer? Bisogna innanzitutto scrivere le istruzioni in un linguaggio che sia comprensibile alla macchina: tali linguaggi si chiamano linguaggi di programmazione. Un linguaggio di programmazione è costituito, come ogni altro tipo di linguaggio, da un alfabeto di simboli, con cui vengono costruite le parole e le frasi, e da un insieme di regole sintattiche e lessicali per la costruzione e l'uso corretto delle parole e delle frasi del linguaggio. Esistono molti tipi di linguaggi di programmazione, che si sono evoluti nel tempo e variano in base alloro grado di complessità. Storia dei linguaggi di programmazione Per incontrare uno dei primi esempi di programma pensato per essere eseguito da una macchina dobbiamo andare indietro nel tempo fino all'inizio dell'Ottocento e guardare al lavoro di Ada Lovelace, che realizzò un algoritmo per la macchina analitica di Babbage, una grande macchina alimentata a vapore, molto simile ai telai per l'industria tessile, inventati proprio in quegli anni in Inghilterra, programmabile attraverso delle schede perforate e capace di svolgere le quattro operazioni matematiche, memorizzare i dati e produrre risultati. Ada Lovelace è considerata la prima programmatrice della storia. Il primo linguaggio di programmazione della storia è probabilmente il PlankalkU! di Konrad Zuse, sviluppato in Svizzera durante la seconda guerra mondiale e pubblicato nel 1946. Plankalkul non venne mai realmente usato per programmare. La programmazione dei primi elaboratori era realizzata in short code o in codice binario, da cui si è evoluto l'assembly, al quale faremo cenno nelle prossime pagine. La maggior parte dei linguaggi di programmazione successivi cercarono di rappresentare gli algoritmi di programmazione in maniera più leggibile per il programmatore. Tali linguaggi vengono definiti di alto livello. Tra i primi linguaggi ad alto livello ci fu il Fortran, creato nel 1957 da John Backus, da cui nacque il BASIC (1964). Compaiono in questi linguaggi il controllo delle condizioni (se succede qualcosa allora fai qualcos'altro), reso con l'istruzione IF, e nuove strutture di controllo di flusso come i cicli WHILE e FOR. In questo modo i salti nel codice quasi scompaiono (GOTO), rendendo più facile la manutenzione dei programmi. Con i primi mini e microcomputer e le ricerche nella Silicon Valley, nel 1983 viene inventato il Small-talk, capostipite dei linguaggi definiti object-oriented ("orientati agli oggetti"). La programmazione orientata agli oggetti raggruppa in un'unica entità (la classe) sia le strutture dati che le procedure che operano su di esse, creando per l'appunto un "oggetto". Esempi di linguaggi object-oriented odierni sono il C++ e Java, ideato nel 1995. I linguaggi di programmazione Il motore interno di un computer è la CPU, o processore, composta da unità di controllo e unità aritmetico-logica. All'interno della CPU sono contenuti milioni di transistor, che possiamo immaginare con tanti cancelli capaci di registrare solo due informazioni: aperto (1) o chiuso (O). Le istruzioni necessarie per far compiere delle azioni alla CPU sono scritte in linguaggio macchina, a sua volta costituito da un alfabeto, detto codice binario, composto da due sole lettere: O e 1. L'unità aritmetico-logica della CPU esegue a sua volta operazioni logiche (OR, NOT, AND, XOR e così via) che restituiscono falso o vero (O o 1), utilizzando lo stesso alfabeto del linguaggio macchina. Un linguaggio è di livello tanto più basso quanto è più vicino al linguaggio macchina. Usando i linguaggi di livello basso, vengono scritte manualmente le varie istruzioni e gli indirizzi di memoria. L'esempio tipico di linguaggio di basso livello è l'Assembly. Data la natura" elementare" dei programmi scritti in linguaggio macchina, un programma scritto per un determinato processore non funzionerà per un computer dotato di un processore diverso Possiamo quindi dire che la CPU viene programmata in linguaggio macchina e che immediatamente sopra il linguaggio macchina esiste il linguaggio Assembly. Se si utilizza un linguaggio con un livello più alto rispetto al linguaggio macchina, è necessario un passaggio intermedio che "traduca" il linguaggio sorgente in linguaggio macchina, ossia in operazioni elementari che interagiscano direttamente con gli elementi del computer. I tipi di traduttori sono due: i compilatori e gli interpreti. Gli interpreti traducono un'istruzione alla volta e contemporaneamente la eseguono. Durante la fase di interpretazione viene anche eseguito un controllo sulla sintassi del programma e ne vengono segnalate le irregolarità. In caso di errore, è compito del programmatore apportare le dovute correzioni alla riga di codice incriminata per permettere l'avanzamento nell'interpretazione dell'algoritmo. Una volta corretti tutti gli errori, il programma può essere eseguito. Esempi di linguaggi interpreti (o interpretati) sono Python e Java. I compilatori traducono un programma scritto in un linguaggio di alto livello in un programma comprensibile al codice macchina di uno specifico hardware. Il vantaggio della compilazione è quello di ottenere dei file eseguibili in poco tempo, ma ha lo svantaggio di dover creare un programma diverso per ogni sistema operativo o hardware differente, sacrificando quella che si definisce "portabilità" del software. Esempi di linguaggi compilati sono C e Pascal. Il vantaggio di un programma interpretato risiede nella portabilità del software creato, che è quindi compatibile con macchine e piattaforme diverse: questo avviene tuttavia a discapito della velocità di esecuzione, dato che, oltre a eseguire il codice, l'interprete deve controllarlo. In passato la compilazione è stata la norma per tutti i linguaggi di programmazione di uso genera-le, ora si tende invece a scegliere linguaggi interpretati o linguaggi pensati come un ibrido tra la compilazione e l'interpretazione. Sebbene il divario tra questi due approcci sia stato ridotto notevolmente, la compilazione rimane il metodo preferito per applicazioni che richiedono le massime prestazioni possibili. Il numero dei linguaggi conosciuti è molto alto: sono circa 2500 i linguaggi più o meno noti e diffusi. Come abbiamo visto, possono essere classificati in base alla distanza dal linguaggio macchina (distinguendo linguaggi di alto e di basso livello) oppure in base all'approccio usato (distinguendo linguaggi compilati e interpretati). Lisp, C, C++, Pascal, HML, CSS, JavaScript, Java, PHP, Python, Ruby ecc. sono fra i linguaggi più conosciuti. Non tutti però sono linguaggi di programmazione veri e propri: alcuni sono linguaggi di programmazione puri, altri sono orientati al Web, altri sono linguaggi di markup. I linguaggi di markup permettono di rappresentare in maniera strutturale un testo. Si usano per marcare la struttura di un documento: per esempio, una volta individuati il titolo, il sottotitolo e il paragrafo di un documento vengono apposte delle etichette (o tag) che delimitano l'elemento. Questo permette di identificare le varie parti del testo e codificarle, in maniera da renderle interpretabili ed eseguibili da più software. I linguaggi di markup hanno convenzioni e marcature diverse. I più famosi sono HTML (che serve per la creazione di ipertesti nel Web), XML, Xhtml (un'unione dei due linguaggi precedenti), TeX e le sue varianti (come LaTeX). La formattazione grafica dei testi codificati con questi linguaggi viene definita da ulteriori codici, come CSS, Xsl e altri linguaggi analoghi. I linguaggi di programmazione orientati al Web sono invece PHP, ASP, RubyOnRails (una variante di Ruby), Javascript e molti altri. Anche tra questi ci sono delle differenze: sono tutti linguaggi interpretati, ma mentre i primi vengono interpretati lato server (ossia l'interprete risiede sulla macchina server dove sono ospitate le pagine di codice) JavaScript ha il suo interprete direttamente nel browser. Infine, possono essere considerati linguaggi di programmazione a tutti gli effetti Lisp, C, C++, Pascal, Java, Python e molti altri, che servono per codificare dei veri e propri programmi.