Les strings
Voici la transcription d'une session de cours qui a eu lieu vendredi 22 juin 2007 sur la canal IRC #rubyfr, par Alexandre Perrin aka kAworu. Merci à lui !
15:04 < kAworu> bonjours à tous !
15:05 < kAworu> je vais donner un petit cours sur les Strings Ruby.
15:05 < kAworu> ça sera pas trop long, genre 20 mins, et après questions/remarques
15:05 < kAworu> on va s'arranger pour mettre aussi les logs à dispo
15:06 < kAworu> qui est motivé dans la salle ? (c'est là qu'il faut faire du bruit)
15:06 < Desintegr> :)
15:06 < kirby> new irb session
15:08 < Zmanu> \o<
15:08 < kAworu> PAN!
15:08 < CryoGen`> \o/
15:08 < kAworu> ok commençons !
15:08 < kAworu> alors, en Ruby il y a plusieurs façon de créer des Strings
15:08 < kAworu> classiquement :
15:08 < kAworu> >> s = "foo"
15:08 < kirby> => "foo"
15:08 < kAworu> >> s = 'foo'
15:08 < kirby> => "foo"
15:09 < kAworu> comme dans pas mal de langages, les '' échappent les caractères spéciaux comme les sauts de lignes.
15:09 < kAworu> >> puts "foo\nbar"
15:09 < kirby> foo
15:09 < kirby> bar
15:09 < kirby> => nil
15:09 < kAworu> >> puts 'foo\nbar'
15:09 < kirby> foo\nbar
15:09 < kirby> => nil
15:10 < kAworu> il y une construction équivalente à "" et ''
15:10 < kAworu> >> a = %{ceci est une string}
15:10 < kirby> => "ceci est une string"
15:11 < kAworu> >> a = %q{ et ceci aussi, mais sans les \n retours à la lignes par exemple}
15:11 < kirby> => " et ceci aussi, mais sans les \\n retours \303\240 la lignes par exemple"
15:11 < kAworu> ce genre de construction existent pour beaucoup de types.
15:12 < kAworu> un Array par exemple peut se construire comme ceci :
15:12 < kAworu> >> %w{ ceci est un array }
15:12 < kirby> => ["ceci", "est", "un", "array"]
15:13 < kAworu> ça rend parfois le code plus lisible. mais pour les strings, rarement.
15:13 < kAworu> une construction sur plusieurs lignes peut se faire avec %{} et %q{} ou encore avec un document en ligne
15:13 < kiorky> kAworu: ton array il est mit où là
15:13 < kAworu> kiorky: il est mis nul par, car y a pas d'affectation
15:14 < kAworu> mais le => montre ce qui est retourné
15:14 < kiorky> ok, c'est bien ce que je pensais :)
15:14 < kAworu> >> foo = <<FIN
15:14 < kAworu> >>voici une String
15:14 < kAworu> >>sur plusieurs lignes
15:14 < kAworu> >>FIN
15:15 < kirby> => "voici une String\nsur plusieurs lignes\n"
15:15 < kAworu> voilà pour les constructions, on peut utiliser String#new mais je ne vois pas d'interêt à le faire.
15:15 < kAworu> En ruby, on peut slicer les String, un peu comme en python
15:16 < kAworu> >> "abcdefg"[0,1]
15:16 < kirby> => "a"
15:16 < kiorky> kAworu: <<FIN ?
15:16 < kiorky> ha
15:16 < kAworu> kiorky: c'est la construction document en ligne
15:16 < kAworu> kiorky: comme en shell.
15:16 < kiorky> EOF
15:16 < kAworu> kiorky: ok, si tu préfère.
15:17 < kAworu> kiorky: FIN c'est plus francophone.
15:17 < kAworu> bref.
15:17 < kAworu> pour les slices, faut faire attention
15:17 < kAworu> dans python, le 1er arg c'est le slice de début, et le 2ème le slice de fin
15:18 < kAworu> genre "abc"[1,2] renvoie "b"
15:18 < kAworu> en ruby c'est différent
15:18 < kAworu> le 1er argument c'est le slice de début, et le 2ème de combien il faut "avancer", le nombre de char que l'on prend.
15:20 < kAworu> >> "abcdefg"[2,3]
15:20 < kirby> => "cde"
15:20 < kAworu> ça veut dire, à partir du 2ème slice, on prend les 3 premier caractères.
15:20 < kAworu> pour faire plus Orienté Objet, on peut utiliser String#slice à la place de String#[]
15:20 < kAworu> >> "abcdefg".slice(2,3)
15:20 < kirby> => "cde"
15:21 < kAworu> on peut aussi aller à l'envers, par exemple pour avoir le dernier char, pas besoin de savoir la longeur.
15:21 < kAworu> >> "abcdefg"[-1,1]
15:21 < kirby> => "g"
15:22 < kAworu> finalement si il n'y pas de 2ème argument, ça retourne la valeur ASCII
15:22 < kAworu> >> "abcdefg"[0]
15:22 < kirby> => 97
15:22 < kAworu> parce que Ruby considère les Strings byte par byte.
15:22 < kAworu> une autre manière de convertir une string en ASCII :
15:22 < kAworu> >> ?a
15:22 < kirby> => 97
15:23 < kAworu> c'est moyennement pratique si a est dynamique dans un script.
15:23 < kAworu> à ce moment il vaut mieux faire
15:23 < kAworu> >> "a"[0]
15:23 < kirby> => 97
15:23 < kAworu> pour convertir un code ASCII en String, il faut utiliser Integer#chr
15:24 < kAworu> >> 97.chr
15:24 < kirby> => "a"
15:24 < kAworu> voilà pour l'ASCII.
15:24 < kAworu> maintenant il arrive très souvent que l'on veuille savoir si une String est vide
15:24 < kAworu> dans un autre langage, on voit souvent : if string == ""
15:25 < kAworu> c'est possible en ruby, mais on préfère
15:25 < kAworu> String#empty?
15:25 < glooze> >> "".empty?
15:25 < kirby> => true
15:25 < kAworu> En faite, c'est Enumerable#empty?, car on peut utiliser ça sur un Array, un Hash etc.
15:26 < kAworu> c'est plus lisible et sympa de lire
15:26 < kAworu> >> if "".empty? then puts "vide" else puts "pas vide" end
15:26 < kirby> vide
15:26 < kirby> => nil
15:26 < glooze> >> if "blabla".empty? then puts "vide" else puts "pas vide" end
15:26 < kirby> pas vide
15:26 < kirby> => nil
15:26 < kAworu> les méthodes qui finissent par ? en ruby, sont des prédicats qui retourne true/false
15:27 < kAworu> c'est une convention
15:27 < kAworu> autrement autre truc utile c'est de savoir la taille d'une String
15:27 < kAworu> >> "foo".size
15:27 < kirby> => 3
15:27 < kAworu> un alias de String#size est String#length
15:28 < kAworu> à nouveau, c'est plutôt Enumerable#size car on peut appliquer cette méthode à un Array ou un Hash
15:28 < kAworu> >> "bar".length
15:28 < kirby> => 3
15:28 < kAworu> le problème c'est que comme Ruby considère les Strings par byte, l'utf-8 passe mal avec ces méthodes.
15:28 < kAworu> >> "ééé".size
15:29 < kirby> => 6
15:29 < kAworu> logique, car "é" est codé sur 2 bytes.
15:29 < kAworu> pour contourner ceci, il faut faire :
15:29 < kAworu> >> require "jcode"
15:29 < kirby> => true
15:29 < kAworu> >> $KCODE = "UTF-8"
15:29 < kirby> => "UTF-8"
15:29 < kAworu> maintenant, les méthodes size et length sont les mêmes.
15:30 < kAworu> mais de nouvelles méthodes existent, jsize et jlength qui calculent correctement les Strings utf8
15:30 < kAworu> >> "ééé".size
15:30 < kirby> => 6
15:30 < kAworu> >> "ééé".jsize
15:30 < kirby> => 3
15:31 < kAworu> pour finir la 1ère partie, voici comme récupérer et afficher des Strings en ruby
15:31 < kAworu> >> puts "foo"
15:31 < kirby> foo
15:31 < kirby> => nil
15:31 < kAworu> affiche "foo\n", avec un retour à la ligne. On peut utiliser print qui fait pareil sans le retour à la ligne.
15:31 < kAworu> pour recevoir des Strings on utilise gets (ou readline)
15:32 < kAworu> >> a = gets
15:32 < kAworu> >>plop
15:32 < kAworu> >> puts a
15:32 < kirby> plop
15:32 < kirby> => nil
15:32 < kAworu> si on veut pas avoir une variables temporaire pour ça, on peut utiliser $_
15:33 < kAworu> >> gets
15:33 < kAworu> >>foo
15:33 < kAworu> >> puts $_
15:33 < kirby> foo
15:33 < kirby> => nil
15:33 < kAworu> souvent, on veut travailler avec des données reçue par l'utilisateur
15:33 < kAworu> à propos de gets.
15:33 < kAworu> le problème c'est qu'on a toujours ce \n à la fin
15:34 < kAworu> >> nick = gets
15:34 < kAworu> >>kAworu
15:34 < kAworu> >> nick
15:34 < kirby> => "kAworu\n"
15:34 < kAworu> on peut utiliser String#chop, qui supprime le dernier byte d'une String.
15:34 < kAworu> mais c'est un peu dangereux :
15:34 < kAworu> >> nick.chop
15:34 < kirby> => "kAworu"
15:34 < kAworu> >> nick.chop.chop
15:34 < kirby> => "kAwor"
15:35 < kAworu> la méthode String#chomp fait pareil mais ne supprime que les retours à la lignes, espaces, tabs etc.
15:35 < kAworu> >> nick.chomp
15:35 < kirby> => "kAworu"
15:35 < kAworu> >> nick.chomp.chomp
15:35 < kirby> => "kAworu"
15:35 < kAworu> maintenant, c'est uniquement la valeur retournée qui est "chompé"
15:36 < kAworu> on peut changer une String sur place, avec une méthode qui fini par !
15:36 < kAworu> c'est une convention ruby, les méthodes ! modifient l'objet de base.
15:36 < kAworu> >> nick
15:36 < kirby> => "kAworu\n"
15:36 < kAworu> il y a toujours le \n même après les chop et les chomp
15:36 < kAworu> >> nick.chomp!
15:36 < kirby> => "kAworu"
15:36 < kAworu> >> nick
15:36 < kirby> => "kAworu"
15:37 < kAworu> voilà. donc pour travailler avec ce que donne l'utilisateur, on peut faire : nick = gets; nick.chomp!
15:37 < kAworu> ou directement : nick = gets.chomp!
15:38 < kAworu> un truc que l'on voit, c'est que l'on devrait écrire dans un autre langage : gets().chomp!()
15:38 < kAworu> alors qu'en ruby, on peut négliger les parenthèse si ça ne prête pas à confusion.
15:38 < kAworu> pour finir, gets et puts utilisent STDIN et respectivement STDOUT
15:39 < kAworu> lorsque l'on écris gets, on peut remplacer par STDIN.gets
15:39 < kAworu> on peut utiliser STDERR.puts pour afficher les erreurs.
15:39 < kAworu> les variables STDIN,STDERR,STDOUT peuvent aussi s'appeler $stdin,$stderr et $stdout
15:40 < kAworu> les variables $foo sont des variables globales.
15:40 < kAworu> questions ?
15:40 < kAworu> (j'en ai encore un bout si vous voulez)
15:40 < glooze> Moi ca me semble bien /D
15:41 < kAworu> la suite ?
15:41 < glooze> Comment on mets toute une chaine en majuscules ? :p
15:41 < glooze> *met
15:41 < geekounet> .upcase!
15:41 < kAworu> >> "foo".upcase
15:41 < kirby> => "FOO"
15:41 < kAworu> oui, mais upcase! comme pour chomp!, change la String de base
15:41 < geekounet> ué
15:41 < kAworu> >> a = "foo"
15:41 < kirby> => "foo"
15:41 < kAworu> >> a.upcase
15:41 < kirby> => "FOO"
15:42 < kAworu> renvoie FOO mais a n'a pas changé
15:42 < kAworu> >> a
15:42 < kirby> => "foo"
15:42 < kAworu> >> a.upcase!
15:42 < kirby> => "FOO"
15:42 < kAworu> >> a
15:42 < kirby> => "FOO"
15:42 < kAworu> bon, je continue !
15:42 < kAworu> comment itérer sur une String ?
15:43 < kAworu> si on veut itérer sur chaques "mots", le plus simple est de passer par un array
15:43 < kAworu> on split la String en Array, et on itère dessus
15:43 < kAworu> >> "il était une fois ...".split
15:43 < kirby> => ["il", "était", "une", "fois", "..."]
15:43 < kAworu> par défaut, String#split va séparer selon les espaces.
15:44 < kAworu> on peut lui donner un char (et même une regexp) pour lui indiquer comment découper
15:44 < glooze> >> "il était une fois ..., bertrand".split(',')
15:44 < kirby> => ["il était une fois ...", " bertrand"]
15:44 < glooze> ;)
15:44 < glooze> pour la regexp, je te laisse l'honneur :p
15:45 < kAworu> glooze: bwof on va pas s'égarer
15:45 < kAworu> les motivé testeront par eux même, ou lanceront ri String.split
15:45 < kAworu> la réciproque de String#split est Array#join.
15:46 < kAworu> >> puts "il était une fois ...".split.join
15:46 < kirby> ilétaitunefois...
15:46 < kirby> => nil
15:46 < kAworu> >> puts "il était une fois ...".split.join(" ")
15:46 < kirby> il était une fois ...
15:46 < kirby> => nil
15:46 < kAworu> voilà, c'est mieux comme ça.
15:46 < kAworu> si on veut itérer sur chaques caractères, c'est plus complexe.
15:46 < kAworu> on peut utiliser String#each_byte
15:47 < kAworu> >> "abc".each_byte { |b| puts b.chr }
15:47 < kirby> a
15:47 < kirby> b
15:47 < kirby> c
15:47 < kirby> => "abc"
15:47 < kAworu> ça deviens problèmatique si on utilise des caractères à plusieurs bytes
15:47 < kAworu> >> "él".each_byte { |b| puts b.chr }
15:47 < kirby> Ã
15:47 < kirby> ©
15:47 < kirby> l
15:47 < kirby> => "él"
15:48 < kAworu> dans ce cas, on oublie pas de require "jcode" et $KCODE = "UTF-8"
15:48 < kAworu> et on utilise String#scan
15:48 < kAworu> >> "él".scan(/./) { |c| puts c }
15:48 < kirby> é
15:48 < kirby> l
15:48 < kirby> => "él"
15:49 < kAworu> on peut générer des suites de Strings, comme des suites de nombres
15:49 < kAworu> >> ("a".."c").each { |c| puts c }
15:49 < kirby> a
15:49 < kirby> b
15:49 < kirby> c
15:49 < kirby> => "a".."c"
15:49 < kAworu> ou encore :
15:50 < kAworu> >> "a".upto("c") { |c| puts c }
15:50 < kirby> a
15:50 < kirby> b
15:50 < kirby> c
15:50 < kirby> => "a"
15:50 < kAworu> cela joue avec les codes ASCII, donc l'ordre n'est pas impressionnant.
15:50 < kAworu> >> "z".succ
15:50 < kirby> => "aa"
15:50 < kAworu> "z" on passe à "aa", comme après 9 on passe à 10
15:51 < kAworu> ok, maintenant un peu de transtypage.
15:51 < kAworu> comment convertir une String en autre chose ?
15:51 < kAworu> dans bcp de language, on transtype avec une fonction globale, comme int("12")
15:52 < kAworu> en Ruby, c'est orienté objet :
15:52 < kAworu> >> "1".to_i
15:52 < kirby> => 1
15:52 < kAworu> >> "1.12".to_f
15:52 < kirby> => 1.12
15:52 < kAworu> ces méthodes se base sur le début de la chaine, et non pas la totalité
15:53 < kAworu> >> "1 fois".to_i
15:53 < kirby> => 1
15:53 < kAworu> >> "1.2 fois".to_f
15:53 < kirby> => 1.2
15:53 < kAworu> >> "1.2 fois".to_i
15:53 < kirby> => 1
15:53 < kAworu> on peut aussi transformer une String en Symbol
15:53 < kAworu> >> "plop".to_sym
15:53 < kirby> => :plop
15:54 < kAworu> les symbol en Ruby, sont des identifiants, Des Strings non modifiables.
15:54 < kAworu> on les utilisent lorsque le contenu n'est pas important, mais la valeur l'est.
15:54 < kAworu> par exemple dans les Hash, on préfère
15:54 < kAworu> >> hash = { :a => 1 }
15:54 < kirby> => {:a=>1}
15:54 < kAworu> que
15:55 < kAworu> >> hash = {"a" => 1}
15:55 < kirby> => {"a"=>1}
15:55 < kAworu> c'est plus économique en mémoire aussi. dans le cas du Hash, on s'en fout que la clef soit modifiable etc, le contenu de la clef n'as pas d'importance.
15:55 < kAworu> maintenant comment savoir si on peut traiter un objet comme un chaine ?
15:56 < kAworu> >> 1.respond_to?(:to_s)
15:56 < kirby> => true
15:56 < kAworu> on utiliser Object#respond_to?
15:56 < kAworu> cette méthode, comme son nom l'indique est un prédicat (donc retourne true ou false)
15:57 < kAworu> on lui donne le symbol d'un nom de méthode, pour savoir si l'objet en question possède cette méthode.
15:57 < kAworu> ok, pour finir parlons un peu de Regexp
15:58 < kAworu> pour savoir si "ça match", c'est assez simple
15:58 < kAworu> >> "15:58:16< kAworu>" =~ /<(.*)>/
15:58 < kirby> => 8
15:59 < kAworu> on utilise l'opérateur =~ avec une regexp en argument.
15:59 < kAworu> on peut assigner la regexp à une variable, et utiliser cette variables dans la comparaison etc.
15:59 < kAworu> >> a = /<(.*)>/
15:59 < kirby> => /<(.*)>/
15:59 < kAworu> >> "15:58:16< kAworu>" =~ a
15:59 < kirby> => 8
16:00 < kAworu> les regexp sont des objets Ruby comme les autres, avec des méthodes etc.
16:00 < kAworu> lorsque l'on s'interesse juste à une partie, on peut utiliser String#sub et String#gsub
16:01 < kAworu> >> "16:00:36< kAworu>".sub(/[0-9:]*/, '')
16:01 < kirby> => "< kAworu>"
16:01 < kAworu> >> "16:00:36< kAworu>".gsub(/[0-9]/, '')
16:01 < kirby> => "::< kAworu>"
16:02 < kAworu> et pour les match
16:02 < kAworu> >> "16:00:36< kAworu>".sub(/^.*<(.*)>$/, '\1')
16:02 < kirby> => " kAworu"
16:03 < kAworu> une autre technique peut-être plus lisible :
16:03 < kAworu> >> "16:00:36< kAworu>"[/^.*<(.*)>$/, 1]
16:03 < kirby> => " kAworu"
16:04 < kAworu> il existe String#sub! et String#gsub!
16:04 < kAworu> si vous avez suivis, vous avez compris à quoi ça sert.
16:04 < kAworu> on peut utiliser $1,$2 etc. comme match de la dernière regexp
16:05 < kAworu> >> if "16:00:36< kAworu>" =~ /^.*<(.*)>$/ then puts "ton nick est" + $1 end
16:05 < kirby> ton nick est kAworu
16:05 < kirby> => nil
16:06 < kAworu> pour finir, comment substituer du code et des variables dans les strings.
16:06 < kAworu> la façon ruby de faire est d'utiliser #{}
16:06 < kAworu> ainsi j'aurai pus écrire ce qu'il y a plus haut comme ça :
16:06 < kAworu> >> if "16:00:36< kAworu>" =~ /^.*<(.*)>$/ then puts "ton nick est #{$1}" end
16:06 < kirby> ton nick est kAworu
16:06 < kirby> => nil
16:07 < kAworu> >> a = 12
16:07 < kirby> => 12
16:07 < kAworu> >> "j'aime bien #{a}"
16:07 < kirby> => "j'aime bien 12"
16:07 < kAworu> la construction #{} est très puissante. on peut mettre pleins de code ruby dedans.
16:07 < kAworu> et c'est persistant.
16:07 < kAworu> >> "rien du tout #{class Foo; end}"
16:07 < kirby> => "rien du tout "
16:07 < kAworu> >> Foo.new
16:07 < kirby> => #<Foo:0xb7a5b54c>
16:08 < kAworu> autrement, on peut utiliser du printf style
16:08 < kAworu> >> "voici %d" % a
16:08 < kirby> => "voici 12"
16:08 < kAworu> >> a = 20
16:08 < kirby> => 20
16:08 < kAworu> >> "voici %d" % a
16:09 < kirby> => "voici 20"
16:09 < kAworu> voilà, il reste un cas extrême
16:09 < kAworu> c'est lorsque l'on veut initialiser la String *avant* la variable.
16:09 < kAworu> ce n'est évidement pas possible ni avec printf style ni avec #{}
16:10 < kAworu> kirby: restart_irb
16:10 < kirby> new irb session
16:10 < kAworu> >> "ceci est %d" % a
16:10 < kirby> NameError: undefined local variable or method `a' for main:Object
16:10 < kAworu> >> "ceci est #{a}"
16:10 < kirby> NameError: undefined local variable or method `a' for main:Object
16:10 < kAworu> là il faut ruser, et utiliser ERB.
16:10 < kAworu> ERB (pour eRuby) est du code ruby embarqué dans du texte.
16:10 < kAworu> c'est beaucoup utilisé dans Ruby On Rails pour les vues.
16:11 < kAworu> >> require "erb"
16:11 < kirby> => true
16:11 < kAworu> >> e = ERB.new 'il est <%= time %>'
16:11 < kirby> => #<ERB:0xb7a7922c @src="_erbout = ''; _erbout.concat \"il est \"; _erbout.concat(( time ).to_s); _erbout", @filename=nil, @safe_level=nil>
16:12 < kAworu> ok, on a e initialisé, alors que la variable time n'est pas définie.
16:12 < kAworu> >> defined? time
16:12 < kirby> => nil
16:12 < kAworu> >> time = Time.now
16:12 < kirby> => Fri Jun 22 14:12:19 +0000 2007
16:12 < kAworu> >> e.result(binding)
16:12 < kirby> => "il est Fri Jun 22 14:12:19 +0000 2007"
16:12 < kAworu> et maintenant on peut changer la variables time sans problèmes.
16:12 < kAworu> >> time = "16:12"
16:12 < kirby> => "16:12"
16:13 < kAworu> >> e.result(binding)
16:13 < kirby> => "il est 16:12"
16:13 < kAworu> le binding passé en paramètre c'est à cause de irb
16:13 < kAworu> dans un fichier il n'y a pas besoin de passer binding en paramètre.
16:13 < kAworu> voilà !
16:13 < kAworu> questions / remarques ?
16:13 < glooze> ahah
16:13 < glooze> la classe
16:15 < kAworu> bon, je finis de mijoter un petit script ruby pour passer les logs IRC en HTML
16:15 < kAworu> et après les logs seront dispo sur le net.
16:15 < kAworu> pour ceux qui ont pas pu être là etc.
16:15 < c0rwin> merci pour la présentation ;)
16:16 < kAworu> no prob
16:16 < kAworu> j'en ferai d'autres si ça vous a plus :D
16:16 < c0rwin> j'en veux bien une sur les threads :p
16:17 < kAworu> c0rwin: ok, la prochaines fois c'est Thread/Mutex alors.


