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) :

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 :
  irb
puis 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 FAIRE
En 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 Vous pouvez tester le code en ajoutant progressivemenet chaue transformation.
    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
./