Guide de survie pour Ruby
Tout d'abord, une url avec des exemples sur les fonctionnalités de ruby (du genre à garder sous le main) :- Projet ruby-kickstart.com (ancienne version locale ensta au cas où...)
- exemples pour array.rb (Fouiller l'arborescence pour avoir d'autres exemples)
Premier contact avec Ruby
Tout est objet en Ruby même les nombres (Fixnum, ...). L'approche objet est très agréable d'utilisation : on enchaine les opérations de manière naturelle, un peu comme des filtres unix avec des "pipes". Le résultat d'un appel est un objet, qui dispose donc de méthodes :
# Tout est objet, même les nombres:
-199.abs # 199
"ruby is cool".length # 12
"Rick".index("c") # 2
"Nice Day Isn't It?".downcase.split(//).sort.uniq.join # " '?acdeinsty"
Pour en savoir plus sur les chaînes Ruby, pour pouvez utiliser la commande unix
ri (pour Ruby Info, l'équivalent du man unix).
Même les boucles ne sont que des cas particuliers d'une notion plus générale appelée Itérateur (bien plus simple à exploiter qu'en C++ ou en Java)
for i in myList do
puts "i=" + i
end
N'est qu'une facilité syntaxique pour ecrire :
myList.each{ | i | puts "i=" + i}
Les collections ruby (Array, ...) proposent de nombreux itérateurs, et on
peut en définir de nouveaux.
De plus, toute classe peut-être réouverte et complétée.
Vous pouvez lancer l'interpréteur interactif Ruby :
irbpuis tapez l'exemple suivant :
class Integer
def fact
if self <= 1 then
1
else
self * (self-1).fact
end
end
end
# Test par .
5.fact
=> 120
# Mais 120 est aussi un Fixnum. Donc on peut faire :
5.fact.fact
=> 6689502913449127057588118054090372586752746333138029810295671352301633
5572449629893668741652719849813081576378932140905525344085894081218598
98481114389650005964960521256960000000000000000000000000000
Il y a de nombreuses façons d'écrire du code Ruby. Voici une version C like
de la méthode ci-dessus :
class Integer
def fact() self <= 1 ? 1 : self*(self-1).fact end
end
Une version par itérateur (mais pas forcément plus lisible si on ne connait
pas "inject" !) donnerait :
class Integer
def fact
(2..self).inject(1) { |f, n| f * n }
end
end
Prenez bien conscience dans cet exemple simple, que vous n'avez pas créé
une nouvelle fonction ou classe personnelle, mais que vous venez de
compléter
une classe Ruby que vous n'avez pas écrite !
Les bases
Les variables
@@variable_de_classe
@variable_d_instance
variable_locale
$variable_globale (à éviter ;-)
UneConstante
Les chaines de caractères et les symboles
"Une_chaine" est un objet de la classe String qui a donc ses méthodes et peut etre éventuellement modifiée
str = "abcdefghij"
str[2..3] = str[2..8].swapcase.reverse
str
=> "abIHGFEDCefghij"
Un "symbole" ressemble à une chaine, mais il est représenté en mémoire par un
entier unique, souvent utilisé comme clés dans une table de hachage, ou
comme nom d'option dans un programme.
opts[:une_option] = 999
but = TkButton.new(coolingFrame,
:text => "Calculer \"temp_init\"" ,
:command => proc {self.calcul_temp_init} ,
:font => "Helvetica 14" )
On peut facilement passer de l'un à l'autre voire utiliser l'un pour
l'autre par my_str.to_sym ou :my_symbol.to_s.
Création d'une table de hachage
@@default_opts = {
# Type de refroidissement : constantes REFROID_GEO et REFROID_LOG
:cooling_type => AnnealingAlgo::REFROID_GEO,
# Raison de la suite geometrique des temperatures
# (utile seulement si cooling_type==REFROID_GEO)
:geo_coeff => 0.9,
:solver_title => "Recuit simulé",
:prefs_title => "Préférence pour le Recuit Simulé"
}
Les structures de controles
les tests et les booléens
Tout objet peut faire l'objet de tests, mais les seuls objets suivants
représente la valeur false :
- la constante false (singleton d ela classe
- la valeur nil (objet non déclaré, ou non initialisé)
En particulier les valeurs suivantes sont considérées comme true (grosse
différence avec Perl, Php et autres langages incidieux)
- les nombres 0, 0.0, 0x0
- les chaines de caractères "", "0", "false", ...
# Le test tout simple
if bool_expr
some_code
end
# Structure générale du if
if bool_expr [then]
some_code
elsif bool_expr [then]
some_code
else
some_code
end
# if est une expression dont la valeur est le résultat de la derniere
# instruction
ma_valeur = if bool_expr [then]
some_code
elsif bool_expr [then]
some_code
else
some_code
end
# Un test peut aussi s'écrire :
some_code if bool_expr
# Remarque "if not" peut etre remplacé par "unless"
some_code unless bool_expr
# enfin, vous pouvez utiliser les expessions ternaires comme en C/C++ :
ma_valeur = bool_expr ? "result_si_oui" : "result_si_non"
Instruction case (le switch)
Utilisation classique :
case inputLine
when "debug"
# détection d'un chaine précise
dumpDebugInfo
dumpSymbols
when /p\s+(\w+)/
# détection et exploitation d'une expression régulière
dumpVariable($1)
when "quit", "exit"
# plusieurs mots possibles
exit
else
print "Illegal command: #{inputLine}"
end
Il faut bien comprendre que ce "case" est beaucoup plus puissant que le "switch" de
C. En effet, le travail de comparaison effectué par la clause "when" dépend (mais
de manière naturelle) à la fois du type de la variable inputLine et du type de
ce qui suit le "when".
les boucles
while bool_expr
some_code
end
Comme pour le if, on peut mettre le while après l'instruction
some_code while bool_expr
et si some_code prend plusieurs lignes, il faut délimité par begin..end
d'où la forme utilisée dans le TP :
begin
some_code
end while bool_expr
Un exemple de classe complète
A FAIREEn attendant : voir wikipedia/ruby
Introspection
Tout est objet en Ruby ? Alors pouquoi il existe par exemple des fonctions comme puts, print, ... Et bien parce que ce sont en fait des méthodes d'un objet implicite de classe Kernel.
13.class
=> Fixnum
13.0.class
=> Float
C'est d'ailleurs pour cela que 13. (avec un point) ne peut pas représenter un
flottant, comme en C. En effet le point suivant le 13 permet
d'appeler une méthode de l'objet
de classe Fixnum.
"my text".class
=> String
[ 1,2,3 ].class
=> Array
{ 1 => "Ain", 2 => "Aisne", 3 => "Allier" }.class
=> Hash
Les classe ruby sont elles-même des objets d'une certaine classe :
String.class
=> Class
String.ancestors
=> [String, Enumerable, Comparable, Object, Kernel]
Class.ancestors
=> [Class, Module, Object, Kernel]
1.class.ancestors
=> [Fixnum, Integer, Precision, Numeric, Comparable, Object, Kernel]
Au fait, 2 est un objet ! De quels méthodes dispose-il ?
2.methods
["%", "odd?", "inspect", "prec_i", "<<", "tap", "div", "&", "clone", ">>", "public_methods", "object_id", "__send__", "instance_variable_defined?", "equal?", "freeze", "to_sym", "*", "ord", "+", "extend", "next", "send", "round", "methods", "prec_f", "-", "even?", "singleton_method_added", "divmod", "hash", "/", "integer?", "downto", "dup", "to_enum", "instance_variables", "|", "eql?", "size", "instance_eval", "truncate", "~", "id", "to_i", "singleton_methods", "modulo", "taint", "zero?", "times", "instance_variable_get", "frozen?", "enum_for", "display", "instance_of?", "^", "method", "to_a", "+@", "-@", "quo", "instance_exec", "type", "**", "upto", "to_f", "<", "step", "protected_methods", "<=>", "between?", "==", "remainder", ">", "===", "to_int", "nonzero?", "pred", "instance_variable_set", "coerce", "respond_to?", "kind_of?", "floor", "succ", ">=", "prec", "to_s", "<=", "fdiv", "class", "private_methods", "=~", "tainted?", "__id__", "abs", "untaint", "nil?", "chr", "id2name", "is_a?", "ceil", "[]"]
Cela fait beaucoup de méthodes hein ! Je voudrais afficher seulement
l'ensemble des méthodes de l'objet 2 sans les méthodes communes
à tout objet, triées, et avec seulement une virgule-espace comme séparateur ??
(2.methods - Object.methods).sort.join(", ")
=> "%, &, *, **, +, +@, -, -@, /, <<, <<, [], ^, abs, between?, ceil, chr, coerce, div, divmod, downto, even?, fdiv, floor, id2name, integer?, modulo, next, nonzero?, odd?, ord, prec, prec_f, prec_i, pred, quo, remainder, round, singleton_method_added, size, step, succ, times, to_f, to_i, to_int, to_sym, truncate, upto, zero?, |, ~"
Exercice : faites la même chose avec C++, java ou python...
De même, quelles sont les méthodes disponibles pour un objet tableau ?
([1, 2, 3].methods - Object.methods).sort.join(", ")
=> "&, *, +, -, <<, [], []=, all?, any?, assoc, at, choice, clear, collect, collect!, combination, compact, compact!, concat, count, cycle, delete, delete_at, delete_if, detect, drop, drop_while, each, each_cons, each_index, each_slice, each_with_index, empty?, entries, enum_cons, enum_slice, enum_with_index, fetch, fill, find, find_all, find_index, first, flatten, flatten!, grep, group_by, index, indexes, indices, inject, insert, join, last, length, map, map!, max, max_by, member?, min, min_by, minmax, minmax_by, nitems, none?, one?, pack, partition, permutation, pop, product, push, rassoc, reduce, reject, reject!, replace, reverse, reverse!, reverse_each, rindex, select, shift, shuffle, shuffle!, size, slice, slice!, sort, sort!, sort_by, take, take_while, to_ary, transpose, uniq, uniq!, unshift, values_at, zip, |"
Ajouter que la classe String est également un objet constant de la class "class", ...
Bric à Brac
Fibonacci en Ruby :Principe : en créer une Hash (dictionnaire) mémorisant les valeurs de la fonction de Fibonacci. Le bloc {...} permet de calculer la valeur intiale pour une clé donnée si celle-ci n'a jamais été initialisée. L'appel à merge permet d'ajouter à cette Hash les deux premiers éléments de la suite de Fibonacci.
irb
fibs = Hash.new {|h,k| h[k] = h[k-1] + h[k-2]}.merge(0=>0, 1=>1)
fibs[70]
=> 190392490709135
Passage little endian en big endian d'une chaine hexa sur mot de 32 bits : Algo
- découper la chaine pour obtenir un tableau de blocs de 8 caractères
- découper chaque bloc en sous-tableau de chaines de deux caractères (i.e. un byte)
- inverser l'ordre de ce sous-tableau
- réunir ces sous-tableau pour en faire des sous-chaine (inversées)
- réunir le tableau final
irb
s = "1234567812345678"
s.scan(/.{8}/).map{|b| b.scan(/.{2}/).reverse.join}.join
=>> "7856341278563412"
# Variante
s.scan(/(..)(..)(..)(..)/).map(&:reverse).join
Autre solution : utiliser les flux potentiellement de taille infinie de
ruby-2.0 (avec lazy...) TODO
Une chaine aléatoire en ruby :
(1..50).map{ ('a'..'z').to_a.sample }.join
# => "maeutvhotoracijglbcvlgavclfbkppceubwyebbqufthmuyyd"
# Vous voulez une ligne de commande sous unix
ruby -e 'p (1..50).map{%w(0 1 O I).sample}.join'
# =>"10O00110101I10IIOI10OIIII0OI1I1OII0OOO01OO001111O0"
# vous voulez maitriser les caractères utilisés ?
chars = (0..9).to_a + ('A'..'Z').to_a + ('a'..'z').to_a + %w(! @ # $ % * . ,);
passwd = (1..50).map{chars.sample}.join
# => "w%jz0@cpf4.WpXbkpATbk!Zyhij8lerRNzsL0L93Jv5.$CTJEa"
A SUIVRE
./

