7 Programmazione condizionale e cicli

Finora abbiamo eseguito tutte le nostre operazioni in maniera sequenziale, ovvero i comandi venivano risolti uno dopo l’altro. A volte può succedere che si voglia eseguire un comando o un insieme di comandi solo se si verifica una certa condizione oppure vogliamo eseguire lo stesso comando un numero predefinito di volte (for) oppure ancora fino a che una certa condizione non si verifica (while).

7.1 if else

Nel primo caso avremo bisogno di utilizzare la programmazione condizionale, ovvero lo statement if(<condizione>){<comandiTRUE>}. Se dovremo dire a R cosa fare quando una certa condizione viene rispettata allora useremo solo l’if, altrimenti, se dobbiamo specificare anche l’altra strada, cioè quando la condizione non viene rispettata, dovremo usare anche l’else{<comandiFALSE>}.

La <condizione> va definita in termini di operazione logica, ovvero deve produrre come risultato un solo TRUE o un FALSE.

Se la condizione avrà valore TRUE R eseguirà il codice in <comandiTRUE>, se anche l’else è stato specificato allora, in caso la condizione abbia valore FALSE, R eseguirà il codice in <comandiFALSE>.

Ad esempio, possiamo usare l’if per rendere positivo un numero negativo:

if (num < 0) {
  num <- num * -1
}

Se num è positivo R non eseguirà alcun comando, in caso contrario lo moltiplicherà per -1 rendendolo positivo.

Tra le parentesi graffe è possibile scrivere un numero indefinito di righe di codice. Proviamo a scrivere la funzione precedente aggiungendo la funzione print("<testo>") per comunicare con l’utente:

num <- -1
if (num < 0) {
  print("num è negativo.")
  print("Non ti preoccupare, lo sistemo io.")
  num <- num * -1
  print("Ora num è positivo.")
}
## [1] "num è negativo."
## [1] "Non ti preoccupare, lo sistemo io."
## [1] "Ora num è positivo."
num
## [1] 1

Esercizio 18

Cosa ritornerà il codice seguente?

x <- 1
if (3 == 3) {
  x <- 2
}
x

Un 2. All’inizio x è uguale a 1, siccome <condizione> è sempre uguale a TRUE, R eseguirà i comandi all’interno della graffa e trasformerà x in 2.

Esercizio 19

Cosa ritornerà il codice seguente?

x <- 1
if (x == 1) {
  x <- 2
  if (x == 1) {
    x <- 3
  }
}
x

Ancora un 2. Questa volta la seconda condizione non viene rispettata e quindi R non esegue i comandi tra le parentesi del secondo if.

Per definire i comandi da eseguire nel caso la condizione non sia verificata possiamo usare lo statement if(<condizione>){<comandiTRUE>}else{<comandiFALSE>}.

Partiamo con un numero decimale:

a <- 3.14

Isoliamo la parte decimale:

dec <- a - trunc(a)
dec
## [1] 0.14

Ora usiamo if else per arrotondare a:

if (dec >= 0.5) {
  a <- trunc(a) + 1
} else {
  a <- trunc(a)
}

a
## [1] 3

Se si hanno più casi da analizzare possiamo annidare i costrutti di programmazione condizionale if else usando lo statement else if(){}.

Ad esempio, se vogliamo confrontare il risultato di due giocatori a e b dobbiamo considerare i 3 casi possibili: vincita, perdita o parità.

a <- 1
b <- 1

if (a > b) {
  print("A vince!")
} else if (a < b) {
  print("B bince!")
} else {
  print("parità")
}
## [1] "parità"

7.2 cicli for

Per ripetere un pezzo di codice un numero predefinito di volte, una per ogni elemento di un vettore di indici, possiamo usare il ciclo for.

Se ad esempio si vuole eseguire un comando 4 volte si può usare il comando seguente:

for (indice in c("My", "first", "for", "loop")) {
  print("un giro")
}
## [1] "un giro"
## [1] "un giro"
## [1] "un giro"
## [1] "un giro"

indice assumerà i valori nel vettore c("My", "first", "for", "loop") e per questo R eseguirà il codice tra parentesi graffe una volta per ognuno dei valori nel vettore. La variabile indice è disponibile all’interno del ciclo per essere usata a proprio piacimento, ad esempio:

for (indice in c("My", "first", "for", "loop")) {
  print(indice)
}
## [1] "My"
## [1] "first"
## [1] "for"
## [1] "loop"

La variabile indice risiede nel Global environment per cui è accessibile anche al di fuori del ciclo for e avrà come valore l’ultimo assunto nel ciclo. In questo caso "loop". A differenza dell’ambiente function, le variabili create all’interno di un ciclo sono disponibili nel Global environment.

Esercizio 20

Scrivere una funzione che, attraverso il ciclo for, calcoli il numero di assi in una mano da 10 carte estratte a caso.

mano <- estraiCarte(deck, 10)
nAssi <- 0
for (carta in 1 : nrow(mano)){
  if(mano$figura[carta] == "asso"){
    nAssi <- nAssi + 1
  }
}
mano
##     figura   seme valore id
## 19    otto  fiori      8 19
## 14      re  fiori     13 14
## 31    nove quadri      9 31
## 18    nove  fiori      9 18
## 42    jack  cuori     11 42
## 36 quattro quadri      4 36
## 30   dieci quadri     10 30
## 47     sei  cuori      6 47
## 22  cinque  fiori      5 22
## 35  cinque quadri      5 35
nAssi
## [1] 0

Si noti che il codice precedente è equivalente a

nAssi <- sum(mano$figura == "asso")

Esercizio 21

Creare una funzione che calcoli la somma del valore delle carte in una mano di blackjack. Si usi il mazzo BJdeck creato nel capitolo precedente e la funzione per estrarre carte estraiCarte(mazzo, nCarte).

mano <- estraiCarte(BJdeck, 3)
calcolaSomma <- function(mano) { 
  nCarte <- nrow(mano)
  nAssi <- 0
  somma <- 0
  for(carta in 1 : nCarte){
    if(mano$figura[carta] != "asso"){
      somma <- somma + mano$valore[carta]
    }else{
      nAssi <- nAssi + 1
    }
  }
  #cat("la somma dei valori senza gli assi è ", somma,"\n")
  if(nAssi > 0){
    for(asso in 1 : nAssi){
      if((somma + 11) > 21){
        somma <- somma + 1
      }else{
        somma <- somma + 11
      }
    }
  }
  return(somma)
}
calcolaSomma(mano)
## [1] 18

7.3 while

Se non abbiamo idea di quante volte dobbiamo eseguire un certo comando possiamo usare lo statement while(<condizione>){<comandiTRUE>}. Qusto statement ci permette di eseguire <comandiTRUE> finchè la <condizione> non diventa FALSE. Il test sulla condizione viene fatto in entrata del ciclo, cioè prima di eseguire <comandiTRUE>. Se la condizione non è più verificata, il ciclo while viene interrotto.

Attenzione! Non eseguire MAI il ciclo while(TRUE){} poichè la condizione rimarrà sempre vera e il ciclo non si interromperà MAI. In tal caso basta premere il bottone STOP nella console per bloccare l’esecuzione.

Proviamo a stampare “ciao” nella console fino a che l’utente non dirà di smettere. Per svolgere questo esercizio avrete bisogno del codice seguente:

risposta <- readline(prompt = "<messaggio>")

Tramite questo comando possiamo assegnare a risposta ciò che l’utente digita nella console.

Quindi possiamo procedere scrivendo il codice necessario per svolgere l’operazione richiesta:

continua <- "y"
while(continua != "smetti"){ 
  print("ciao")
  continua <- readline(prompt = 'Se vuoi smettere scrivi "smetti": \n')
}

Esercizio 22

Quale sarà il valore di conteggio e di somma alla fine di questo comando?

somma = 0
conteggio = 1
while (conteggio < 10){
  somma = somma + conteggio
  conteggio = conteggio + 1
}

conteggio sarà uguale a 10, mentre somma sarà uguale a 45

Esercizio 23

Supponiamo di avere una giovane coppia che ha chiesto un prestito di 300.000 euro a una banca a un interesse annuo del 5%. L’ammortamento è in 30 anni e la rata deve essere pagata mensilmente. La coppia decide di pagare il prestito con una rata di 1.600 euro. Riusciranno a pagare il mutuo in 30 anni? Usare R per simulare il pagamento e provare a rispondere alla domanda.

# set up
nMesi <- 0         # conta il numero di mesi
capitale <- 300000 # capitale iniziale
rata <- 1600       # rata mensile
tasso <- 0.05      # tasso di interessa annuale
pagato <- 0        # quanto pagato finore

# Covnertire il tasso annuale in mensile e trasformarlo in moltiplicatore (1+tasso)
tassoM <- (1+tasso) ^ (1/12)

# eseguire i comandi finchè il capitale non è esaurito
while ( capitale > 0 ) {
  
  # calcoli del mese
  nMesi <- nMesi + 1               
  capitale <- capitale * tassoM  # aggiungi interessi
  capitale <- capitale - rata    # paga la rata
  pagato <- pagato + rata        # totale pagato finora
  
  # stampare in console il bilancio mensile = print()
  # cat( "mese", nMesi, ": capitale", round(capitale), "\n")
  
} # end of loop

# pagamenti totali:
cat("pagamento totale:", pagato, "\n" )
## pagamento totale: 569600
cat("in mesi: ",nMesi)
## in mesi:  356

Esercizio supplementare avanzato

Creare una funzione che simuli una partita di blackjack senza scommesse. Un solo giocatore contro il banco. Queste le regole:

  1. Un mazzo da 52 carte.
  2. Si mescola il mazzo (shuffle).
  3. All’inizio sia il banco che la propria mano sono vuote (data.frame()).
  4. Si estraggono 2 carte, la prima la si da al giocatore, la seconda al banco.
  5. Si rivela la carta del giocatore. Si chiede al giocatore se vuole continuare a giocare (“y”) oppure se vuole uscire (“n”).
  6. Si estrae una carta e la si aggiunge al banco.
  7. Si calcolano le somme dei valori delle carte (calcolaSomma) sia nel banco che nella mano.
  8. Se il giocatore ha deciso di continuare viene estratta un’altra carta e aggiunta alla mano. Si continua con i passi 5/6/7 fino a che il giocatore o il banco non hanno sforato (hanno superato il 21) o il giocatore non esce dal gioco (al passo 5 sceglie “n”).
  9. Se il banco ha un valore totale uguale o più alto del valore della mano del giocatore o il giocatore ha sforato, il banco vince. Altrimenti, vince il giocatore.

Per eseguire il passo 5 usare le seguenti righe di codice:

#1. preparazione del mazzo, aggiungiamo a "BJdeck" una colonna di identificatori "id=1:52". Ci servirà per ricordare quali carte sono già state estratte
BJdeck$id <- 1:52
BJPartita= function(mazzo){
  BJdeckPartita <- mazzo
  #2. mischia il mazzo
  shuffle(BJdeckPartita) 
  #start the game
  #3.
  mano <- data.frame()
  banco <- data.frame()
  continuaWhile <- TRUE
  while(continuaWhile){
    #4.
    carte <- estraiCarte(BJdeckPartita, 2)
    #togliamo le carte estratte dal mazzo
    BJdeckPartita <- BJdeckPartita[-carte$id, ]
    #assegniamo le carte
    mano <- rbind(mano, carte[1,])
    banco <- rbind(banco, carte[2,])
    sommaMano <- calcolaSomma(mano)
    sommaBanco <- calcolaSomma(banco)
    #5. 
    cat("\n \n la tua mano è: \n")
    print(mano[,c(1,2)],row.names = FALSE)
    if(sommaMano > 21){ #il giocatore ha sforato
      cat("Hai perso! \n")
      continuaWhile <- FALSE # esco dal while, finisce la partita
    }else{
      continua <- readline(prompt="Continuare? (y = sì, n = no)") #comparirà nella console
      while(!(continua %in% c("y","n"))){ #finchè il giocatore non decide se continuare o meno
        continua <- readline(prompt="Comando non riconosciuto. Continuare? (y = sì, n = no)")
      }
      #ora "continua" contiene il carattere "y" o "n"
      if(continua=="n"){
        if((sommaBanco < sommaMano) | sommaBanco >21){ #la mano è maggiore del banco o il banco ha sforato!
          cat("Hai vinto! \n" )
        }else{cat("Hai perso! \n")}
        continuaWhile <- FALSE # esco dal while, finisce la partita
      }
      cat("Le carte del banco: \n")
      print(banco[, c(1, 2)], row.names = FALSE)
    }
  }
}
#per avviare la partita digitare BJparita(BJdeck) nella console