Per completezza, si rimanda al primo post del sito per ulteriori informazioni.

Si ricorda che dello stesso esercizio possono esistere più varianti.


Produttoria

Si scriva una funzione produttoria(a,b) che, dati come argomenti due interi positivi a e b, con a ≤ b, restituisca il prodotto di tutti gli interi fra a e b, estremi compresi. Esempi:

produttoria(4,6) → 120

produttoria(10,10) → 10

produttoria(10,11) → 110

function produttoria(a,b) {
  let n = 1;
  for (let i = a; i <= b; i++) {
    n *= i; 
  }
  return n;
}

Quaterne

Si scriva una funzione quaterne(a,b) che, dati come argomenti due interi a e b, con a ≤ b, restituisca il numero di quaterne (cioè sequenze distinte di quattro numeri consecutivi) comprese fra a e b, estremi esclusi. Esempi:

quaterne(4,6) → 0

quaterne(4,10) → 2 (sono: 5/6/7/8 e 6/7/8/9)

quaterne(-4,10) → 10 (sono -3/-2/-1/0, -2/-1/0/1, … , 5/6/7/8, 6/7/8/9)

function quaterne(a,b) {
  if (a == b || a > b)
    return 0;
  
  let n_quaterne = 0;

  for (let i = a; i < b; i++) {
    if ((i + 4) < b)
      n_quaterne++;
  }
  return n_quaterne;
}

Numeri perfetti

Un numero naturale n si dice perfetto se è uguale alla somma dei propri divisori propri (ovvero, tutti i suoi divisori positivi escluso n). Si scriva una funzione perfetto(n) che, dato un numero naturale n, restituisca true se n è perfetto, false altrimenti. Esempi:

perfetto(6) → true (infatti 1+2+3 = 6)

perfetto(10) → false (infatti 1+2+5 ≠ 10)

perfetto(28) → true (infatti 1+2+4+7+14 = 28)

function perfetto(n) {
  let divisori = [];
  let tot = 0;
  
  for (let i = 1; i < n; i++) {
    if (n % i == 0)
      divisori.push(i);
  }

  for (let i in divisori)
    tot += divisori[i];

  return tot == n;
}

Range

Si scriva una funzione range(a,b) che, dati due interi a e b restituisca un array ordinato di interi, contenente tutti e soli gli interi i tali che a ≤ i e i ≤ b. Esempi:

range(2,6) → [2, 3, 4, 5, 6]

range(10,10) → [10]

range(-5, 1) → [-5, -4, -3, -2, -1, 0, 1]

range(10, 4) → []

function range(a,b) {
  let arr = [];

  for (let i = a; i <= b; i++) {
      arr.push(i);
  }
  return arr;
}

Penultimo

Si scriva una funzione penultimo(a) che, dato un array di stringhe a, restituisca la penultima stringa secondo l’ordine alfabetico fra quelle presenti nell’array, oppure undefined se non esiste una penultima. Esempi:

penultimo([“pera”, “zucca”, “mela”]) → “pera”

penultimo([“dattero”, “zucca”, “mela”]) → “mela”

penultimo([“zucca”]) → undefined

function penultimo(a) {
  if (a.length < 2)
    return undefined;

  a.sort();
  return a[a.length - 2];
}

Ordinamento per lunghezza

Si scriva una funzione ordlun(a) che, dato un array di stringhe a, restituisca un array contenente le stesse stringhe, ordinate secondo la loro lunghezza (dalla più breve alla più lunga); a parità di lunghezza, andranno ordinate secondo l’ordine alfabetico. Esempi:

ordlun([“pera”, “zucca”, “mela”]) → [“mela”, “pera”, “zucca”]

ordlun([“dattero”, “zucca”, “mela”]) → [“mela”, “zucca”, “dattero”]

ordlun([]) → []

function ordlun(a) {

  function compare(str1,str2) {
    let len1 = str1.length;
    let len2 = str2.length;

    if (len1 < len2)
      return -1;
    else if (len1 > len2)
      return 1;
    else {
      // stessa lunghezza delle 2 stringhe => ordinamento lessicografico
      if (str1 < str2)
        return -1;
      return 1;
    }
  }

  return a.sort(compare);
}

Multiinsiemi - conversione

Un multiinsieme è una generalizzazione del concetto di insieme in cui lo stesso elemento può apparire più volte. Si potrebbe rappresentare un multiinsieme come un array, per esempio: [4, 7, 10, 4, 9, 7, 4]. Lo si potrebbe anche rappresentare come un oggetto, in cui gli elementi sono le chiavi, e i corrispondenti valori indicano quante volte compare quell’elemento. L’array precedente può dunque essere rappresentato come { 4: 3, 7: 2, 9: 1, 10: 1}.

Si scriva una funzione cvtmi(a) che, data la rappresentazione ad array di un multiinsieme (di interi o stringhe), restituisca la corrispondente rappresentazione a oggetto. Esempi:

cvtmi([“pera”, “zucca”, “mela”]) → { mela: 1, pera: 1, zucca: 1}

cvtmi([“pera”, “pera”, “pera”, “zucca”]) → {pera: 3, zucca: 1}

cvtmi([1,2,3,4,3,4,5,2,1,1,9]) → {1:3, 2:2, 3:2, 4:2, 5:1, 9:1}

function cvtmi(a) {
  let obj = {};

  for (let i in a) {
    if (a[i] in obj)
      obj[a[i]] = obj[a[i]] + 1;
    else
      obj[a[i]] = 1;
  }
  return obj;
}

Multiinsiemi - unione e intersezione

Si scrivano due funzioni: unionemi(a,b) che, dati due oggetti a e b che rappresentano multiinsiemi, come definiti nell’esercizio precedente, restituisca un oggetto che rappresenta l’unione dei due multiinsiemi, e intersezionemi(a,b) che allo stesso modo restituisce l’intersezione fra i due multiinsiemi. Esempi:

unionemi({1:4, 2:1},{1:3, 3:1}) → {1:7, 2:1, 3:1}

intersezionemi({1:4, 2:1},{1:3, 3:1}) → {1:3}

intersezionemi({1:4, 2:1},{}) → {}

function unionemi(a,b) {
  let obj = {};

  for (var e in a)
    obj[e] = a[e];

  for (var e in b) {
    if (e in obj)
      obj[e] = obj[e] + b[e];
    else 
      obj[e] = b[e];
  }
  return obj;
}

function intersezionemi(a,b) {
  let obj = {};

  for (var e in a) {
    if (e in b)
      obj[e] = (a[e] > b[e] ? b[e] : a[e]);
  }
  return obj;
}

Conta vocali

Si scriva una funzione contaVocali(s) che, data una stringa s, restituisca il numero totale di vocali (lettere a, e, i, o, u, sia maiuscole che minuscole) presenti in s. Esempi:

contaVocali(“Ai lati d’Italia”) → 8

contaVocali(“qwerty”) → 1

contaVocali(“3463234”) → 0

contaVocali(“Nel mezzo del cammin di nostra vita”) → 11

function contaVocali(s) {
  let count = 0;
  let vocali = ['a','e','i','o','u'];

  for (let char of s) {
    if (vocali.includes(char) || vocali.includes(char.toLowerCase()))
      count++;
  }
  return count;
}

Una firma atipica

Si scriva una funzione firma(s) che, data una stringa s, restituisca un intero positivo k calcolato come segue: si immagini di sostituire ogni vocale (maiuscola o minuscola) o spazio in s con 1, e qualunque altro carattere con 0. Si consideri poi la stringa risultante come un numero binario, e sia k il suo valore. Esempi:

firma(“Vincenzo Gervasi”) → 18853

firma(“Alina Sirbu”) → 1385

firma("") → 0

function firma(s) {
  let bin = "";
  let vocali = ['a','e','i','o','u',' '];

  let k = 0; // risultato finale
  
  for (let char of s) {
    if (vocali.includes(char) || vocali.includes(char.toLowerCase())) {
      bin += "1";
    } else {
     bin += "0"; 
    }
  }

  // conversione binario-decimale
  let exp = 0;
  for (let i = bin.length - 1; i >= 0; i--) {
    k += (bin[i] == 1 ? 1 : 0) * (2**exp);
    exp++;
  }

  return k;
}

Applicare una funzione a un oggetto

Si scriva una funzione applyobj(o, f) che, dato un oggetto o e una funzione f, restituisca un oggetto o’ con le stesse chiavi di o, e in cui il valore di ogni chiave k sia dato dall’applicazione di f al valore della chiave k in o, ovvero: o’.k == f(o.k). Esempi:

applyobj({pere: 3, mele: 1}, x=>2*x) → {pere: 6, mele: 2}

applyobj({io: “Vincenzo”, tu: “Alina”}, e=>e.length) → {io: 8, tu: 5}

applyobj({io: 8, tu: 5}, e=>e) → {io: 8, tu: 5}

applyobj({}, e=>e+1) → {}

function applyobj(o,f) {

  for (let key in o) {
    o[key] = f(o[key])
  }

  return o;
}

Maxprod

Si scriva una funzione maxprod(a) che, dato un array di numeri naturali a, restituisca un oggetto con struttura {idx: i, val: n} in cui i sia l’indice e n il valore dell’elemento in a per cui è massimo il prodotto dell’indice per il valore dell’elemento. In caso di parità, si scelga l’elemento di indice minore. Esempi:

maxprod([8, 2, 2, 1]) → {idx: 2, val: 2}

maxprod([1, 8, 1, 2, 2]) → {idx: 1, val: 8}

function maxprod(a) {
  let obj = {idx: 0, val: 0}

  let val = 0;
  let idx = 0;
  let val_max_prod = -Infinity;

  for (let i = 0; i < a.length; i++) {
    if (a[i]*i > val_max_prod) {
      val_max_prod = a[i]*i;
      val = a[i]
      idx = i;
    }
  }

  obj["idx"] = idx; 
  obj["val"] = val;
  
  return obj;
}

Appiattimento

Si consideri un array i cui elementi possono essere o numeri, oppure altri array dello stesso tipo (ovvero, aventi per elementi o numeri, oppure altri array dello stesso tipo, e così via). Si scriva una funzione appiattisci(a) che, dato un array a come descritto sopra, restituisca un array contenente i soli numeri, nello stesso ordine in cui comparivano nell’array a. Esempi:

appiattisci([8, [2, 2], 1]) → [8, 2, 2, 1]

appiattisci([[1], 8, [1, 2], 2, []]) → [1, 8, 1, 2, 2]

function appiattisci(a) {
  let res = [];

  function rec_flat(a) {
    for (let elem of a) {
      if (elem instanceof Array) {
        rec_flat(elem);
      } else if (typeof elem == 'number') {
        res.push(elem);
      }
    }
  }

  for (let i = 0; i < a.length; i++) {
    if (a[i] instanceof Array)
      rec_flat(a[i]);
    else if (typeof a[i] == 'number')
      res.push(a[i]);
  }  

  return res;
}

Rosa dei venti

Si considerino le quattro direzioni cardinali (nord, est, sud, ovest), ciascuna codificata con la lettera corrispondente in (N, E, S, W), nonché le direzioni intermedie codificate con due lettere in ordine qualsiasi (es: NE = EN = nord-est). Si scriva una funzione rosa(s) che, data una stringa s contenente la codifica di una direzione come indicato sopra, restituisca un oggetto {x: i, y: j} in cui i e j sono valori fra -1, 0 e 1 che rappresentano lo spostamento unitario lungo l’asse x e lungo l’asse y, rispettivamente, corrispondente alla direzione codificata da s. Esempi:

rosa(“NE”) → {x: 1, y: 1}

rosa(“EN”) → {x: 1, y: 1}

rosa(“S”) → {x: 0, y: -1}

rosa(“NW”) → {x: -1, y: 1}

function rosa(s) {
  let obj = {x: 0, y: 0}

  let arr = s.split('');
  for (let i in arr) {
    switch(arr[i]) {
      case "N": obj["y"] = 1;
        break;
      case "S": obj["y"] = -1;
        break;
      case "W": obj["x"] = -1;
        break;
      case "E": obj["x"] = 1;
        break;
    }
  }
  return obj;
}

Percorso

Si scriva una funzione percorso(a) che, dato un array a di direzioni codificate da stringhe come nell’esercizio precedente, restituisca la distanza euclidea fra l’origine degli assi e la posizione finale di un ipotetico viaggiatore che parta dall’origine e faccia un passo nella direzione indicata da ogni elemento del percorso, in ordine. Esempi:

percorso([]) → 0

percorso([“N”,“E”]) → 1.4142136

percorso([“N”]) → 1

percorso([“N”,“N”,“NE”]) → 3.1622777

percorso([“N”,“S”]) → 0

percorso([“N”,“S”,“NE”]) → 1.4142136

function percorso(a) {
  if (a.length < 1)
    return 0;

  let obj = {x: 0, y: 0};
  let tmp;

  for (let i = 0; i < a.length; i++) {
    tmp = rosa(a[i]);
    obj["x"] += tmp["x"];
    obj["y"] += tmp["y"];
  }
  return Math.sqrt((obj["x"])**2 + (obj["y"])**2);
}

Composizione di funzioni

Si scriva una funzione componi(f,g) che, date due funzioni f e g, restituisca una funzione h tale che h(x) = g(f(x)). Esempi:

componi(x=>2x, x=>2x)(3) → 12

componi(s=>s.length, x=>x**2+1)(“Vincenzo”) → 65

componi(a=>a[0], s=>s.length)([“pere”,“banane”]) → 4

function componi(f,g) {
  return function h(x) {
    return g(f(x));
  }
}

// oppure

function componi(f,g) {
  return (h = x => f(g(x)));
}

Differenza di date

Si rappresenti una data come un oggetto della forma {giorno: g, mese: m, anno: a}, in cui g, m, e a sono indicati come numeri (con l’usuale convenzione: Gennaio = 1, … Dicembre = 12 per i mesi). Si scriva una funzione diff(d1, d2) che, date due date nel formato indicato sopra, restituisca il numero di giorni trascorsi fra la prima e la seconda data (si usino i numeri negativi se d2 è precedente a d1). Si ricorda che Febbraio è lungo 28 giorni negli anni ordinari, e 29 giorni negli anni bisestili; sono bisestili tutti gli anni divisibili per 4, salvo quelli che sono divisibili per 100 (che invece sono ordinari). Ai fini dell’esercizio si possono trascurare le varie riforme del calendario avvenute nei secoli. Esempi:

diff({giorno: 1, mese: 1, anno: 2020},{giorno: 4, mese: 2, anno: 2020}) → 34

diff({giorno: 1, mese: 1, anno: 2019},{giorno: 1, mese: 1, anno: 2020}) → 365

function diff(d1,d2) {
  let tmp_d1 = new Date(d1["anno"],d1["mese"] - 1,d1["giorno"]);
  let tmp_d2 = new Date(d2["anno"],d2["mese"] - 1,d2["giorno"]);
  let res = tmp_d2 - tmp_d1;
  return Math.ceil(res/(24*3600*1000)); // divido per i millisecondi in un giorno
}

Dividere l’eredità

Si consideri un albero k-ario, in cui i nodi hanno la struttura {val: n, figli: [ t1, … tk ]}, come visto a lezione. Si vuole distribuire l’eredità di ogni nodo intermedio ai suoi figli in questo modo: il valore n di un nodo viene distribuito in parti uguali ai figli, ciascuno dei quali riceve dunque n/k. La quota ereditata viene sommata al valore di n di ciascun erede, e se l’erede non è una foglia, il risultato viene ulteriormente diviso ai figli, e così via. Si scriva una funzione eredita(t) che, ricevuto come argomento un albero t nel formato descritto sopra, restituisca il valore totale (n proprio più quota ereditata) del nodo foglia con valore massimo. Esempi:

t={val: 16, figli: [{val: 4},{val: 2, figli: [{val: 8},{val: 2}]}]}

eredita(t) → 13

eredita({val: 5}) → 5

function eredita(t) {
  if (t === undefined)
    return -Infinity;
  
  if (t.figli === undefined)
    return t.val;

  t.val = t.val/t.figli.length;
  let max = t.val;

  for (let figlio of t.figli) {
    figlio.val += t.val;
    if (figlio.val > max)
      max = figlio.val;

    if (figlio.figli) {
      let max_of_children = eredita(figlio);
      if (max_of_children > max)
        max = max_of_children
    }   
  }
  return max;
}