• No results found

String es probablemente la clase más grande construida en Ruby, con más de 75 metodos estándar.

No vamos a tratar todos ellos, la referencia de la biblioteca tiene una lista completa. En su lugar, vamos a ver algunas expresiones comunes de cadenas --las que más suelen aparecer durante el día a día de la programación.

Volvamos a nuestra máquina de discos. A pesar de que está diseñada para conectarse a Internet, tam- bién tiene copias de algunas canciones populares en un disco duro local. De esta manera, si una ardilla muerde nuestra conexión a la red, todavía será capaz de entretener a los clientes.

Por razones históricas (¿hay alguna otra?), la lista de canciones se almacena como filas en un archivo plano. Cada fila contiene el nombre del archivo que contiene la canción, la duración de la canción, el ar- tista y el título, con barras verticales para separar los campos. Un archivo típico puede comenzar

/jazz/j00132.mp3 | 3:45 | Fats Waller | Ain’t Misbehavin’ /jazz/j00319.mp3 | 2:58 | Louis Armstrong | Wonderful World /bgrass/bg0732.mp3| 4:09 | Strength in Numbers | Texas Red

: : : :

En cuanto a los datos, está claro que vamos a utilizar algunos de los muchos métodos de la clase

String, para extraer y limpiar los campos antes de crear objetos Song basados en ellos. Como mínimo,

tendremos que

• dividir cada línea en campos,

• convertir los tiempos de funcionamiento desde mm:ss a segundos, y • eliminar los espacios en blanco de los nombres de los artistas.

Nuestra primera tarea es dividir cada línea en campos, y String#split va hacer el trabajo adecua-

damente. En este caso, vamos a pasar a split una expresión regular, /\s*\|\s*/, que divide la línea

en fichas donde split encuentra una barra vertical, opcionalmente rodeada de espacios. Y, debido a que

la línea a leer del archivo tiene un carácter de nueva línea al final, vamos a utilizar String#chomp para

quitarlo justo antes de aplicar la separación.

File.open(“songdata”) do |song_file| songs = SongList.new

song_file.each do |line|

file, length, name, title = line.chomp.split(/\s*\|\s*/) songs.append(Song.new(title, name, length))

end

puts songs[1] end

produce:

Song: Wonderful World--Louis Armstrong (2:58)

Desafortunadamente, el que creó el archivo original introdujo los nombres de los artistas en columnas, por lo que algunos contienen espacios adicionales. Estos se ven feos en nuestra alta tecnología, super- twist, pantalla plana, Day-Glo display. Así que mejor quitar estos espacios extra antes de ir más allá. Te- nemos muchas maneras de hacer esto, pero probablemente la más simple sea String#squeeze, que

recorta tiradas de caracteres repetidos. Vamos a utilizar la forma squeeze! del método, que altera la

cadena.

File.open(“songdata”) do |song_file| songs = SongList.new

song_file.each do |line|

file, length, name, title = line.chomp.split(/\s*\|\s*/) name.squeeze!(“ “)

songs.append(Song.new(title, name, length)) end

puts songs[1] end

produce:

Song: Wonderful WorldLouis Armstrong (2:58)

Por último, tenemos el asunto de menor importancia del formato del tiempo: el archivo dice 2:58 y que- remos el número en segundos, 178. Se podría utilizar split de nuevo, que dividiría el campo del tiempo

en torno al carácter de dos puntos.

mins, secs = length.split(/:/)

En su lugar, vamos a utilizar un método relacionado. String#scan es similar a split en que rompe

una cadena en trozos sobre la base de un patrón. Sin embargo, a diferencia de split, con scan se es-

pecifica el patrón que se desea que coincida con los trozos. En este caso, queremos que coincida con uno o más dígitos para ambos componentes, los minutos y los segundos. El patrón para uno o más dígitos es

/\d+/.

File.open(“songdata”) do |song_file| songs = SongList.new

song_file.each do |line|

file, length, name, title = line.chomp.split(/\s*\|\s*/) name.squeeze!(“ “)

mins, secs = length.scan(/\d+/)

songs.append(Song.new(title, name, mins.to_i*60+secs.to_i)) end

puts songs[1] end

produce:

Song: Wonderful WorldLouis Armstrong (178)

Nuestro jukebox debe tener capacidad de búsqueda por palabra clave. Dada una palabra de un título de una canción o el nombre de un artista, aparecerá una lista de todas las pistas que coinciden. Escriba

fats, y mostrará canciones de Fats Domino, Fats Navarro, y Fats Waller, por ejemplo. Lo vamos a poner en

práctica mediante la creación de una clase de indexación. Esto ilustra un poco más los muchos métodos de la clase String.

class WordIndex def initialize @index = {} end

def add_to_index(obj, *phrases) phrases.each do |phrase|

phrase.scan(/\w[\w’]+/) do |word| # extraer cada palabra word.downcase! @index[word] = [] if @index[word].nil? @index[word].push(obj) end end end def lookup(word) @index[word.downcase] end end

El método String#scan extrae los elementos de una cadena que coincida con una expresión regular.

En este caso, el patrón \w[-\w‘’]+ coincide con cualquier carácter que pueda aparecer en una palabra,

seguido de uno o más de lo que se especifica entre corchetes (un guión, otro caracter de la palabra, o una comilla simple ). Más adelante hablaremos más acerca de las expresiones regulares. Para hacer nuestras búsquedas case insensitive, podemos asignar tanto las palabras que se extraen como las palabras usadas como claves en la búsqueda de minúsculas. Tenga en cuenta el signo de exclamación al final del primer nombre del método downcase!. Al igual que con el método squeeze! utilizado anteriormente, esto es

una indicación de que el método va a modificar al receptor en su lugar, en este caso la conversión de la cadena a minúsculas (Este ejemplo de código contiene un error menor: la canción “Gone, Gone, Gone” tendría la indexación en tres ocasiones. ¿Se podría arreglar?).

Vamos a ampliar nuestra clase SongList indexándole canciones a medida que se agregan y añadirle

un método para buscar una canción dándole una palabra.

class SongList def initialize @songs = Array.new @index = WordIndex.new end def append(song) @songs.push(song)

@index.add_to_index(song, song.name, song.artist) self end def lookup(word) @index.lookup(word) end end

Por último, vamos a probar todo.

songs = SongList.new song_file.each do |line|

file, length, name, title = line.chomp.split(/\s*\|\s*/) name.squeeze!(“ “)

mins, secs = length.scan(/\d+/)

songs.append(Song.new(title, name, mins.to_i*60+secs.to_i)) end

puts songs.lookup(“Fats”) puts songs.lookup(“ain’t”) puts songs.lookup(“RED”) puts songs.lookup(“WoRlD”)

produce:

Song: Ain’t Misbehavin’--Fats Waller (225) Song: Ain’t Misbehavin’--Fats Waller (225) Song: Texas Red--Strength in Numbers (249) Song: Wonderful World--Louis Armstrong (178)

En el código anterior, el método de búsqueda (lookup) devuelve una matriz de coincidencias. Cuando

se pasa un array puts, simplemente escribe cada elemento, a su vez, separado por un salto de línea.

Podríamos pasar las próximas 50 páginas viendo todos los métodos de la clase String. Sin embargo, vamos a seguir adelante para ver un tipo de datos más simple: el rango.