Skip to content

Instantly share code, notes, and snippets.

@MatteoRagni
Created February 19, 2017 14:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MatteoRagni/fdd3efc58f90451864ce0558bbcd402c to your computer and use it in GitHub Desktop.
Save MatteoRagni/fdd3efc58f90451864ce0558bbcd402c to your computer and use it in GitHub Desktop.
Fondamenti di Informatica: Lezioni 2015

Riassunto lezioni

(per il gruppo A-L)

24 Febbraio

  • Introduzione alla console
    • cd
    • pwd
    • ls
    • man e info
    • l'interprete ruby
    • introduzione al filesystem Unix
  • La funzione puts e l'interpolazione #{ ... }
  • La programmazione condizionale if then ... else ... end

Esercitazione: Radici di un polinomio di secondo ordine

3 Marzo

  • ripasso console
  • introduzione a irb (interprete ruby interattivo)
  • ripasso assegnazione
  • ripasso if
  • esteso ad if..elsif, if postfisso e operatore ternario ... ? ... : ...
  • introdotti not and or
  • introdotti cicli while e for
while condizione_vera

end

for i in 0..n do

end
  • introdotte le funzioni
  • definizione del fattoriale

$$ n! = 1 \cdot 2 \cdot \dots \cdot (n-1) \cdot n $$

  • funzione ricorsiva fattoriale

$$ n! = n \cdot (n-1)! $$

Esercitazione: coefficiente binomiale

$$ \left( \begin{array}{c} n \ k \end{array} \right) = \dfrac{n!}{k!(n-k)!} $$

# Funzione fattoriale
def fatt(n)
  ret = 1
  for i = 1..n do
    ret *= i
  end
  return ret
end

def coeff_bin(n,k)
  return fatt(n)/(fatt(k) * fatt(n - k))
end

puts "(10,3) = #{coeff_bin(10,3)}"

10 Marzo

  • attributi di accesso ai file
  • rendere eseguibile un file: chmod e shebang
  • ripasso funzioni, cicli for e while

Esercitazione: Definire la funzione di Collatz

$$ f(n) = \left{ \begin{array}{lr} \dfrac{n}{2} & \qquad (n,\mathrm{mod},2) = 0 \\ 3n + 1 & \qquad (n,\mathrm{mod},2) \ne 0 \\ \end{array} \right. $$

e definita la sequenza di Collatz la serie

$$ n_{i+1} = f(n_{i}) \qquad i \in 1,\dots, z \qquad z : f(n_z) = 1 $$

(ovvero la serie termina quando converge a 1). Trovare nell'insieme di valori iniziali $$n_1 \in 1, \dots, 10000$$ il numero $$n_1$$ a cui corrisponde lo $$z$$ maggiore.

#!/usr/bin/env ruby
# Attenzione: questo codice non funziona nell'interprete browser

def collatz(n)
  return (n % 2 == 0 ? n/2 : 3 * n + 1)
end

def serie(n)
  z = 0
  while n != 1
    z += 1
    n = collatz(n)
  end
  return z
end

# Main
z_max = 1
n_max = 1

for n in 1..10000 do
  z = serie(n)
  z_max, n_max = z, n if z > z_max
end

puts "Numero = #{n_max}, Iterazioni = #{z_max}"

Esercitazione proposta sotto forma di competizione in aula, vinta da Valentin Bernard. Soluzione: $$n_1 = 6171, z = 261$$

  • Introduzione teorica alle classi

17 Marzo

  • Ripasso delle classi
    • incapsulamento (separazione interfaccia implementazione)
    • polimorfismo (client utilizzano interfacce comuni)
    • ereditarietà (una classe può ereditare metodi e attributi da una sua classe antenata)
  • Panoramica delle classi Tipo di Dato fondamentali:
    • Numeric
    • Fixnum e Bignum
    • Array
    • Hash
    • String
    • NilClass
  • Le variabili e le Costanti
  • I numeri interi (Fixnum e Bignum) e i numeri Float
    • definizione e alcuni esempi di metodi associati
    • trasformazione da intero a float e viceversa (to_f, to_i, abs, floor, ceil, round)
  • Array
    • definizione di Array e di indice
    • size
    • attraversare gli Array

Esercitazione: Ottimizzare la soluzione del problema di Collatz in modo da espandere la ricerca ad ordini di grandezza superiore in un lasso di tempo ragionevole (nessuna ottimizzazione dal punto di vista di utilizzo della memoria)

#!/usr/bin/env ruby
# Attenzione: questo codice non funziona nell'interprete browser

# Parte il cronometro
tic = Time.new

def collatz(n)
  return (n % 2 == 0 ? n/2 : 3 * n + 1)
end

def serie(n, z_ary)
  z = 0
  while n != 1
    z += 1
    n = collatz(n)
    # Controlliamo se esiste già la soluzione per n
    # all'interno del nostro z_ary. Se la soluzione
    # è già stata calcolata, la utilizziamo e
    # usciamo dal ciclo while
    if z_ary[n]
      z += z_ary[n]
      break
    end
  end
  return z
end

# Main
MaxNum = 1000000
z_max = 1
n_max = 1
# Creiamo un nuovo Array, dentro il quale
# per ogni indice n salviamo la soluzione z
# della serie di Collatz
z_ary = Array.new(MaxNum + 1)
# Un Array creato in questo modo sarà un Array
# formato da n + 1 elementi tutti nil

puts "BEGIN"
for n in 1..MaxNum do
  z = serie(n, z_ary)
  # Salviamo l'ultimo risultato nell'Array
  z_ary[n] = z
  z_max, n_max = z, n if z > z_max
end
puts "END"

puts "Numero = #{n_max}, Iterazioni = #{z_max}"

# Si arresta il cronometro
toc = Time.new
puts "Computation time: #{(toc - tic)} seconds"

Il primo a risolvere il problema è stato Valentin Bernard.

24 Marzo

Leggete gli avvisi in cima alla pagina!

  • Ripasso rapido degli Array: indice a base 0, dimensione, attraversamento
  • Esercizio: Scrivere una funzione che trova massimo e minimo in Array anche se contiene tipi di dato diversi da Numeric
# funzione minmax
# input:
#   ary - un array
# output:
#   [min,max] - un array contenente il
#               valore minimo e massimo
# La funzione è robusta rispetto a tipi di dato
# che non sono numerici            
def minmax(ary)
  min = Float::INFINITY  # <- Non me lo ricordavo (-.-')
  max = -Float::INFINITY
  for i in 1...ary.size
    # Controlliamo se l'elemento è Numeric
    if ary[i].is_a?(Numeric) then
      min = ary[i] if ary[i] < min
      max = ary[i] if ary[i] > max
    end
  end
  return [min, max]
end
  • Esercitazione: Definire la funzione Algoritmo BUBBLE SORT (a lezione ne abbiamo presentate due versioni: la prima corretta, la seconda modificata a fini didattici)
# funzione bubble_sort
# input
#  a - un array non ordinato
# output
#  a - un array ordinato
# Ordina il contenuto di un Array
def bubble_sort(a)
  for j in 1..a.size
    for i in 0...a.size - j
      a[i], a[i+1] = a[i+1], a[i] if a[i] > a[i+1]
    end
  end
  return a
end

ary = (0..10).to_a.shuffle
bubble_sort(ary)

# funzione bubble_sort
# input
#  a - un array non ordinato
# output
#  a - un array ordinato
# Ordina il contenuto di un Array
# Versione didattica con un po'
# di stampa a schermo
def bubble_sort_did(a)
  puts "INITIAL = #{a} (#{a.size} elements)"
  for j in 1...a.size
    n = a.size - j
    puts "** Scan: 0 to #{n}"
    for i in 0...n
      if a[i] > a[i+1]
        puts "**** Change a[#{i}] = #{a[i]} <-> a[#{i + 1}] = #{a[i+1]}"
        a[i], a[i+1] = a[i+1], a[i]
      end
    end
    puts "** Array = #{a}"
    puts
  end
  puts "FINAL = #{a}"
  return a
end

bubble_sort_did(ary.shuffle)
  • Introduzione all'utilizzo dei blocchi
  • Introduzione alla scrittura dei blocchi
ary = (1..10).to_a.shuffle

# Utilizzo del blocco EACH
# con do |...| ... end
ary.each do |elemento|
  puts "Elemento = #{elemento}"
end

# con { |...| ... }
ary.each { |elemento|
  puts "Elemento = #{elemento}"
}

# utilizzo del blocco EACH_WITH_INDEX
ary.each_with_index { |elemento, indice|
  puts "Elemento[#{indice}] = #{elemento}"
}

# Costruire una nostra funzione each
def mio_each(ary)
  for i in 0...ary.size
    yield ary[i]
  end
end

# Costruire una nostra funzione each with index
def mio_each_with_index(ary)
  for i in 0...ary.size
    yield ary[i], i
  end
end

# Usare le nostre funzioni
mio_each(ary) do |e|
  puts "Elemento = #{e}"
end

mio_each_with_index(ary) do |e, i|
  puts "Elemento[#{i}] = #{e}"
end

10 Aprile

  • Introduzione a String. Inizializzazione (con o senza interpolazione) e metodi.
  • Introduzione ai Symbols, utilizzati come elementi di identificazione univoca all'interno del codice.
  • Introduzione agli Hash, contenitori di dati "ordinati per chiave arbitraria".
  • Esercitazione: scrivere una piccola rubrica personale, con i metodi per stampare i contatti, cercare e ordinare. Attenzione, per poter mettere un argomento di dafault in una funzione, il contenuto dell'argomento deve essere una variabile globale (quindi iniziare con la lettere $ - è un problema di visibilità della variabile), a differenza di quanto abbiamo visto a lezione (errore mio, scusate). Questo non è vero quando la funzione è un metodo che fa parte di una classe.
DB = [
  { :nome => "John", :cognome => "Dorian", :tel => "1234567"},
  { :nome => "Percival", :cognome => "Cox", :tel => "3425345"},
  { :nome => "Christopher", :cognome => "Turkleton", :tel => "calltur(k)"}, # :D
  { :nome => "Carla", :cognome => "Espinoza", :tel => "9838762"},
  { :nome => "Jan", :cognome => "Itor", :tel => "9873624"} # 8)
]

# stampa a schermo il contenuto di ary
# se non si passa nessun valore usa di
# default l'intero database DB
def stampa(db = DB)
  db.each { |e|
    puts "#{e[:nome]} #{e[:cognome]}: #{e[:tel]}"
  }
end

# cerca all'interno di un array tutti i record
# che hanno come valore value alla chiave key
# Restituisce un array popolato di tutti i record
# della query (ricerca)
def cerca(key, value, db = DB)
  record = []
  db.each { |e|
    record << e if e[key] == value
  }
end

# ordina gli elementi all'interno dell'array
# basandosi suala chiave key. Se non si passa nessun valore
# usa in automatico il simbolo :cognome
def ordina(key = :cognome, db = DB)
  for j in 1..db.size
    for i in 0...db.size - j
      db[i], db[i+1] = db[i+1], db[i] if db[i][key] > db[i+1][key]
    end
  end
  return db
end

# utilizziamo le funzioni che abbiamo definito
puts "Stampa tutta la rubrica"
stampa

puts "Cerca Percival e stampa"
stampa( cerca(:name, "Percival") )

puts "Ordina tutto l'array sulla base del nome"
stampa( ordina(:nome) )

21 Aprile

  • I block, costruire un integratore con il metodo dei trapezi, basato sulla relazione

$$ \int_{a}^{b}{f(x)dx} \approx \dfrac{1}{2}\dfrac{b - a}{n}~\sum_{s=0}^{n-1}{f(s \Delta x + a) + f((s+1) \Delta x + a)} $$

def integratore(a,b,n)
  deltax = (b - a)/n
  result = 0.0
  for s in 0...n
    x_s = s*deltax + a
    fA = yiald(x_s)
    fB = yield(x_s + deltax)
    result += 0.5 (fA + fB) * deltax
  end
  return result
end

risultato = integratore(1, 2, 100) { |x| Math::E**(Math::cos(x)) }
  • Le classi Proc
  • I file: apertura, lettura e scrittura
#!/usr/bin/env ruby

File.open("data.txt", "w") { |file|
  for i in 1..10 do
    for j in 1..10 do
      file.print "#{i + j}\t"
    end
    file.puts
  end
}

# Leggere il file
ary = []
File.foreach("data.txt") { |line|
  ary << line.chomp.split("\t").map { |e| e.to_i }
}

# come funziona map?
# proviamo a ricostruirlo
def my_map(ary)
  for i in 0...ary.size do
    ary[i] = yield(ary[i])
  end
  return ary
end
  • La gestione degli errori
def dividi(a,b)
  raise ArgumentError, "a è di tipo #{a.class} deve essere Numeric" if not a.is_a?(Numeric)
  raise ArgumentError, "b è di tipo #{b.class} deve essere Numeric" if not b.is_a?(Numeric)

  raise RuntimeError, "Divisione per zero" if b == 0

  return a/b
rescue RuntimeError
  if b == 0 then
    b += 10**(-15)
    retry
  end
ensure
  puts "Codice da eseguire SEMPRE"
end

8 Maggio

  • Implementare il gioco del mastermind
class GameError < RuntimeError; end

=begin

 +--- Mastermind --------------------------------+
 |                                               |
 | Attributi:                  Metodi:           |
 |  - @length (read)            - play() = Bool <----->
 |     lunghezza codice da        interfaccia    |
 |     indovinare                 per giocare    |
 |  - @range (read)               con l'utente   |
 |     range per i perks                         |
 |  - @guess (read)             - initialize(    |
 |     massimo numero di            length = 4,  |
 |     tentativi (negativo          range = 1..7,|
 |     per tent. infiniti)          guess = -1,  |
 |  - @try (priv)                   int = true   |
 |     tentativo                   ) = obj      <------>
 |  - @code (priv)                 inzializza un |
 |     codice da indovinare        nuovo oggetto |
 |  - @interactive (priv)          Mastermind    |
 |     interattività (puts)                      |
 |  - @crp (priv)               - delta(x,y) = d |
 |     perks corretti nella        delta di      |
 |     posizione corretta          kronecker     |
 |  - @cwp (priv)                                |
 |     perks corretti nella     - print_status() |
 |     posizione sbagliata        = nil          |  
 |                                 stampa il     |
 |                                 risultato del |
 |                                 l'ultimo try  |
 |                                               |
 |                              - count_perks()  |
 |                                = [@rcp, @rwp] |
 |                                 conta i perks |
 |                                 dell'ultima   |
 |                                 giocata       |
 +-----------------------------------------------+

=end


class Mastermind

  # Definizione degli attributi readable.
  attr_reader :length, :range, :try, :guess
  # questa è una scorciatoia che definisce i metodi
  #
  # # attr_reader :variabile
  # def variabile
  #   return variabile
  # end
  #
  # allo stesso modo esistono shortcuts per
  # gli attributi writable:
  #
  # attr_writable :variabile
  #
  # che definisce il seguente metodo:
  #
  # def variabile=(v)
  #  @variabile = v
  # end
  #
  # e per le variabili read&write:
  #
  # attr_accessor :variabile

  # oggetto = initialize(length, range, guess, interactive)
  #
  #  Costruttore dell'oggetto. Solitamente si inizializzano tutte le
  #  variabili dell'oggetto. Questo è l'unico punto in cui l'utente
  #  ha controllo sulla partita.
  def initialize(length = 4, range = 1..7, guess = -1, int = true)
    # controllo sull'argomento length
    raise ArgumentError, "length" if not @length.is_a?(Fixnum)
    @length = length

    # Controllo sull'argomento guess
    raise ArgumentError, "guess" if not @guess.is_a?(Fixnum)
    @guess = guess

    # controllo sull'argomento range, che può essere un
    # Fixnum o un Range
    if range.class == Fixnum and range > 1 then
      @range = 1..range
    elsif range.class == Range then
      @range = range
    else
      raise ArgumentError, "range"
    end

    if int == true or int == false then
      @interactive == int
    else
      raise ArgumentError, "int"
    end

    # inizializza le variabili private
    @code = []
    @rcp = 0
    @rwp = 0
    @try = 0
  end # initialize

  # (true, false) = play()
  #
  # La partita segue questa logica
  #
  #  reset -> player = yield(@rcp, @rwp)  <----+
  #            |                               |
  #            +-> count_perks(player)         |
  #                 |                      True|
  #                 +-> (logica giocata)?* ----+
  #                  False|                        False
  #                       +-> (@rcp == @length)? -+-----> Sconfitta
  #                                               |       return false
  #                                               |True
  #                                               +-----> Vittoria
  #                                                       return true
  #
  # * logica giocata:
  # se @guess > 0:
  #    @try <= @guess and @rcp != @length
  # altrimenti
  #    @rcp != @length
  #
  # La logica giocata tiene conto della strategia di uscita per l'utente:
  # se l'utente gioca "exit", "quit", ... etc. il programma esce.
  def play
    reset

    while (@guess > 0 ? @try <= @guess : true) and @rcp != @length
      begin
        player = yield(@rcp, @rwp)

        raise Exception if %w|exit quit close q x c|.include?(player)
        raise GameError, "Play must be an Array. I received #{player.class}" if not(player.is_a?(Array))
        raise GameError, "Play must be of #{@length} perks. I received #{player.size} perks" if player.size != @length
        player.each { |e|
          raise GameError, "Perks must be Fixnum, not #{e.class}" if not(e.is_a?(Fixnum))
        }

        count_perks(player)
        print_status

      rescue GameError => err
        puts "[ERROR] #{err.message}"
        if @interactive then
          retry
        else
          exit(1)
        end
      rescue Exception
        puts "The code is #{@code}. Bye looser!" if @interactive
        exit(0)
      end
    end

    if @rcp == @length then
      puts "[PC] YOU WIN!" if @interactive
      return true
    else
      puts "[PC] Stupid LOOSER!" if @interactive
      return false
    end
  end # play

  # nil = print_status
  #  Stampa a schermo lo stato dell'ultima giocata
  def print_status
    if @interactive then
       puts "[PC] Perks in correct position: #{@rcp}"
       puts "[PC] Perks in wrong position:   #{@rwp}"
      print "[PC] Tries: #{@try}"
      puts (@guess > 0 ? " of #{@guess}" : "")
    end
  end # print_status

  private

  # d = delta(x,y)
  #  Stampa a schermo il detla di kronecker:
  #  d = 1 se x == y
  #  d = 0 se x != y
  def delta(x, y)
    return (x == y ? 1 : 0)
  end # delta

  # @code = reset()
  #  resetta una partita generando un nuovo codice.
  def reset
    puts "[PC] Generating a new game..." if @interactive

    @code = []
    @rcp = 0
    @rwp = 0
    @try = 0

    @code = Array.new(@length) { Random.rand(@range) }
  end # reset

  # [@rcp, @rwp, @try] = count_perks(player)
  #   Calcola la giocata sulla base del codice inserito dal
  #   giocatore. Utilizza le formule in calce a questo script.
  def count_perks(player)
    @rcp = 0
    for j in 0...@length do
      @rcp += delta(@code[j], player[j])
    end

    @rwp = 0
    for i in @range do
      alpha, beta = 0, 0
      for j in 0...@length do
        alpha += delta(@code[j], i)
        beta += delta(player[j], i)
      end
      @rwp += [alpha, beta].min
    end
    @rwp -= @rcp

    @try += 1
    [@rcp, @rwp, @try]
  end # count_perks
end # class Mastermind

# Ambiente di test per la classe
if $0 == __FILE__ then

  game = Mastermind.new

  game.play { |rcp, rwp|
    print "Insert: "
    s = gets.chomp
    if %w|quit close x c q exit|.include?(s)
      s
    else
      s.split(/\s*,\s*/).map { |e| e.to_i }
    end
  }

end

15 Maggio

  • Risoluzione esercizio d'esame (problema del campionato). La soluzione prevede l'uso di classi specializzate che ereditano dalla classe Hash. Questo ci ha permesso di introdurre le keyword super (richiama lo stesso metodo della classe precedente, passando gli stessi argomenti), e la self.
#!/usr/bin/env ruby
=begin

  http://goo.gl/UYM4kv -> Testo otriginale esercizio
  http://goo.gl/ZuuXQf -> campionato.txt

  -- esercizio ispirato da ---
  Esercizi di programmazione in C
  Esercitazioni per il corso di Fondamenti di Informatica
  Fulvio Corno Silvia Chiusano Politecnico di Torino – Dipartimento di Automatica e Informatica
  -------------------------

  Realizzare un programma in grado di calcolare la classifica di un campionato di calcio giocato
  tra N squadre, numerate consecutivamente a partire da 0.
  I risultati delle partite sono memorizzati in un file ASCII il cui nome e' passato come
  primo argomento alla funzione classifica.

  Questo file contiene un risultato per riga nel formato:

  squadra1 squadra2 goal1 goal2

  ove squadra1 e squadra2 sono stringhe con i nomi delle squadre che si sono incontrate
  mentre e goal1 e goal2 sono le reti segnate da ciascuna squadra.
  Le colonne sono separate da \t.

  Il programma deve calcolare e riempire una hash di output.
  Le chiavi della hash sono stinghe e sono i nomi delle squadre.
  I valori della hash sono a loro volta delle hash chiave -> valore che contengono

  :partite => numero di partite giocate
  :punti   => i punti conseguiti (si ricorda che la vittoria vale tre punti ed il pareggio un punto)
  :vinte   => numero di partite vinte
  :perse   => numero di partite perse

  A titolo di esempio, supponendo che il file CAMPIONATO.TXT contenga le seguenti informazioni:

  ROSSI BIANCHI 1 1
  VERDI NERI 1 0
  ROSSI NERI 2 0

  allora la hash risultante deve contenere le seguenti informazioni:

  {
     'ROSSI'   => {  :partite => 2, :punti => 4, :vinte => 1, :perse => 0 },
     'BIANCHI' => {  :partite => 1, :punti => 1, :vinte => 0, :perse => 0 },
     'NERI'    => {  :partite => 2, :punti => 0, :vinte => 0, :perse => 2 },
     'VERDI'   => {  :partite => 1, :punti => 3, :vinte => 1, :perse => 0 },
  }

=end

################################################################################
# RISOLVERE L'ESERCIZIO QUI
################################################################################

require 'pry'

class CampArgumentError < ArgumentError; end
class CampRuntimeError < RuntimeError; end

class Partita < Hash
  def initialize(ary)
    super
    if ary.is_a?(Array) and ary.size == 4 then
      result = ary[2] <=> ary[3]
      self[ary[0]], self[ary[1]] = result, -result  
    else
      raise ArgumentError "Wut??"
    end
  end
end

class Squadra < Hash
  def initialize
    super
    self[:partite] = 0
    self[:vinte]   = 0
    self[:perse]   = 0
    self[:punti]   = 0
  end

  def result(p)
    self[:partite] += 1
    if p == 1 then
      self[:vinte] += 1
      self[:punti] += 3
    elsif p == 0 then
      self[:punti] += 1
    elsif p == -1 then
      self[:perse] += 1
    end
  end
end

class Campionato < Hash

  def initialize(file)
    if File.exist?(file) then
      File.foreach(file) { |line|
        self.+ Partita.new(line.chomp.split("\t"))
      }
    else
      raise CampArgumentError, "Non esiste il file: #{file}"
    end
  end

  def +(p)
    if p.is_a?(Partita) then
      p.keys.each { |squadra|
        self[squadra] = Squadra.new if not self.keys.include?(squadra)
        self[squadra].result(p[squadra])        
      }
    else
      raise CampArgumentError, "Mi aspetto una partita, non #{p.class}"
    end
  end

end

def campionato(file)
  return Campionato.new(file)
end

RESULT = campionato("campionato.txt")
binding.pry

################################################################################
#
################################################################################
  • Risoluzione dell'esercizio del metodo di approssimazione zeri funzione star, ponendo l'accento sulla efficienza del codice.
#!/usr/bin/env ruby
=begin

  http://goo.gl/ -> Testo originale esercizio

  Scrivere la funzione `star` che si aspetta 3 argomenti

  x0   = approssimazione iniziale della radice
  x1   = seconda approssimazione iniziale della radice
  tol  = tolleranza ammessa
  imax = massimo numero di iterate

  ed un blocco che restituisce il valore della funzione della
  quale cechiamo lo zero.

  La funzione deve implementare il metodo iterativo STAR per il calcolo
  dello zero di una funzione. La funzione restituisce una hash con 3 chiavi

    :solution  => con la soluzione
    :iter      => un intero con il conteggio del numero delle iterate fatte
    :converged => true/false (vero se arrivato a convegenza)

  Se x0, x1, x2 sono le ultime tre
  iterate e f0 = f(x0), f1 = f(x1), f2 = f(x2) allora x3 con il metodo STAR
  si calcola con

                     f2
    x3 = x2  - ---------------
               d20 + d21 - d10

  dove

           f2 - f0              f2 - f1            f1 - f0
    d20 = ---------      d21 = ---------    d10 = ---------
           x2 - x0              x2 - x1            x1 - x0

  quindi il metodo star richiede le ultime 3 approssimazioni della radice
  mentre all'inizio ne forniamo solo 2.
  Per far partire il metodo usiamo quindi UN SOLO passo del metodo
  delle secanti

                 f1              f1 - f0
    x2 = x1  - -----      d10 = ---------
                d10              x1 - x0

   Le iterate terminano se si supera imax (fallimento) o se |f(xk)| <= tol (successo)

ESEMPIO:
  approssimare la radice di f(x) = (3x/2)**4-1

  x0   = 10
  x1   = 9
  tol  = 1e-12
  imax = 20
  sol  = star(x0,x1,tol,imax) { |x| (3*x/2)**4-1 }

  sol è una hash che contiene sol = { :solution => ..., :iter => ... , :converged => ...}

=end

################################################################################
# RISOLVERE L'ESERCIZIO QUI
################################################################################
require 'pry'
def star( x0, x1, tol, imax )

  # Controllo degli argomenti
  raise ArgumentError, "Fornire un blocco" if not block_given?
  if x0.is_a?(Numeric) then
    x0 = x0.to_f
  else
    raise ArgumentError, "x0: must be Numeric, passed #{x0.class}"
  end
  if x1.is_a?(Numeric) then
    x1 = x1.to_f
  else
    raise ArgumentError, "x1: must be Numeric, passed #{x1.class}"
  end
  if tol.is_a?(Numeric) then
    tol = tol.to_f
  else
    raise ArgumentError, "tol: must be Numeric, passed #{tol.class}"
  end
  if imax.is_a?(Numeric) then
    imax = imax.to_i
  else
    raise ArgumentError, "imax: must be Numeric, passed #{imax.class}"
  end

  # inizializzazione del risultato
  ret = { :solution => nil, :iter => 0, :converged => false}
  # Definite qui per motivi di visibilità
  x2, f0, f1 = nil, nil, nil

  # Main loop da eseguire fino a quando
  while (ret[:iter] <= imax)
    #binding.pry
    if x2
      # Effettivo calcolo del metodo star
      f2 = yield(x2)
      break if f2.abs < tol
      d20 = (f2 - f0)/(x2 - x0)
      d21 = (f2 - f1)/(x2 - x1)
      d10 = (f1 - f0)/(x1 - x0)

      f0 = f1
      f1 = f2
      x0 = x1
      x1 = x2
      x2 -= f2/(d20 + d21 - d10)
    else
      # da eseguire solo la prima volta
      f0 = yield(x0)
      f1 = yield(x1)
      d10 = (f1 - f0)/(x1 - x0)

      x2 = x1 - f1/d10
    end
    ret[:iter].next
  end
  ret[:solution]   = x2
  ret[:converged]  = (ret[:iter] <= imax ? true : false)
  return ret
end

x0   = 10.0
x1   = 9.0
tol  = 1e-12
imax = 20
sol  = star(x0, x1, tol, imax) { |x| (3*x/2)**4-1 }
pp sol

################################################################################
#
################################################################################

22 Maggio

  • Implementare la classe PlotAscii che data una funzione o dei dati li stampa a schermo sotto forma di grafico
# PlotAscii
# Require returns true if it is possible to
# load a gem, false if it is not possible
$COLORIZE = require 'colorize'

class Array
  # Expands Array standard class to add a method
  # that is called min_at, that returns the index
  # of the minimum element of an Array
  def min_at
    return index(min)
  end

  # Evaluates distances froma a given target
  def distances(target)
    map { |e| (e.is_a?(Numeric) ? (e.to_f - target)**2 : nil) }
  end

  # Finds the index at wich we have the minimum distance
  def find_min_at(target)
    distances(target).min_at
  end
end # class Array

# This extension of the class String will be performed only if
# and only if the colorize gem is not available
class String
  # The following is an hash that contains the escape
  # sequence for colors. To print a coloured string whe need
  # to enclose it in a first escape sequence and than close the
  # string with a resetting escaped sequence
  TXTclr = {
    :black  => "\e[0;30m",
    :red    => "\e[0;31m",
    :green  => "\e[0;32m",
    :yellow => "\e[0;33m",
    :blue   => "\e[0;34m",
    :purple => "\e[0;35m",
    :cyan   => "\e[0;36m",
    :white  => "\e[0;37m"
  }
  TXTrst  =  "\e[0m"

  # define a new method with the same name as the symbol in TXTclr hash
  # that returns a coloured string embraced in ecape tags.
  TXTclr.keys.each do |color|
    define_method color do
      TXTclr[color] << self << TXTrst
    end
  end
end if not $COLORIZE # class String


class PlotAscii
  PX_ON  = '*'
  PX_OFF = ' '

  @@instances = 0
  attr_reader :xstep, :lims, :ncols, :nrows

  def initialize(ncols = 60, nrows = 20, leftmargin = 2)
    # Checking arguments
    if nrows.is_a?(Fixnum) then
      if nrows > 2 then
        @nrows = nrows
      else
        raise ArgumentError, "Rows number must be positive and greater than 2"
      end
    else
      raise ArgumentError, "Rows number must be a Fixnum"
    end
    if ncols.is_a?(Fixnum) then
      if ncols > 2 then
        @ncols = ncols
      else
        raise ArgumentError, "Cols number must be positive and greater than 2"
      end
    else
      raise ArgumentError, "Cols number must be a Fixnum"
    end

    if leftmargin.is_a?(Fixnum) then
      if leftmargin >= 0 then
        @leftmargin = leftmargin
      else
        raise ArgumentError, "Left margin must be positive"
      end
    else
      raise ArgumentError, " number must be a Fixnum"
    end
    @lims = {}
    lims({ :x0 => 0, :x1 => @ncols,
           :y0 => 0, :y1 => @nrows })

    @functions = []

    @@instances += 1
    @title = "Plot #{@@instances}"
    clear
  end

  def lims(h)
    h.keys.each do |e|
      raise ArgumentError, "#{e} must be a Numeric" if not h[e].is_a?(Numeric)
    end

    @lims[:x0] = h[:x0].to_f if h[:x0]
    @lims[:x1] = h[:x1].to_f if h[:x1]
    @lims[:y0] = h[:y0].to_f if h[:y0]
    @lims[:y1] = h[:y1].to_f if h[:y1]

    raise ArgumentError, ":x1 must be greater then :x0" if @lims[:x0] >= @lims[:x1]
    raise ArgumentError, ":y1 must be greater then :y0" if @lims[:y0] >= @lims[:y1]

    @lims[:dx] = (@lims[:x1] - @lims[:x0])/@ncols.to_f
    @lims[:dy] = (@lims[:y1] - @lims[:y0])/@nrows.to_f

    @xpoints = Array.new(@ncols) { |i| (@lims[:x0] + i * @lims[:dx]) + @lims[:dx]/2 }
    @ypoints = Array.new(@ncols) { |i| (@lims[:y0] + i * @lims[:dy]) + @lims[:dy]/2 }

    return self
  end

  def xlim=(*varg)
    if varg.size == 2 then
      return lims({:x0 => varg[0], :x1 => varg[1]})
    elsif varg.size == 1 then
      if varg[0].is_a?(Array) then
        return lims({:x0 => varg[0][0], :x1 => varg[0][1]})
      elsif varg[0].is_a?(Hash)
        return lims(varg[0])
      end
    else
      raise ArgumentError, "Wrong number of argument in xlim: expected 1 or 2, got #{varg.size}"
    end
  end

  def ylim=(*varg)
    if varg.size == 2 then
      return lims({:y0 => varg[0], :y1 => varg[1]})
    elsif varg.size == 1 then
      if varg[0].is_a?(Array) then
        return lims({:y0 => varg[0][0], :y1 => varg[0][1]})
      elsif varg[0].is_a?(Hash)
        return lims(varg[0])
      end
    else
      raise ArgumentError, "Wrong number of argument in ylim: expected 1 or 2, got #{varg.size}"
    end
  end

  def add_function(ch, desc, color = nil, &block)
    @functions << {
      :class => :function,
      :ch => (color ? ch.send(color) : ch),
      :desc => desc,
      :block => block
    }

    return self
  end

  def add_data(ch, desc, color = nil)
    data = yield

    raise RuntimeError, "Block must pass an Array, not #{data.class}" if not data.is_a?(Array)
    data.each do |point|
      raise RuntimeError, "Not valid data in add_data" if not point.is_a?(Array)
      raise RuntimeError, "Not valid data in add_data" if not (point[0].is_a?(Numeric) and point[0].is_a?(Numeric))
    end

    @functions << {
      :class => :data,
      :ch => (color ? ch.send(color) : ch),
      :desc => desc,
      :data => data
    }

    return self
  end

  def title=(s)
    @title = s.to_s
  end

  def draw
    @functions.each do |h|
      case h[:class]
      when :function
        (0...@ncols).each do |i|
          y = h[:block].call(@xpoints[i])
          j = @ypoints.find_min_at(y)
          set_pixel(i, j, h[:ch])
        end
      when :data
        h[:data].each do |p|
          i = @xpoints.find_min_at(p[0])
          j = @ypoints.find_min_at(p[1])
          set_pixel(i, j, h[:ch])
        end
      end
    end
  end

  def to_s
    draw
    margin = ' ' * @leftmargin
    label_width = [@lims[:y0].to_s.size, @lims[:y1].to_s.size].max
    label_margin = ' ' * label_width + ' '

    # Ticks (upper Y)
    @string =  margin + "y".rjust(label_width) + ' ' + "▲\n" + margin + label_margin + "│\n"
    @string << margin + @lims[:y1].to_s.rjust(label_width) + ' ┼' + "\n"

    # Effective plot
    @plot.reverse.each { |line| @string << margin + label_margin + '│' + line.join + "\n" }

    # Ticks (lower Y)
    @string << margin + @lims[:y0].to_s.rjust(label_width) + ' ┼' + ('─' * @ncols) + "┼─► x \n"
    @string << margin + label_margin + @lims[:x0].to_s + (' ' * (@ncols - @lims[:x0].to_s.size - @lims[:x1].to_s.size)) + @lims[:x1].to_s + "\n"
    @string << "\n Legend:\n"
    @functions.each do |h|
      @string << "  [" << h[:ch] << "] : " + h[:desc] << "\n"
    end

    return @string
  end
# │ ▲ ┼ ─ ►
  private

  def clear
    @plot = Array.new(@nrows) { Array.new(@ncols) { PX_OFF }}
  end

  def set_pixel(i,j, ch = PX_ON)
    if in_range?(i,j)
      @plot[j][i] = ch
    end

    return self
  end

  def set_pixels(ary, ch = PX_ON)
    ary.each do |p|
      set_pixel(p[0], p[1], ch)
    end

    return self
  end

  def in_range?(i, j)
    return ((0...@ncols).include?(i) and (0...@nrows).include?(j))
  end

  def pixel?(i, j)
    return false if not in_range?(i,j)
    return (not @plot == PX_OFF)
  end
end

if __FILE__ == $0 then
  plot = PlotAscii.new
  plot.lims({:x0 => -1, :x1 => 10, :y0 => -2, :y1 => 10})

  # Simple plot example
  plot.add_function("*", "sin(x)", :red) { |x| Math::sin(x) }

  # Parametric plot example
  [0, 1, 2].each do |p|
    plot.add_function("#{p}", "cos(x) + #{p}", :blue) { |x| Math::cos(x) + p }
  end

  # Point data example
  data = []
  (0).upto(10) do |e|
    data << [e, e]
  end
  plot.add_data(".", "data", nil) do; data; end

  # Print out a plot
  puts plot.to_s
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment