Algoritmo e computazione: un'introduzione ai due concetti più fraintesi di sempre (parte 1/2)

Un nuovo (?) modo di pensare

Chiunque inizi a cimentarsi con le lezioni di programmazione (che sia in C, in C++, in Java o in qualsivoglia altro linguaggio) si trova fin da subito a dover far i conti con un nuovo modo di pensare: il pensiero algoritmico, noto anche come pensiero computazionale, ("Computational Thinking" in inglese).

Lezioni di programmazione online

Sia chiaro: dico "nuovo" non perché sia nuovo per davvero. Il pensiero algoritmico è infatti parte integrante del modo in cui pensa un essere umano quando si approccia alla risoluzione di un problema. E allora perché "nuovo"? Seguitemi e capirete.

Due approcci complementari per la risoluzioni di problemi

Quando siamo chiamati a risolvere un problema il primo istinto è quello di tuffarsi in pista, iniziando a guardarci intorno prestando attenzione ai vari elementi del problema in esame alla ricerca di "appigli" vari, in altre parole fatti o informazioni utili a sbloccare nella nostra mente tutte quelle intuizioni, associazioni, deduzioni che, alla fine, ci spianano la strada verso la soluzione. È un po' quello che accade quando cerchiamo di risolvere un problema di geometria, dove generalmente ci viene fornita una figura e alcune misure relative a delle parti che la compongono; oltre a, chiaramente, una domanda sulla figura stessa, come ad esempio: "Quanto misura il lato ai vertici B e C del trapezio dato?" oppure "Quanto misura l'area del rettangolo aventi vertici A, B, C' e D' facente parte del trapezio riportato in figura?".

Questo approccio coinvolge però in realtà due tipi di pensiero, che in questa sede chiameremo pensiero esecutivo (o estemporaneo) e pensiero proiettivo. Qual è la differenza? 

Pensiero esecutivo

Diciamo che pensiamo esecutivamente ogni volta che ci accorgiamo che un certo passo di risoluzione può essere eseguito subito, senza attendere oltre. Ad esempio, se ci viene chiesto di ordinare numericamente in ordine crescente tre carte prese a caso da un mazzo di carte francesi, ci renderemo conto quasi subito se ci sono carte da scambiare e se ci sono carte da lasciare dove sono. Ad esempio, nella figura seguente ci accorgiamo immediatamente che occorre scambiare il 9 e il J, e che il K va invece lasciato lì dov'è.

In una situazione del genere, l'istinto sarebbe dunque quello di scambiare di posto il J e il 9, ottenendo così un insieme di tre carte ordinate in modo crescente (9, J e K). Osserviamo adesso la seguente situazione.

In questo scenario conosciamo solo una delle tre carte, il K. Questa situazione ci dà subito una informazione certa e chiara: il K non si sposterà. Questo è vero perché, se le due carte ignote fossero inferiori a K, nessuna delle due andrebbe ad occupare il posto di K; mentre, se una o entrambe fossero un K, non ci sarebbe motivo di modificare la posizione del K a destra (attenzione: per semplicità, stiamo volutamente ignorando la precedenza dei semi, in quanto non sostanziale per questa discussione).

In definitiva, quindi, sappiamo che il K mostrato in figura non si sposterà qualunque cosa capiti, poiché non esiste alcuna carta maggiore di K. Pertanto, deduciamo che non c'è alcuna ragione per cui K debba spostarsi; e così decidiamo di lasciarla lì dov'è. Questa decisione  estemporanea viene fuori da quello che abbiamo chiamato pensiero esecutivo. Ma come ci comportiamo con le altre due carte? Intuitivamente, verrebbe da dire che non possiamo risolvere il problema fin quando non sapremo di quali carte si tratta. Questo però è vero solo in parte.

È vero se intendiamo risolvere il problema solo attraverso l'approccio estemporaneo  visto poc'anzi (osservo --> ragiono --> svolgo). È falso se contempliamo la possibilità di applicare un approccio di tipo proiettivo.

Pensiero proiettivo

Un approccio proiettivo non intende produrre come soluzione una sequenza di azioni svolte sul momento, come invece suggerisce di fare l'approccio estemporaneo, ma piuttosto una sequenza di azioni da svolgere in base a diversi scenari possibili. Una soluzione per il problema delle carte elaborata seguendo un approccio proiettivo si presenterà quindi come una sfilza di "SE ... ALLORA ...", ciascuno avente il compito di imporre una propria regola operativa in base al verificarsi di una determinata condizione.

Potremmo quindi muoverci nel seguente modo. Anzitutto, poiché avremo bisogno di riferirci ad entrambe le carte in modo non ambiguo, introdurremo dei nomi per tali carte, banalmente "A" per la prima a sinistra e "B" per quella al centro.

Ora, è chiaro che al posto di A e B potrebbe esserci qualunque cosa. Questo potrebbe portarci a concludere che è necessario prevedere tutti i casi possibili e dunque definire un'azione per ciascun caso. Ad esempio, potremmo pensare di produrre le seguenti regole:

  • SE A è un asso e B un asso ALLORA non scambiare
  • SE A è un asso e B un 2 ALLORA non scambiare
  • SE A è un asso e B un 3 ALLORA non scambiare
  • ...e così via fino a SE A è un asso e B un K ALLORA non scambiare.

Poi però dovremmo proseguire in questo modo:

  • SE A è un 2 e B un asso ALLORA scambia
  • SE A è un 2 e B un 2 ALLORA non scambiare
  • SE A è un 2 e B un 3 ALLORA non scambiare
  • ...e di nuovo via così fino a completare le coppie di tipo <2, b>.

Si riprenderà quindi definendo tutte le regole per le coppie di tipo <3, b>, ovvero:

  • SE A è un 3 e B un 1 ALLORA scambia
  • SE A è un 3 e B un 2 ALLORA scambia
  • SE A è un 3 e B un 3 ALLORA non scambiare

e via così per tutte le coppie possibili <a, b>.

Domanda: è davvero necessario ragionare in questo modo? Non c'è un modo più "asciutto" ma comunque in grado di contemplare tutti i casi possibili in un colpo solo o comunque in una manciata di istruzioni? La risposta è sì. A ben vedere, infatti, riflettendoci un po' su, si capisce subito che lo scambio debba avvenire solo quando la carta A risulti maggiore della carta B. Grazie a questa osservazione possiamo quindi formulare le seguente semplice regola:

SE A è più grande di B ALLORA scambia di posto A e B.

Detto, fatto. Con un approccio proiettivo abbiamo trovato la soluzione a un problema apparentemente irrisolvibile. In realtà, la nostra soluzione non va a risolvere un problema specifico, come quello dello scenario J, 9, K visto prima, ma un'intera classe di problemi dello stesso tipo, nella fattispecie il problema (computazionale) di dover ordinare due carte in base al loro valore numerico. L'applicazione o meno della regola dipenderà quindi dalla specifica istanza del problema computazionale che si presenterà, dando così luogo a uno scambio o meno in base alla specifica situazione.

Il pensiero proiettivo si fonde in modo naturale con quello estemporaneo

I due tipi di pensiero appena discussi (estemporaneo e proiettivo) si rincorrono continuamente nella nostra esperienza quotidiana in un continuo alternarsi di decisioni attuate istantaneamente e, viceversa, di decisioni pianificate. Questa mescolanza ci fa apparire il nostro pensiero come un'unica cosa, un unico modo di approcciare alla risoluzione di un problema. In realtà, come abbiamo visto, si tratta di due approcci distinti che semplicemente si alternano nella nostra mente in base alla necessità e alla complessità del problema da risolvere. Ecco perché il pensiero proiettivo, che chiameremo anche pensiero computazionale, non è qualcosa di veramente nuovo, inteso come "estraneo alla nostra esperienza". Al contrario, esso scatta in modo naturale in ognuno di noi, agendo nascostamente in congiunzione con il pensiero esecutivo, confondendosi con esso e lasciando quindi, in un certo senso, perdere le proprie tracce nei meandri dei nostri ragionamenti. L'obiettivo, per un aspirante programmatore, è imparare a distinguere e riconoscere le due forme di pensiero e ad applicarle a suo piacimento, ovverosia in funzione delle sue necessità. In particolare:

  • durante l'analisi di un problema computazionale, entrambe le modalità di pensiero vengono messe in campo in misura pressoché uguale allo scopo di identificare elementi del problema utili all'elaborazione di una soluzione computazionale; esattamente come quando si cerca di risolvere un problema di geometria: né più né meno;
  • durante l'elaborazione di una soluzione computazionale, il programmatore userà principalmente il pensiero computazionale per costruire una sequenza di azioni potenziali atte a risolvere computazionalmente il problema analizzato.

Soluzione computazionale Vs. Soluzione estemporanea

Una soluzione computazionale è sempre qualcosa espressa nella forma SE ... ALLORA ...? No, non sempre. Si prenda a titolo di esempio il problema dello scambio.

Si hanno un bicchiere A e un bicchiere C, ognuno contenente una bibita differente, e si desidera scambiarne il contenuto. L'esperienza quotidiana ci suggerisce che è possibile usare un terzo bicchiere di appoggio al fine di versarvi temporaneamente uno dei due contenuti. L'idea è cioè quella di liberare uno dei due bicchieri per consentire il travaso del contenuto dell'altro. Se conveniamo di chiamare V il bicchiere vuoto, la sequenza computazionale finale sarà dunque:

  1. V <-- A
  2. A <-- C
  3. C <-- V

Se però la si guarda bene, questa sequenza corrisponde esattamente alle azioni che avremo svolto una volta effettuato lo scambio. Sembrerebbe quindi che, in questo caso, non ci sia una distinzione netta tra una soluzione computazionale ed una esecutiva, cosa che prima eravamo in grado di fare grazie alla presenza delle regole SE ... ALLORA ....

In realtà però non è così. Una differenza è ancora riscontrabile e consiste nel livello di generalità che emerge dalla sequenza. Riflettendoci bene, infatti, è certamente vero che le azioni svolte al termine dello scambio saranno le tre sopra descritte; ma è altrettanto vero che, al momento dell'esecuzione, i bicchieri A e C hanno un contenuto ben definito, cioè acqua e coca cola. L'esecuzione delle tre azioni sopra descritte darà quindi luogo ad una sequenza che è più appropriato descrivere nel modo seguente:

  1. V <-- acqua
  2. A <-- coca cola
  3. C <-- acqua

Come vedete, a sinistra delle frecce sono rimasti i nomi dei contenitori; ma a destra delle frecce sono comparsi i "valori" associati ai contenitori in un preciso momento della computazione in via di svolgimento.

  1. Al primo passo, infatti, dentro A ci sarà l'acqua.
  2. Al secondo passo, invece, in C avremo la coca cola.
  3. Ma al terzo passaggio l'acqua sarà definitivamente ormai in V (in virtù del primo passaggio), per cui il risultato finale al termine delle tre azioni sarà quello di aver trasferito l'acqua in C e la coca cola in A.

Scopri gli insegnanti di programmazione vicino a te!
Andrei 1ª lezione gratis (2)10100
Nicolò 1ª lezione gratis (5)10100

La differenza sta dunque solo ed unicamente nella presenza o meno di generalità? Nì. Immaginiamo di avere un bicchiere vuoto e di voler versare al suo interno dell'acqua. L'azione da svolgere è in tal caso la seguente:

V <-- acqua

Cos'è questa? È un'azione computazionale o esecutiva? La risposta in questo caso è: dipende. In sintesi:

  • se ritroviamo questa scrittura in una soluzione computazionale allora si tratterà di un'azione computazionale, meglio nota come istruzione, in altre parole un'azione da svolgere;
  • se invece la ritroviamo all'interno di una sequenza esecutiva si tratterà semplicemente di un'azione già svolta.

La differenza dunque sta tutta nella presenza o meno di progettualità. Quindi:

  • se l'intento di una scrittura è prescrivere lo svolgimento potenziale o certo di una data azione, allora tale scrittura costituisce una soluzione computazionale, meglio nota come algoritmo;
  • al contrario, se l'intento di una scrittura è fare il "report" di qualcosa che si è già verificato, allora tale scrittura costituisce una soluzione esecutiva, meglio nota come computazione.

In definitiva, un algoritmo si presenta come una sequenza di istruzioni, ovvero di azioni ancora da svolgere (e che non è detto verranno svolte tutte quante). Una computazione è  invece una sequenza di azioni già svolte, come risultato dell'esecuzione di un algoritmo.

Nel prossimo articolo vedremo alcuni esempi dove metteremo a confronto algoritmi e rispettive sequenze di computazione.

Ti è piaciuto? Condividilo
Fabio
Classe '80, laureato in informatica e dottorando di ricerca a Catania. Di tanto in tanto imparo cose e cerco di spiegarle agli altri.Contattare
Contattare
Usa il nostro Strumento di Ricerca Intelligente
© 2007 - 2024 Letuelezioni.it è un membro della famiglia GoStudent Mappa del sito: Insegnanti privati