Displaying articles with tag

Terzo Incontro del Ruby Social Club a Milano

Posted by Chiaroscuro, Mon Sep 15 12:56:00 UTC 2008

1 comment | Filed Under: Non Solo Ruby | Tags:

Ruby vs Chuck Norris

Posted by jeko, Thu Jul 31 13:30:00 UTC 2008

Ora non sono in ufficio, ma tutti pensano che stia lavorando febbrilmente, perché ho fatto un bot jabber sulla mia postaizone che riceve comandi XMPP e riproduce suoni di tastiera. Sono sicuro che su Travian non sto subendo attacchi perché con Ruby e Watir ho fatto un Travian Bot (grazie kiaro per il supporto! :) ) che mi avverte in caso di attacchi (sempre jabber) e se non dovessi prendere provvedimenti, pensa a "schivare" l'attacco autonomamente.

Il mio editor per blog e CMS è ora msWord, perché ho fatto una cliplet (ancora non ho trovato un nome migliore per gli script che lavorano in clipboard) che trasforma il codice che copio da word in XHTML 1.1 valido (e senza m$ zozzerie) pronto da essere incollato nel mio blog (che non ho). L'altro ieri un mio collega (un redattore, non un tecnico) era disperato perché doveva metter mano a centinaia di pagine web... gli ho spiegato ERB, abbiamo fatto insieme uno script semplice semplice, e ora, oltre ad aver finito il suo lavoro in tempo, è andato in libreria a comprarsi "learn to program" di chris pine, e ieri mi ha spedito il suo primo programma! (che fa una pagina web con scritto Jeko Frocio migliaia di volte e con font diversi... mi ha commosso...).

Ho fatto un Bot che spara insulti temporizzati a caso su Jabber e con Capistrano faccio il deploy dei progetti php. Ho fatto uno script per manipolare i dati EXIF delle mie foto e recuperare solo quelle photoshoppate (avevo mischiato tutto e stavo per fare un gran casino). Quando flickr era passata a yahoo io non volevo reiscrivermi (Cosa che ho fatto puntualmente) e avevo fatto uno script per backupparmi tutte le foto di flickr e mettele su amazon S3, e con ActiveRecord ho migrato tutto il database di un nostro cliente dal vecchio CMS al nostro CMS, suppongo il vecchio fornitore non sia contento dal momento che riteneva che la propria struttra dati non fosse migrabile...

Ad ogni modo, per riallacciarmi al titolo di questo aricolo, ho iniziato a compilare una lista sui possibili usi di ruby, traendo ispirazione dai Sacri Testi di Chuck.

  • fa spuntare il sorriso
  • stimola la mente
  • fa ricrescere i capelli
  • elimina la forfora
  • sgorga gli scarichi
  • lubrifica e protegge il motore
  • attrae persone del sesso opposto
  • chiude il buco dell'ozono
  • è ottimo come propellente per razzi
  • diminuisce i tassi d'interesse
  • inverte la polarità terrestre
  • elimina la terra dai funghi
  • uccide gli insetti fastidiosi
  • previene il formarsi della lanuggine dall'ombelico
  • associato al dna di una zanzara trovato nell'ambra, potrebbe far andare la juve in serie B. Definitivamente.
  • rocco siffredi nasconde sotto il cuscino un manuale di ruby
  • fa morire il cattivo alla fine del film
  • fa venire gli addominali a tartaruga
  • se masterizzi un cd con ruby, e lo metti sul lunotto posteriore della macchina, puoi sfuggire agli autovelox
  • mio cugino ha scoperto ruby, non l'ha usato, ed è morto
  • i numeri di lost sono l'MD5 di ruby
  • allontana il babau
  • Zidane progamma in dot net. Materazzi ai mondiali gli ha detto che lui usa ruby.
  • toglie le macchie impossibili
  • le tette di giorgia palmas sono state fatte con ruby
  • ...anche la ricetta della nutella
  • ti fa parlare come un vero pirata... arrr...
  • Yukihiro Matsumoto ha programmato ruby *...Chuck Norris ha programmato Yukihiro Matsumoto
  • ti fa passare il test del palloncino
  • evita che la gomma si attacchi al lavoro del tuo dentista
  • ci puoi decifrare le scritte in sanscrito sulle braccia dei calciatori
  • trasforma i puffi in oro
  • l primo giorno Dio scrisse
1
2
3
4
5
6
7
8
9
10
module All
       class Sky
               def initialize
               end
       end
       class Earth
               def initialize
               end
       end
end
  • allenta i bulloni arrugginiti
  • se programmi in ruby in africa, va in crash un programma java in nord america

La cosa curiosa è che io ho scritto solo le prime 2, le altre le ho fatte a caso con uno script in Ruby :)

Bella Jeko

Stefano Guglielmetti alias Jeko*

Schiavo dell'industria informatica italiana dal 1996, anno in cui entra in Flashnet, dalla quale esce nel 1998 con la qualifica di webmaster per fondare la sua prima società (flux s.n.c) a 19 anni con 3 amici, due dei quali sono tutt'ora soci (a prova del fatto che non è un infame totale) Sempre nel 1998 inizia l'avventura con Interact, di cui diventa socio, e in un batter di ciglio sono passati quasi 10 anni. Quando ormai aveva preso gran parte delle speranze maturate nella breve fase dell'entusiasmo iniziale, scopre ruby e i metodi agili, e, se non altro, ora si diverte. (quando fa il programmatore e non il manager - project manager - direttore tecnico - chaos wrangler)

3 comments | Filed Under: Filosofia Non Solo Ruby | Tags:

Perchè siamo un gruppo

Posted by reggie, Fri Jun 13 06:00:00 UTC 2008

3 comments | Filed Under: FAQ Filosofia Rails | Tags:

Classi senza Burocrazia

Posted by Chiaroscuro, Mon Nov 05 01:51:00 UTC 2007

Prendiamo un caso base e procediamo ad astrarlo un passo alla volta. Partiamo con una classe Cat, il mio animale preferito. Come è noto un gatto può essere univocamente e universalmente identificato in base a nome, sesso e colore:

1
2
3
4
5
6
7
  class Cat
    attr_reader :name, :gender, :colour

    def initialize name, gender, colour
      @name, @gender, @colour = name, gender, colour
    end
  end    

Perdonatemi l’idioma per l’assegnamento degli attributi di classe, ma è molto rapido da utilizzare con il copy and paste.

Il primo passo consisterà nel sostituire initialize con qualcosa di più conciso e immediato. Idealmente vorremmo poter usare questo comando nel corpo della classe per poter inizializzare gli attributi:


  init_with :name, :gender, :colour

Il lettore più attento mi dirà che a questo scopo esiste già Struct che mi permette di scrivere concisamente:


  Cat = Struct.new :name, :gender, :colour

E’ vero, Struct è molto utile e rapido, ma presenta anche alcuni problemi. Il problema principale per me consisteva nella difficoltà di trovare al volo classi definite con Struct all’interno del codice. L’occhio è abituato a cercare una struttura di classe, con un certo tipo di indentazione e syntax-colouring e una classe definita con Struct tende a sfuggire e a confondersi con il resto del codice. Un altro problema, a volte molto fastidioso, è che Struct non rappresenta le sue proprietà come attributi di classi, quelli con la chiocciolina per intenderci, ma probabilmente con una hash table. Questo spesso confonde le nostre aspettative su certe operazioni possibili.

Affrontiamo ora il problema di definire init_with. Questo metodo, per poter essere disponibile nel corpo di una qualunque classe, deve essere definito come metodo di classe in Object, oppure nella classe Module, che lo rende disponibile a tutte le classi e tutti i moduli. Riapriamo la classe Module e definiamo un metodo che prende un numero variabile di argomenti:

1
2
3
4
5
6
class Module

    def init_with *args
    end
    
end

Il metodo init_with dovrà generare un costruttore initialize nel contesto della classe che invoca init_with:

1
2
3
4
5
6
7
8
9
10
  def init_with *args
    args_list = args.map {|e| e.to_s }
    attributes_list = args_list.map {|e| '@'+e }
        
    class_eval %{
      def initialize #{args_list.join ','}
        #{attributes_list.join ','} = #{args_list.join ','}
      end
    }
  end    

Innanzitutto generiamo una lista di attributi espressi come stringhe anzichè come simboli:


  args_list = args.map {|e| e.to_s }

Poi decoriamo ogni attributo con una chiocciolina:


  attributes_list = args_list.map {|e| '@'+e }

Infine valutiamo nel contesto della classe chiamante un template di initialize:

1
2
3
4
5
  class_eval %{
    def initialize #{args_list.join ','}
      #{attributes_list.join ','} = #{args_list.join ','}
    end
  }

E questo è quanto. Ora possiamo definire il nostro gatto con un semplice:

1
2
3
4
  class Cat
    attr_reader :name, :gender, :colour
    init_with   :name, :gender, :colour
  end    

mmmhh.. ancora troppe ripetizioni? Facciamo un ultimo sforzo:

1
2
3
4
5
6
7
8
9
10
11
  class Module
    def init_with_readers *args
      sym_list = args.map {|e| ":#{e}" }
      sym_list_string = sym_list.join ', '
        
      class_eval %{
        attr_reader #{sym_list_string} 
        init_with #{sym_list_string}
      }
    end    
  end

Questo codice genera sia i reader che il costruttore in un unico passo. Notate che abbiamo anche riutilizzato init_with senza dover ripetere il codice di generazione per il costruttore.

Il nostro gatto in ultima istanza può ora essere definito come:

1
2
3
  class Cat
    init_with_readers :name, :gender, :colour
  end    

Ruby non è fatto per essere lasciato in pace. Quando scorgete una opportunità per rendere il codice più chiaro e rimuovere ripetizioni, mettete mano alla metaprogrammazione!

15 comments | Filed Under: Filosofia Linguaggio Metodologia | Tags:

Il mio primo Ruby Quiz

Posted by paolo, Thu Feb 08 19:17:00 UTC 2007

1
2
3
4
5
6
$ lcd.rb 012
 -     -
| | |   |
       -
| | | |
 -     -

Il quiz prevede anche un parametro in input (-s) che indica la dimensione dei numeri, -s sta per size. quindi:

1
2
3
4
5
6
7
8
$ lcd.rb -s 2 012
 --      --
|  |  |    |
|  |  |    |
         --
|  |  | | 
|  |  | | 
 --      --

Per semplicità io partirò con un default size di 1, aggiungiendo il supporto al size in seguito.

Partiamo creando la classe principale Lcd, la relativa classe di test LcdTest e il RakeFile per lanciare i nostri test:

in lcd.rb:
1
2
3
4
5
class Lcd
  def Lcd.process number
    '' # do nothing righ now
  end
end
in lcd_test.rb:
1
2
3
4
require 'test/unit'
require 'lcd'
class LcdTest < Test::Unit::TestCase
end
in RakeFile:
1
2
3
4
5
6
7
8
9
10
11
12
require 'rake'
require 'rake/testtask'

desc 'Default: run unit tests.'
task :default => :test

desc 'Test the lcd'
Rake::TestTask.new(:test) do |t|
  t.libs << '.'
  t.pattern = '*_test.rb'
  t.verbose = true
end

Ora per lanciare i test basta scrivere rake dalla shell e, siccome non ci sono test, avremo un messaggio d’errore. In verità per lanciare i test non mi serve fare un RakeFile perchè potrei direttamente lanciare lcd_test.rb. Il fatto di avere rake mi torna utile quando voglio lanciare più di un file di test contemporaneamente, quindi il mio approccio, quando avrò più di una classe di test, sarà indicativamente: lancio il test singolo con xxx_test.rb per testare la classe xxx e lancio rake quando voglio testare tutto.

Iniziamo con lo scrivere 2 test a caso, uno per il numero ‘0’ e uno per il numero ‘5’ in modo da avere qualcosa su cui provare la nostra classe Lcd:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class LcdTest < Test::Unit::TestCase
  def setup
    @lcd0 = File.new('0.txt').read
    @lcd5 = File.new('5.txt').read
  end
  
  def test0
    assert_equal(@lcd0, Lcd.process(0))
  end
  
  def test5
    assert_equal(@lcd5, Lcd.process(5))
  end
end

I file 0.txt e 5.txt contengono rispettivamente:

1
2
3
4
5
 -
| |

| |
 - 
e:
1
2
3
4
5
 -
|  
 -
  |
 -  

Tenere i casi di test fuori da lcd_test.rb mi permette di tenere la classe pulita. Ovviamente il test ora fallisce perchè non c’è l’implementazione del metodo Lcd.process(...).

Iniziamo a pensare a come risolvere il problema. I display LCD sono composti da 7 segmenti che illuminandosi o rimanendo spenti producono i vari numeri.

Quindi decido di implementare la rappresentazione di ogni numero con una maschera binaria di 7 bit che mi identificano se i vari led sono accesi(1) o spenti(0):

1
2
3
4
5
6
7
8
9
10
11
n.   01234567 (led)
0 => 1110111
1 => 0010010
2 => 1011101
3 => 1011011
4 => 0111010
5 => 1101011
6 => 1101111
7 => 1010010
8 => 1111111
9 => 1111011

ovvero in Ruby:

1
2
3
4
5
6
7
8
9
10
11
12
MASKS ={
  '0' => 0b1110111,
  '1' => 0b0010010,
  '2' => 0b1011101,
  '3' => 0b1011011,
  '4' => 0b0111010,
  '5' => 0b1101011,
  '6' => 0b1101111,
  '7' => 0b1010010,
  '8' => 0b1111111,
  '9' => 0b1111011
}

Non so bene perchè ma mi viene spontaneo wrappare i numeri Lcd e le maschere in un’oggetto separato:

in lcd_number.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class LcdNumber
  attr_reader :value, :mask
  MASKS ={'0' => 0b1110111,
      '1' => 0b0010010,
      '2' => 0b1011101,
      '3' => 0b1011011,
      '4' => 0b0111010,
      '5' => 0b1101011,
      '6' => 0b1101111,
      '7' => 0b1010010,
      '8' => 0b1111111,
      '9' => 0b1111011}
  
  def initialize value
    @value = value
    @mask = MASKS[value.to_s]
  end
end

e relativo test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class LcdNumberTest < Test::Unit::TestCase
  def setup; 
    @one = LcdNumber.new 1 
    @two = LcdNumber.new 2
  end

  def test_mask
    assert_equal 0b0010010, @one.mask
    assert_equal 0b1011101, @two.mask
  end
  
  def test_value
    assert_equal 1, @one.value
    assert_equal 2, @two.value
  end
end

Oddio! Appena inizio a parlare di maschere di bit la gente tipicamente inizia ad allarmarsi… di sicuro le operazioni bitwise non sono tra le più semplici e piacevoli da maneggiare. Non vi preoccupate non faremo niente di tutto questo. A noi in realtà server solo un modo per dire “questo led è acceso, questo no, questo, si…”.

In pratica avrei potuto fare un array di booleani per ogni numero, tipo:

'0' => [true, true, true, false, true, true, true]

oppure usare dei simboli, tipo:


'0' => [:on, :on, :on, :off, :on, :on, :on]

...ma sinceramente la notazione binaria in questo caso mi sembra più compatta e più leggibile, quindi portate pazienza e non preoccupatevi perchè non userò operatori strano o shift di varia natura.

Ok. L’Hash MASKS viene utilizzata solo all’inizializzazione degli oggetti LcdNumber, dopo di che ogni istanza avrà i field value e mask a disposizione.

Su queste basi iniziamo a pensare come generare l’output. Siccome ho considerato la rappresentazione in Lcd come un’insieme di 7 led che si accendono e si spengono, posso ora incasellare questi led in righe diverse, la riga zero contiene il led 0, la riga 1 contiene i led 1 e 2, la riga 2 contiene il led 4 e così via.

E’ facile notare che ci sono 2 tipologie divese di righe, quelle che contengono un solo segmento e quelle che ne contengono due, ovvero righe even e odd (dato dall’indice di riga, 0 è even, 1 è odd).

devo quindi predisporre dei metodi che tengano in considerazione le due tipologie e che prendano in ingresso i bit della maschera per capire se i segmenti sono accesi o spenti e disegnarli di conseguenza. Ovviamente parto scrivendo il test:

in lcd_number_test.rb:
1
2
3
4
def test_even_line
  assert_equal ' - ', LcdNumber.even_line(1)
  assert_equal '   ', LcdNumber.even_line(0)
end
in lcd_number.rb:
1
2
3
4
5
def LcdNumber.even_line bit
  line = ' ' 
  line << (bit.to_b ? '-':' ')
  line << ' '
end
Aveto notato il metodo to_b? sta per to_boolean, infatti converte 0 in false e 1 in true. Questo metodo non c’è nelle librerie base di Ruby, l’ho preso da facets. Se non avete facets installato dovete scaricarvi la libreria con:

gem install facets

e poi dovete aggiungere in cima a lcd_number.rb la seguente riga:


require 'facet/integer/to_b'

che aggiunge il metodo to_b alla classe Numeric.

Passiamo ora all’implementazione del metodo che stamperà le righe dispari:

in lcd_number_test.rb:
1
2
3
4
5
6
def test_odd_line
  assert_equal '| |', LcdNumber.odd_line(1,1)
  assert_equal '|  ', LcdNumber.odd_line(1,0)
  assert_equal '  |', LcdNumber.odd_line(0,1)
  assert_equal '   ', LcdNumber.odd_line(0,0)
end

e in lcd_number.rb:

1
2
3
4
def LcdNumber.odd_line *bits
  chars = bits.map {|bit| bit.to_b ? '|':' '}
  chars.join(' ') # insert the empty char in the middle
end

Attenzione all’ultima riga del metodo odd_line. Infatti per generare la linea prima trovo i caratteri significativi (pipe o vuoto) e poi inserisco gli spazi interni (un pò strano ma funziona :-)).

A questo punto mi serve un metodo unico in grado di stampare le linee, siano esse even o odd, del numero Lcd. In input passerò uno o due bit della maschera a seconda che sia una riga di un tipo o dell’altro… ma per capire ciò che voglio parto scrivendo il test, il metodo lo chiamerò line e prenderà in ingresso l’indice di riga che voglio stampare (ricordatevi che il numero da stampare è implicito nella variabile value dell’istanza LcdNumber) :

in lcd_number_test.rb:
1
2
3
4
5
6
7
8
9
10
11
12
13
def test_line
  assert_equal '   ', @one.line(0)
  assert_equal '  |', @one.line(1)
  assert_equal '   ', @one.line(2)
  assert_equal '  |', @one.line(3)
  assert_equal '   ', @one.line(4)
  
  assert_equal ' - ', @two.line(0)
  assert_equal '  |', @two.line(1)
  assert_equal ' - ', @two.line(2)
  assert_equal '|  ', @two.line(3)
  assert_equal ' - ', @two.line(4)
end

una possibile implementazione di line è:

1
2
3
4
5
def line n
  bits = bits_for_line n
  return LcdNumber.odd_line(*bits) if n.odd?
  return LcdNumber.even_line(bits) if n.even?
end

Facile no? Questo metodo non fa altro che chiamare odd_line o even_line a seconda della riga che ho chiesto e si preoccupa di passare i bit corrispondenti. Soffermiamoci un secondo sui metodi odd? e even?: nemmeno loro sono compresi nella standard library di Ruby. Facets ci viene in aiuto di nuovo, basta aggiungere in lcd_number.rb:


require 'facet/integer/odd'

Questa non è l’unica cosa che manca per il metodo line, infatti usa un fantomatico bits_for_line che mi dovrebbe restituire i bit relativi alla riga che sto interrogando, prendendoli dalla mask. Poco male, visto che l’implementazione è banale, in lcd_numbers.rb metto:

1
2
3
4
5
6
7
8
9
10
#gives me back the bits (0 or 1) of the nth line
def bits_for_line n                                                                                                   
  case n
    when 0 then @mask[6]
    when 1 then [@mask[5], @mask[4]]
    when 2 then @mask[3]
    when 3 then [@mask[2],@mask[1]]
    when 4 then @mask[0]
  end
end

Qui utilizzo il metodo Fixnum#[] che restituisce il bit n-esimo (0 o 1) della maschera, dove fix[0] è il bit meno significativo (quindi l’ordine è rovescio rispetto a come ho scritto io le maschere di bit).

Finita l’implementazione di bits_for_line lancio lcd_number_test.rb per verificare che line funzioni a dovere.

Adesso viene il bello, devo implementare il metodo process in modo che invochi line correttamente e faccia finalmente girare i test in lcd_test.rb (ve li ricordate quelli con lo 0 e 5?).

Decido di seguire questa strategia: mi faccio un array che conterrà le righe di tutti i numeri in input per avere qualcosa di questo tipo (immaginate che debba stampare 123):
1
2
3
4
5
line[0] #=> '    -  -'
line[1] #=> '  |  |  |'
line[2] #=> '    -  - '
line[3] #=> '  ||    |'
line[4] #=> '    -  - '

quindi scorro ripetutamente i numeri in input (1, 2 e 3) per ogni riga in modo da creare le righe complete. Poi ne faccio un join mettendo un ’\n’ alla fine di ogni riga:

in lcd.rb:
1
2
3
4
5
6
7
8
9
10
11
def process string_number    
  lines = []
  (0..4).each do |line_number| 
    string_number.each_char do |char|
      lcdn = LcdNumber.new(char.to_i)
      lines[line_number] ||= ''
      lines[line_number] << lcdn.line(line_number)
    end
  end
  lines.join("\n")
end 

Per scorrere tutti i numeri dell’ipotetica stringa in input ‘123’, mi viene nuovamente in aiuto facets con il metodo each_char. Inutile dire che dovete aggiungere a lcd.rb:


require 'facet/string/each_char'

Adesso posso finalmente lanciare il test lcd_test.rb e verificare che i numeri 0 e 5 vengano generati correttamente come nei file 0.txt e 5.txt.

Purtroppo non possiamo ancora provare il nostro programmino dalla shell perchè non prende il parametro in input. Poco male in fondo a lcd.rb aggiungiamo:

1
2
3
4
5
if ARGV[0]
  puts Lcd.process(ARGV[0])
else 
  puts 'usage: lcd.rb <NUMBER>'
end

Provo i soliti 0 e 5 da command line e verifico che sia tutto ok. Adesso è ora di aggiungere gli altri test funzionali che mancano: 1.txt, 2.txt etc e aggiungerli a lcd_test.rb. Siccome le ripetizioni non mi piacciono cerco di generalizzare i test con un bell’eval che mi fa il setup dei field con caricate le rappresentazion Lcd dei numeri da 0 a 9 (@lcd0, @lcd1..@lcd9):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class LcdTest < Test::Unit::TestCase
  def setup
    @lcd = {}
    (0..9).each do |n| 
      eval "@lcd[#{n}] = File.new('#{n}.txt').read"
    end
   end
  
  def test_all
    @lcd.each do |n, lcd_string| 
      assert_equal lcd_string, Lcd.process(n), "testing number #{n}"
     end
  end
end

Rilancio i test (con rake stavolta perchè devono girare sia quelli di lcd_test.rb che quelli di lcd_number_test.rb) e verifico che sia tutto a posto.

Bene, se siamo arrivati fino a qui possiamo passare ad aggiungere la gestione del size, quindi creo un file di test per size=2:

size2_6.txt contiene:
1
2
3
4
5
6
7
 -- 
|   
|   
 -- 
|  |
|  |
 -- 

Aggiungo il test a lcd_test.rb:

1
2
3
4
def test_6_size2
  @size2_6 = File.new('size2_6.txt').read
  assert_equal @size2_6, Lcd.process(n,2)
end

e modifico la classe LcdNumber in modo da gestirlo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class LcdNumber
  attr_reader :value, :mask, :size
  ...
  
  def initialize value, size=1
    @value = value
    @mask = MASKS[value.to_s]
    @size = size
  end

  def line n
    bits = bits_for_line n
    return LcdNumber.odd_line(@size, *bits) if n.odd?
    return LcdNumber.even_line(@size, bits) if n.even?
  end

  ...
  
  def LcdNumber.odd_line size, *bits
    chars = bits.map {|bit| bit.to_b ? '|':' '}
    chars.join(' ' * size) # insert the empty char in the middle
  end
  
  def LcdNumber.even_line size, bit
    line = ' ' 
    line << (bit.to_b ? '-':' ') * size
    line << ' '
  end
end

In pratica ho aggiunto un *size in ogni punto dove devo espandere orizzontalmente la stampa dei numeri, e messo un default size=1 in inizializzazione. Sono anche costretto a modificare i test perchè odd_line e even_line ora prendono size come primo parametro. Ho fatto quindi l’espansione orizzontale della stampa, allungando di size volte il carattere centrale della rappresentazione Lcd. Non dimentichiamoci però che se aumenta il size deve aumentare anche l’altezza delle righe odd. Questo lo posso fare all’interno del process in questo modo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def Lcd.process size, string_number    
  lines = []
  (0..4).each do |line_number| 
    string_number.each_char do |char|
      lcdn = LcdNumber.new(char.to_i, size)
      lines[line_number] ||= ''
      lines[line_number] << lcdn.line(line_number)
    end
  end
    
  lines[1] = (lines[1].to_a*size).join("\n")
  lines[3] = (lines[3].to_a*size).join("\n")
  lines.join("\n")
end

Una volta appurato che i test di LcdNumber girano ancora modifico la classe Lcd in modo che chiami il costruttore di LcdNumber con ciò che gli arriva dal parametro -s della command line.

Azz, a dire il vero il parametro size non l’ho ancora preso dalla command line!!! Siccome sono pigro e non ho voglia di farlo da solo mi appoggio alla mitica libreria optparse (inclusa in Ruby, niente installazioni stavolta…).

in lcd.rb:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
require 'optparse'
class Lcd
 ...
end

def parse_options
  options = {}
  OptionParser.new do |opts|
    opts.on("-s [SIZE]") do |size|
      options[:size] = size.to_i
    end  
  end.parse!
  options
end 

options = parse_options
options[:size] ||= 1 #imposto un default se non specificato
  
if ARGV[0]
  puts Lcd.process(options[:size], ARGV[0])
else
  puts 'usage: lcd.rb [-s SIZE] NUMBER'
end

A questo punto posso fare:

1
2
3
4
5
6
$lcd.rb 6
 -
|
 -
| |
 -
oppure usare lo switch -s:
1
2
3
4
5
6
7
8
9
10
11
12
$ lcd.rb 6 -s 4
 ----
|
|
|
|
 ----
|    |
|    |
|    |
|    |
 ----

Ovviamente il parametro -s con optparse lo posso mettere sia prima che dopo il mio numero da stampare, infatti lcd.rb -s 4 6 dà lo stesso risultato.

Bene, abbiamo finito, non ci rimane che giocare allegramente con il nostro nuovo display LCD!!!

Note

Ci sono grossomodo altri 2 modi di affrontare questo problema, il primo usando dei template e il secondo con una state machine, potete trovare le soluzioni migliori sul sito RubyQuiz.com. Il codice riportato sicuramente non è il migliore, se avete idee o suggerimenti, fatevi sotto! Siamo qui per migliorarci no? Trovate i sorgenti completi della mia implementazione qui: lcd.zip

L’autore

Paolo si occupa da anni di sviluppo software per il web, tiene corsi di programmazione e va in giro per le fiere di settore a tenere qualche seminario. E’ inoltre fondatore di SeeSaw che si occupa di applicazioni ruby/rails. Nel (poco) tempo libero va in moto e suona la chitarra.

6 comments | Filed Under: Linguaggio | Tags: