22
Temario:
Temario:
1) Motivacin
Ruby
2) Setup
Instalando Ruby en OSX
Instalando RVM y ruby
Introduccin a RVM
3) Introduccin a Ruby
El Intrprete
Entrada y salida de datos
Introduccin a tipos de datos
Tipos de lenguaje
Tipos de datos frecuentes.
Preguntas
4) Operadores
Operadores aritmticos
Operadores de comparacin
Operadores de asignacin
Operadores lgicos
Preguntas
5) Trabajando fuera del intrprete.
Creando scripts
Analicemos el resultado
Preguntas:
6) Condiciones y loops
Condicionales
Ciclos
While
Until
For
Ejercicios para resolver
Break
Ciclos anidados.
Desafos
Preguntas
7) Mtodos
Definiendo un mtodo
Llamando una funcin
Parmetros
Mtodos con parmetros valores por defecto
El scope de las variables
Variables globales vs locales
Mtodos recursivos
Preguntas
8) Arrays
Creando arrays
ndices
Contando elementos
Los arrays como contenedores
Mutabilidad en los arrays
Operaciones funcionales sobre los arrays
Select
Inject
Group_by
Contando elementos con group by
Ordenando arreglos
Los arrays como conjuntos
Preguntas
11) Objetos y arreglos
Ejercicios resueltos
Preguntas
12) Bloques, Procs y Lambdas
Bloques
Procs
Lambdas
13) Archivos
Preguntas
1) Motivacin
Este es el segundo libro de la coleccin de Fullstack de Desafo Latam, el libro fue creado con el propsito de
ayudar a los alumnos de desafiolatam a apoyar sus clases, y entender a profundidad como funciona Ruby, para
posteriormente sacarle todo el provecho a Ruby on Rails.
Este libro incluye todos los conceptos claves de ruby, desde las instrucciones bscias, hasta el manejo de bloques,
procs y objetos. Este libro fue escrito y pensado para aquellas personas que ya tengan bases en pensamiento
analtico y resolucin de problemas bsicos de programacin, si no es tu caso puedes empezar con el primer
libro de la coleccin.
Ruby
Ruby fue creado por Yukihiro Matsumoto, tambin conocido como Matz, dentro de las caracterstica tiene:
Sintaxis simple
Programacin orientado a objeto (classes, methods, objects)
Caractersticas especiales de orientacin a objetos (Mix-ins, mtodos singleton methods, etc)
Sobrecarga de operadores
Manejo de errores
Iteradores
Closures
Colector de basura
Carga dinmica
Multiplataforma (Unices, Windows, DOS, OSX, OS/2, Amiga, )
2) Setup
La instalacin de ruby depende del sistema operativo, tanto en OSX como en Linux se recomienda instalar RVM
(Ruby Virtual Machine), el cual permite tener instalada diferentes versiones de ruby en el sistema sin conflicto, lo
que es muy til a la hora de trabajar en diversos proyectos.
Se desaconseja trabajar con Windows, si bien ruby funciona bien en windows muchas componentes (gemas) no
funcionan, pero dentro del alcance de este libro no habr problemas con ningn sistema operativo.
xcode-select --install
Existen otros programas similares a RVM, los otros dos ms famosos son Rbenv y CHruby. Para el propsito de
este libro es posible usar cualquiera, pero para el siguiente libro de la coleccin, el libro de Rails se ocupar RVM
para automatizar el deployment con Capistrano as que es mejor instalar RVM desde ya.
Introduccin a RVM
Con RVM instalado (es necesario cerrar la consola y abrirla de nuevo despus de instalarlo)
rvm list muestra todas las versiones de ruby que tienes en el sistema rvm install 2.3.1 instala
la versin 2.3.1 de ruby rvm use 2.3.1 marca como default la versin 2.3.1 de ruby.
Finalmente podemos saber si la versin que tenemos de ruby ocupando:
ruby -v
3) Introduccin a Ruby
El Intrprete
La forma ms rpida de empezar a trabajar con Ruby es con el intrprete, para entrar a el debemos abrir la
terminal y escribir
irb
Dentro del intrprete ya no podemos escribir comandos bash como cd o ls en su lugar debemos escribir
mtodos y operadores de ruby por ejemplo podemos escribir 2+2 y ver como output 4
Para salir del intrprete debemos escribir exit y con eso volveremos a la terminal bash .
Introduccin a variables
Podemos almacenar valores en variables simplemente asignndolos, ejemplo a = 2
Con los valores asignados podemos realizar las mismas operaciones que en otros lenguajes, como en el siguiente
caso:
1
2
3
a = 2
b = 3
a + b
En el intrprete cuando realizamos cualquier operacin obtenemos el output inmediatamente, pero esto no
suceder cuando creemos nuestros programas fuera del intrprete como normalmente se hace.
1
2
3
4
5
a
b
a
c
#
= 2
= 3
+ b
= _
=> 5
1
2
a = gets
puts a
Antes de mostrar el valor, la consola quedar bloqueada hasta que nosotros ingresemos una secuencia de
caracteres, ya sea nmeros o letras.
El problema de gets como lo podemos ver en la imagen es que tambin captura el salto de lnea que uno ingresa
al presionar la tecla enter.
Para evitar este problema podemos ocupar el mtodo chomp:
1
2
a = gets.chomp
puts a
a = gets.chomp
2
3
puts a.class
# String
En este caso veremos que el resultado es string sin importar si ingresamos un nmero o una secuencia de
caracteres. Ahora si probamos con un nmero, obtendramos:
1
2
b = 2
puts a.class
Veremos que b es del tipo de dato Fixnum, o sea un nmero, hemos descubierto algo importante que 2 no es lo
mismo que "2" y por lo mismo no podemos sumarlos.
2+"2"
1
2
3
4
Antes de ahondar sobre los distintos tipos de datos es necesario que hagamos un pequeo de repaso de los tipos
de lenguaje de programacin para que podemos entender mejor como funciona ruby.
Tipos de lenguaje
Languages de tipo esttico vs dinmicos
En ruby los tipos de datos se revisan en tiempo de ejecucin, o sea dinmico, esto quiere decir que si hay una
operacin que sume datos que no son sumables, como por ejemplo "2" + 2 no lo sabremos hasta que se
lea exactamente esa lnea, pero adems tiene otra ventaja, en algunos lenguajes como C (de tipado esttico) uno
tiene que definir el tipo de variable, por ejemplo:
int c = 5;
Luego cambiar el valor que contiene c a algo que no sea un nmero entero causara un error, incluso tampoco
podramos decir especficamente que ahora c va a contener una variable de otro tipo, en cambio en los lenguajes
dinmicos como ruby se puede hacer:
1
2
c = 5
c = "hola"
Independiente de que sea mucho ms fcil para un programador trabajar de esta forma, tambin hace el cdigo
ms propenso a errores, pero para evitar varios de estos errores hay otra caracterstica muy potente que tiene
ruby, llamada duck typing.
TrueClass, FalseClass
Array
Symbol
Hash
Time
String
Los strings son cadenas de texto como "hola mundo". Ruby diferencia los identificadores (o sea los nombres de
las variables) de los strings porque estos ltimos estn rodeados de comillas dobles o comillas simples.
1
2
3
4
b
a
c
d
=
=
=
=
5
b
"b"
'b'
Cunto vale a y cuanto vale c?, si no te qued clara la idea prubalo en el intrprete y obtendrs los resultados.
Interpolacin
La interpolacin consiste en mostrar el valor de una variable dentro de un string como en el siguiente caso:
1
2
3
edad = 30
puts "tienes #{edad} aos"
# tienes 30 aos
El mensaje mostrara que tienes 30 aos, la interpolacin slo funciona sobre comillas dobles y no funciona
sobre comillas simples.
1
2
3
edad = 30
puts 'tienes #{edad} aos'
# tienes #{edad} aos
1
2
(2**62 - 1).class
(2**62).class
En el primer caso obtendremos Fixnum en el segundo Bignum, si en ambos obtienes bignum es muy posible
que la arquitectura de tu computador sea de 32 bits.
Como detalle cabe destacar que los parntesis son necesarios porque el .class tiene precedencia por sobre la
operacin, entonces sin los parntesis en el primer caso haramos .class de 1 que es un Fixnum y luego no lo
habramos podido restar de 2**62, en el segundo sin los parntesis habramos sacado el .class de 62 y tratado de
elevar 2 a Fixnum operacin que claramente no tiene sentido.
Float
Float son los valores con decimales, o sea por ejemplo:
2.0
3.14
1.5
Los nmeros flotantes son representaciones inexactas de los nmeros reales y por lo mismo se debe tener
precaucin al compararlos a escala muy pequeas.
En ruby la divisin no exacta de dos nmeros enteros no da como resultado un nmero decimal en lugar de eso
da como resultado otro entero. Ejemplo: al dividr 4 / 5 esperaramos como resultado 8, pero en su lugar
obtenemos cero.
Hay dos formas de resolver el problema:
La primera es agregando un .0, por ejemplo 4.0 / 5 o 4 / 5.0 nos darn como resultado 0.8
La segunda es transformando el dato con el mtodo .to_f, ejemplo 4.to_f / 5 , esta segunda forma es la
que hay que utilizar cuando nuestros valores estn dentro de variables.
La divisin de un float con un entero o de un entero con un float si da un float como resultado.
1
2
a = nil
a.nil?
En cualquier otro lenguaje habramos obtenido un error del tipo que a no es un objeto y por lo tanto no tiene el
mtodo .nil?, ruby lo soporta. Como resultado del experimento anterior obtendremos el valor booleano true,
pero se preguntamos por un valor no nulo, como por ejemplo 2.nil? obtendremos como resultado false.
no confundir con undefined, una variable ser undefined mientras no se haya definido.
TrueClass y FalseClass
Los objetos true y false sirven para representar valores booleanos, son tiles para representar estados, veamos un
ejemplo bsico:
1
2
3
if true
puts "hola"
end
En muchos lenguajes existe una conversin implcita entre el 0 y el falso y el 1 y verdadero, pero en ruby son
cosas distintas.
1
2
En ruby todo es un objeto, true y false son instancias de las clases TrueClass y FalseClass respectivamente. Esto
significa que si preguntamos por la clase de true obtendremos TrueClass y si lo hacemos por la clase de false
obtendremos FalseClass.
true.class # TrueClass
Array []
Los arrays de ruby son contenedores que permiten agregar mltiples datos y de diversos tipos, los datos dentro
pueden accedidos secuencialmente o por su ndice. ejemplo:
1
2
a = [1,2,3,4,5, "hola"]
puts a[0]
Los ndices van desde cero hasta n - 1. Acceder a un indice ms all del rango tendr como efecto la devolucin
de nil
1
2
3
a.each do |i|
puts a[i]
end
Los arrays puedes ser recorridos de otras formas, y son un captulo completo de este libro que abordaremos ms
adelante.
Smbolos :simbolo
Los smbolos son tipos de datos similares a los strings pero estn optimizados para ocupar menos memoria. los
smbolos empiezan con :
1
2
a = :hola
puts a
Para qu sirven?
En muchas ocasiones no nos interesa ocupar el string para operar sobre el, en algunas ocasiones lo ocupamos
para denotar el estado de una variable, por ejemplo supongamos que tenemos un semforo que puede tener tres
estados, y estos pueden ser "rojo", "amarillo" o "verde"`, en ese caso conviene utilizar smbolos y simplemente
guardar el estado como smbolo, ejemplo semforo = :amarillo
Si el array almacena posibles estados entonces tambin podemos aplicar la misma lgica
1
2
Hash {}
Los hash son otro tipo de contenedor que lugar de indexarse por un nmero se indexan por una clave. Ejemplo:
1
2
Para acceder a los valores de un hash debemos hacerlo por la clave, como en el siguiente ejmplo:
1
2
a["clave1"]
# => "valor1"
A los hash se les suele llamar diccionarios porque funcionan de la misma forma, para poder obtener el
significado de una palabra la bscas por el ndice.
La sintaxis de los hash es clave => valor , donde la clave puede ser un string o smbolo y el valor puede
ser cualquier objeto, o sea cualquier tipo de dato ya sea una array o un hash.
Hash y Smbolos
Mencionamos que los hashs pueden utilizar smbolos como claves, para lograrlo podemos utilizar una de las dos
siguientes sintaxis.
1
2
1
2
Time
La clase Time nos permite generar instancias de tiempo, Time.now nos devuelve una instancia con la hora local
del sistema, luego podemos movernos en el tiempo sumando y restando segundos.
1
2
3
hora = Time.now
puts hora + 60
puts hora + 3600
Preguntas
Cul es la diferencia entre un smbolo y un array?
Cul es la diferencia entre un array y un hash?
En que consiste el duck typing?
Para que sirve la interpolacin?
Es lo mismo un string con doble comilla y que uno de comillas simples?
Cmo ruby diferencia un string de una variable?
Es lo mismo a = 2, que a = "2"?
Es lo mismo a = "b", que a = b?
Cul es la diferencia entre Bignum y Fixnum?
Qu es una IDE?
4) Operadores
En ruby los operadores son parte del comportamiento de los objetos, estn definidos dentro de las clases como
veremos ms adelante y son necesarios para sobre los distintos tipos de datos existen.
Los operadores bsicos se dividen en estos cuatro tipos:
1. Operadores aritmticos
2. Operadores de comparacin
3. Operadores de asignacin
4. Operadores lgicos
Operadores aritmticos
Los operadores aritmticos son aquellas instrucciones que permiten operar sobre los distintos tipos de datos,
principalmente aplican a nmeros, pero algunos objetos tienen sus propias definiciones de estos
comportamientos
Operator
Nombre
Ejemplo
Resultado
Suma
2+3
Resta
2-3
-1
Multiplicacin
3*4
12
Divisin
3/4
Mdulo (resto)
3%4
**
Potencia
2 ** 4
16
Operadores de comparacin
Los operadores de comparacin son aquellos que comparan dos valores y obtienen como resultado un valor
booleano.
Operator
==
Nombre
Igual a
Ejemplo
Resultado
2 == 2
true
==
Igual a
2 == 2
true
!=
Distinto a
2 != 2
false
>
Mayor a
3>4
false
>=
Mayor o igual a
3 >= 3
true
<
Menor a
4<3
false
<=
Menor o igual a
3 <= 4
true
Operadores de asignacin
Los operadores de asignacin tienen como resultado cambiar la variable a la izquierda de la expresin, por
ejemplo al hacer a = 2 la variable a se le asigna el valor 2.
Operator
Nombre
Ejemplo
Resultado
Asignacin
a=2
a toma el valor 2
+=
Incremento y
asignacin
a += 2
-=
Decremento y
asignacin
a -= 2
*=
Multiplicacin y
asignacin
a *= 3
/=
Divisin y asignacin
a /= 3
Operadores lgicos
Los operadores de comparacin son aquellos que comparan dos valores que reciben el nombre de operandos y
obtiene como resultado un valor booleano.
Operator
Nombre
Ejemplo
Resultado
and
false and
true
false or
true
or
or
true
not
no
not false
true
Preguntas
Que se entiende con que el operador es parte del comportamiento de un objeto? Cul es la diferencia entre un
operador aritmtico y uno de asignacin? Qu significa la expresin a += 1 ?
touch prueba.rb
Como primera prueba dentro del script vamos a pedir al usuario dos datos y los vamos a sumar.
Para eso dentro del archivo pondremos:
1
2
3
4
5
Para probar el script abriremos la carpeta donde est nuestro script utilizando bash y dentro de ella podemos
ejecutar el script escribiendo:
ruby prueba.rb
Luego ingresamos un valor, enter, luego otro valor, enter y veremos como resultado la concatenacin de ambos
nmeros en lugar de las suma.
Existe otra forma de correr los archivos en ruby ocupando el build de sublime directamente, como lo muestra la
siguiente imagen.
Para eso primero debemos seleccionar el build sistema que corresponde y lugar debemos seleccionar la opcin
build o el shortcut que ah aparece.
Analicemos el resultado
Por qu obtuvimos una concatenacin?
Porque gets.chomp devuelve un string, y ruby a travs de su Duck Typing interpreta la suma de dos strings como
la concatenacin.
Entonces cmo hacemos que de la suma? Fcil, transformamos los tipos de datos ocupando el mtodo .to_i
1
2
3
4
5
En el cdigo anterior tambin aprendimos otra leccin interesante, los mtodos de los distintos objetos se
pueden ir concatenado, puesto gets.chomp devuelve un string entonces podemos aplicar los mtodos de strings
al resultado. Ahondaremos ms en este tema en el futuro.
Antes de avanzar al siguiente captulo deberas ser capaz de contestar estas preguntas, si no te recomiendo que
vuelvas a leerlo y lo contestes
Preguntas:
Cul es la diferencia entre el comando de bash irb y el de ruby?
Qu significa que los mtodos se puedan concatenar?
Cul es la relacin entre concatenacin de mtodos y concatenacin de strings?
Se puede utilizar _ para repetir la ltima lnea cuando estamos fuera del intrprete?
6) Condiciones y loops
Condicionales
Al igual que en casi todos los lenguajes de programacin, es posible ramificar el cdigo basado en condiciones if
y else, por ejemplo:
1
2
3
4
5
6
7
El cdigo dentro del if slo se ejecuta si se cumple la condicin, el cdigo dentro del else slo se ejecuta si no se
cumple.
En ruby tambin es posible invertir las operaciones utilizando la instruccin unless.
1
2
3
4
5
6
7
Otra forma de invertir las condiciones es ocupando el operador lgico de not, para no sobrecomplicarnos lo
correcto es siempre escribir las condiciones en positivo, puesto que las expresin como yo no, no lo hara son
difciles de leer y no siempre quieren decir lo que dicen, escribiendo las expresiones en positivo evitaremos
muchos problemas.
Una pregunta interesante es por que es importante tener una instruccin unless cuando es tan fcil negarla con
un not, ejemplo if not(edad < 18) La razn es porque el objetivo de Ruby es que se lea como si fuese
ingls.
En ruby tambin es posible utilizar versiones cortas de la expresin if y unless de la siguiente forma:
1
2
A este tipo de expresiones se les llama inline por estar en la misma lnea.
elsif
la instruccin elsif permite capturar la opcin en caso de que no se cumpla el if anterior.
1
2
3
4
5
6
7
8
9
Se debe tener mucho cuidado que utilizar dos ifs no es lo mismo que utilizar if y elseif, analicemos el siguiente
caso:
1
2
3
4
5
6
7
8
9
10
En este caso si la persona tiene 11 aos, mostrar que tiene ms de 10 y luego mostrar que es menor de 10 aos
y si la persona tiene 20 o ms entonces mostrar que tiene ms de 10 aos y ms de 20 aos.
Case
Cuando son muchos los posibles casos, se recomienda ocupar la instruccin case.
1
2
3
4
5
6
7
a = 10
case
when (a > 1 and a < 10)
puts "Estoy entre 1 y 10"
when a >= 10
puts "a es mayor que 10"
end
La instruccin Case puede ser utilizada de dos formas, con parmetro o sin, ya revisamos el primer caso, ahora
veamos el segundo.
1
2
3
4
5
6
7
8
9
10
case a
when 1..10
puts "Estoy entre 1 y 10"
when 11
puts "Soy 11, o sea ms que 10"
when String
puts "No soy un nmero soy un string"
else
puts "Ups, no se que hacer con #{a}"
end
Gracias al duck typing de ruby la instruccin case se convierte en una herramienta bastante poderosa para
manejar diversas situaciones.
Operador ternario.
El operador ternario es una variante del if que permite asignar un valor u otro dada una condicin.
la lgica es la siguiente:
si_es_cierto ? resultado_cierto : resultado falso.
llevado a ruby sera:
a == 5 ? b = 1 : b = 2
Ciclos
Los ciclos son instrucciones que permiten repetir una o un conjunto de instrucciones, aunque siempre debemos
tener cuidado de que no sea un nmero infinito de veces.
Times
La forma ms fcil de repetir una instruccin o grupo de instrucciones es con la funcin times
1
2
3
5.times do
puts "repitiendo"
end
Adems si queremos mostrar el nmero de la repeticin podemos hacerlo utilizando una variable.
1
2
3
5.times do |i|
puts "repitiendo: #{i}"
end
Tanto times como muchas de las instrucciones que veremos a continuacin reciben bloques, estos son tan
importantes en ruby que tienen su propio captulo en este libro, pero por ahora es importante entender que
todos los bloques tienen dos posibles sintaxis.
La primera es con do y end como vimos previamente, y la segunda es entre llaves
Desafio
While
while permite repetir una instruccin mientras se cumpla una condicin, la sintaxis del while es:
1
2
3
while (condicion) do
end
Mientras la condicin sea verdadera todo lo que est dentro del bloque se repetir.
while tiene una sintaxis alternativas al igual que el if
Y funciona de la misma forma, al igual que en el caso anterior el cdigo se ejecutar mientras la condicin sea
verdadera.
La instruccin while no puede recibir un bloque :(
Until
Until es la versin negativa del while, la idea al igual que unless es evitar ocupar el operador not que hace ms
difcil leer ciertas expresiones (ademas como operador lgico sigue reglas que en ciertas ocasiones puede ser
difcil de entender, como por ejemplo !a y !b = !(a o b)
La sintaxis es:
1
2
3
until (condicion) do
end
Al igual que while tambin puede ser usada en una sola lnea
For
for es una instruccin que nos permite iterar sobre rangos y arreglos, por ejemplo:
1
2
3
for i in 0..10
puts i
end
Para un arreglo tenemos varias opciones parar iterarlo, principalmente se dividen en sobre los ndices, los
arreglos, y ambos simultneamente.
Por ndice
1
2
3
4
a = [9,3,5,7,1,2,3]
for i in 0..a.length
puts a[i]
end
Por elemento
1
2
3
4
a = [9,3,5,7,1,2,3]
for element in a
puts element
end
Recordar que element en este caso es simplemente una variable que sirve para iterar por sobre los valores de a,
no es una palabra reservada.
Utilizando each
1
2
3
4
a = [9,3,5,7,1,2,3]
a.each do |element|
puts element
end
a = [9,3,5,7,1,2,3]
a.each_with_index do |element, indice|
puts "el elemento #{element} en la posicin #{indice}"
end
1
2
prng = Random.new
prng.rand(100)
1
2
3
4
begin
puts "escriba si para continuar"
input = gets.chomp
end while(input != "si")
El problema de 3n + 1
La conjetura de collatz dice que para cualquier nmero positivo entero:
Si el nmero es par se divide en dos Si el nmero es impar se multiplica por 3 y se le suma 1.
Si ahora tomamos el nmero resultante y aplicamos el mismo criterio sucesivamente esta secuencia tarde o
temprano el nmero resultante ser 1.
Ejemplo
10 => 5 => 16 => 8 => 4 => 2 => 1
Lo que debes hacer es crear un mtodo que permita mostrar toda la secuencia a partir de un nmero ingresado
por el usuario.
Bsqueda binaria
Utilizando exclusivamente ifs y while es posible construir un pequeo juego donde el ordenador adivina un
nmero que tu piensas entre 1 y 1000 en un mximo de diez intentos.
Escoges un nmero, La idea ahora es partir buscando en la mitad del arreglo, por ejemplo en la posicin 500, si
el jugador dice que es mayor, buscaremos en la 750, luego si dice que es menor buscaremos en 675, y as
sucesivamente hasta encontrar el nmero
Break
La instruccin break es capaz de romper un ciclo, la podemos ocupar directamente o dentro de una condicin,
ej:
1
2
3
4
5
6
10.times do |i|
puts i
break
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
i = 0
while (i < 100)
puts i
if i > 5
break
end
i = i + 1
end
#
#
#
#
#
#
#
#
0
1
2
3
4
5
6
=> nil
Ciclos anidados.
Es perfectamente posible anidar ciclos, o sea escribir un ciclo de otro, esto tiene varias utilidades, desde las ms
obvias como repetir un ciclo n veces hasta mostrar todos los elementos que existen dentro de una matriz.
Supongamos que por algn motivo queremos generar la secuencia:
1
2
3
4
0
1
1
1
0
1
2
3
5
6
7
8
9
10
11
12
1
2
3
4
5
1
1
1
2
2
2
.
5
4
5
6
1
2
3
.
5
6.times do |i|
6.times do |j|
puts "#{i} #{j}"
end
end
Desafos
Similar al ejercicio anterior genera todas las combinaciones de 4 nmeros posibles del nmero cero al diez.
Genera todas las secuencias de nmeros posibles entre tres nmeros tales que el nmero de la izquierda
siempre sea menor o igual que el de la derecha
ej:
111
112
11.
1 1 10
1 2 2 # el trmino 1 2 1 no podra aparecer
Antes de avanzar al siguiente captulo deberas ser capaz de contestar estas preguntas, si no te recomiendo que
vuelvas a leerlo y lo contestes
Preguntas
Por qu es bueno escribir siempre las condiciones en sentido positivo?
Cul es la diferencia entre if y unless?
De un ejemplo de un if inline?
Cul es la diferencia entre while y until?
7) Mtodos
En muchos lenguajes existe la posibilidad de agrupar cdigo y darle un nombre para reutilizarlo a voluntad, a
esto se le llaman funciones.
En lenguajes como en ruby no existen las funciones, pero existe algo muy similar llamado mtodos y sirven para
exactamente lo mismo construir
Definiendo un mtodo
El mtodo ms bsica que podemos definir:
1
2
def nombre_metodo()
end
Dentro de la definicin de un mtodo existe: - def (para indicar que se va a definir una funcin) - el nombre - los
parmetros (que van dentro de los parntesis) - ej: metodo(x, y) - el retorno
Ejemplo de un mtodo ms complejo:
1
2
3
def perimetro(r)
return 2 * Math.pi * r
end
perimetro(10)
De esta forma uno define una funcin y luego la ocupa varias veces.
Retorno implcito
En ruby el retorno de la funcin es siempre la ltima lnea, por lo tanto podramos escribir el cdigo anterior de
la siguiente forma y sera exactamente lo mismo.
1
2
3
def perimetro(r)
2 * Math.pi * r
end
1
2
3
def foo(x)
true if x > 18
end
En este caso el mtodo foo devolver true si x es mayor de 18, pero en lugar de devolver falso si es menor,
devolver nulo, luego no es lo mismo nulo que falso y tendremos problemas con nuestro cdigo, una mejor
solucin sera:
1
2
3
4
5
6
7
def foo(x)
if x > 18
true
else
false
end
end
Otro motivo por el cual tenemos que tener mucho cuidado con el retorno implcito es que agregando un puts en
la ltima lnea para saber el valor de una funcin romperamos el retorno, imaginemos este caso:
1
2
3
4
5
6
7
8
def foo(x)
if x > 18
true
else
false
end
puts x
end
El mtodo puts muestra en pantalla el valor de x, pero luego devuelve nil, as es como funciona puts y otros
mtodos para mostrar en pantalla, eso no tiene nada de malo, el problema es que los mtodos devuelven el valor
de la ltima lnea, o sea en este caso se devolver nil y nuestro mtodo dejar de funcionar.
Antes de pasar a otro tema veamos una forma ms corta y ms elegante de implementar el mismo mtodo.
1
2
3
def foo(x)
x > 18
end
Parmetros
Un mtodo puede recibir diversos parmetros, los parmetros son variables que puede recibir un mtodo.
Ejemplo:
1
2
3
4
5
6
def mostrar_valores(a,b)
puts a
puts b
end
mostrar_valores(5,8)
1
2
3
4
5
6
7
8
def mostrar_valores(c,d)
puts c
puts d
end
a = 5
b = 8
mostrar_valores(a, b)
En el momento del llamado se asignan los valores, o sea, cuando se hace el llamado mostrar_valores(a,b) ruby lo
que hace es remplazar eso por mostrar_valores(5,8), luego dentro del mtodo lo que hace es hacer c = 5, d = 8,
el orden siempre se conserva.
1
2
3
def foo(a, b = 5)
puts b
end
Si llamamos a foo como foo(8) entonces ruby lee que a es el valor 8, como b no fue pasado lo toma como
5 y eso es lo que muestra en pantalla.
1
2
3
4
5
6
7
def foo()
a = 7
end
a = 5
foo()
puts a
1
2
3
4
5
6
def foo(a, b)
puts b
puts a
return
foo(2,5)
Qu muestra foo? 2 y 5 o 5 y 2?
La respuesta correcta es 5 y 2 debido a que cuando llamamos a foo, a toma el valor 2 y b toma el valor 5, luego
mostramos b primero.
Un ejemplo ms, ahora lidiando con el tema del ambiente y la sobreescritura de variables
1
2
3
4
5
6
7
8
def foo(a, b)
puts b
puts a
return
a = 2
b = 5
foo(b,a)
Qu se muestra en pantalla?
El llamado a foo en la lnea 8 se hace con los valores 5,2, o sea foo(5,2), luego dentro de foo a tendra el valor 2 y
b el valor 5, y como el orden se muestra al revs se muestra primero 5 y luego 2.
1
2
defined? $x
# => "global-variable"
Mtodos recursivos
Los mtodos recursivos son mtodos que se llaman as mismo, son muy tiles para resolver problemas de
inteligencia artificial, de investigacin de operaciones, de bsqueda o problemas matemticos, en el contexto de
una aplicacin en Rails muy rara vez se utilizan.
En matemticas suelen existir expresiones del tipo:
f (n) = f (n 1) + 10
lease que el siguiente termino de la funcin se calcula como el termino anterior + 10 o sea:
f (2) = f (1) + 10 f(1) = f(0) + 10
Este tipo de expresiones son muy fciles de modelar con funciones recursivas.
1
2
3
def fun(x)
fun(x - 1) + 10
end
1
2
3
4
5
6
7
def fun(x)
puts "con x = #{x}"
if x > 1
resultado = fun(x - 1) + 10
end
resultado || 0
end
Entonces si llega al final del cdigo y no ha terminado devuelve el resultado y cuando llega al ltimo nmero
devuelve cero.
Veamos otra operacin, una famosa, fibbonacci, esta serie parte:
1 1 2 3 5 8 13 21 35
En la serie de fibbonacci todos cada nmero de la serie viene dado por la suma de sus dos nmeros anteriores, o
sea f(n) = f(n-1) + f(n-2), y el primer trmino f(0) es 1 y el segundo trmino o sea f(1) tambin es 1.
1
2
3
4
5
6
7
8
def fibo(x)
if x > 2
resultado = fibo(x - 1) + fibo(x - 2)
else
return 1
end
resultado
end
puts fibo(6)
Preguntas
Cul es la diferencia entre una variable global y una local?
Qu es un mtodo?
Cul es la diferencia entre un mtodo y una funcin?
Cmo se hace para que un mtodo tenga valores para los parmetros por defecto?
Importa el orden de los parmetros en un mtodo?
Existen las funciones en ruby?
Qu se entiende por scope?
Todas los mtodos devuelven valores? Cules no?
Qu es un mtodo recursivo?
Qu es el stack?
Cul es la diferencia entre break y return?
En qu consiste el error SystemStackError: stack level too deep ?
8) Arrays
Creando arrays
Ya tuvimos un primer acercamiento a los array, pero en este captulo aprenderemos a sacarle provecho.
Hay dos formas de crear un array, la primera es con los [], la segunda es ocupando la clase Array
Ejemplos:
1
2
a = [1,2,3,4]
# => [1, 2, 3, 4]
El constructor del array es contraintuitivo, puede recibir uno o dos parmetros, si slo hay un parmetro se crea
un array vaco con la cantidad de elementos indicadas por el parmetro:
1
2
a = Array.new(4)
# => [nil, nil, nil, nil]
Y cuando hay dos parmetros el segundo es un elemento que se repite n veces, donde n es el primer parmetro
1
2
a = Array.new(3,0)
# => [0,0,0]
ndices
Los arrays tienen dos partes, una son los elementos, o sea el contenido dentro del array, la otra es el ndice, y
permite acceder al elemento que est dentro, los ndices van de cero hasta n - 1, donde n es la cantidad de
elementos del arreglo.
O sea en un arreglo que contiene 5 elementos, el primer elemento est en la posicin cero, y el ltimo en la 4.
1
2
a = Array.new(3,0)
# => [0,0,0]
3
4
5
6
a[2]
# => 0
a[3]
# => nil
Los ndices tambin se pueden utilizar con nmeros negativos y de esta forma referirse a los elementos desde el
ltimo al primero.
1
2
a = [1,2,3,4,5]
a[-1] # => 5
Contando elementos
Podemos contar la cantidad de elementos dentro de un array con el mtodo .count o con el mtodo .length
1
2
3
4
a.count
# => 5
a.length
# => 5
1
2
3
4
Tiene el mismo valor que b[0] porque no cambiamos ni a ni b, cambiamos uno de los elementos adonde ambas
variables apuntaban.
1
2
3
4
b = a.dup
b[0] = "probando el cambio"
a.inspect
b.inspect
Lo importante de los arrays como contenedores es saber manipularlos, hay dos formas de acceder a los datos
dentro de un array, secuencialmente y aleatoriamente
Lo forma secuencial consiste en ver los datos en orden, ya vimos previamente dos de esas formas.
Con for
1
2
3
4
a = [1,2,6,1,7,2,3]
for i in a:
puts a[i]
end
Con each
1
2
3
4
a = [1,2,6,1,7,2,3]
a.each do |i|
puts i
end
.each es un mtodo muy interesante que realiza dos cosas, al primera es aplicar todas las operaciones definidas
dentro del bloque, la segunda es devolver el arreglo original.
1
2
3
b = a.each do |i|
puts i**2
end
Ahora b contiene los mismos valores de a y por explicado anteriormente si cambiamos algn valor de b cambiar
el valor de a.
Map
La primera es map, o collect, ambas son exactamente iguales. Map sirve para aplicar una operacin a cada
elemento del array, y devuelve un array con las operaciones aplicadas, ejemplo:
1
2
3
4
5
a = [1,2,3,4,5,6,7]
b = a.map do |e|
e * 2
end
# => [2, 4, 6, 8, 10, 12, 14]
O en su forma express:
1
2
a = [1,2,3,4,5,6,7]
b = a.map {|e| e * 2}
map y collect son exactamente lo mismo, no hay ninguna diferencia entre ocupar uno u otro.
1
2
a = [1,2,3,4,5,6,7]
b = a.collect {|e| e * 2}
Select
La segunda operacin funcional muy til sobre array es select, select permite sacar de un arreglo todos los
elementos que no cumplan un criterio, select al igual que map devuelve un array nuevo sin modificar el original
1
2
3
4
a = [1,2,3,4,5,6,7]
b = a.select{ |x| x % 2 == 0} # seleccionamos todos los pares
# => [2,4,6]
Hay funcin que hace lo contrario a select, se llama reject y lo que hace es eliminar a todos los elementos del
arreglo que no cumplan con el criterio.
1
2
b = a.reject{ |x| x % 2 == 0}
# => [1, 3, 5, 7]
.select y .reject no estn solo limitados a nmeros, por ejemplo supongamos que tenemos un arreglo que tiene
nmeros y palabras y queremos seleccionar solo las palabras.
Inject
Inject permite operar sobre todos los resultados pero en lugar de devolver un arreglo, devuelve un nico valor
con el resultado de las operaciones, por lo mismo en el bloque hay que pasar dos variables, la que itera y la que
guarda el resultado.
Ms adelante en este libro estudiaremos los procs los procs son perfectos sustitutos para los bloques y funcionan
muy bien en conjunto con las operaciones como map, filter e inject.
Gracias a los procs y al mtodo .to_proc de ruby es posible utilizar lo siguiente:
1
2
Group_by
.group_by permite agrupar los elementos de un array acorde al criterio especificado en el bloque, a diferencia de
los otros mtodos group_by devuelve un hash donde la llave es el criterio de aceptacin en el grupo y el valor es
un array con los elementos que cumplen con la condicin.
Para ejemplificar agrupemos todos los nmeros dentro de un array acorde si son pares o no.
1
2
a.group_by {|x| x % 2 == 0}
# => {false=>[1, 3, 5, 7], true=>[2, 4, 6]}
Un array puede contener elementos de muchos tipos, as que podramos agruparlo acorde al tipo con:
1
2
a.group_by{|x| x}
# => {1=>[1, 1], 3=>[3], 2=>[2, 2], 6=>[6], 8=>[8], 9=>[9]}
Nos devuelve un hash donde el key es el elemento y en el value aparece un elemento por cada repeticin, ahora
el siguiente paso sera contar cada uno de esos, para eso ocuparemos un map, para devolver un arreglo nuevo que
tenga el elemento y la cantidad de repeticiones.
1
2
Ordenando arreglos
Es posible ordenar todos los elementos dentro de un arreglo utilizando .sort
1
2
3
a = [2,82,1,5,6]
a.sort
# => [1, 2, 5, 6, 82]
Pero si dentro de nuestro arreglo hubiese un tipo de datos que no fuera compatible con la comparacin, como
por ejemplo un string, entonces obtendramos un error.
1
2
3
a = [2,82,1,5,6, "90"]
a.sort
ArgumentError: comparison of Fixnum with String failed
Esto lo podemos corregir ocupando sort_by, en lugar de sort, el mtodo sort_by recibe un bloque o proc que
nos permite explicar como comparar.
Desordenando arreglos
1
2
a.shuffle
[5, 1, 6, 82, 2]
1
2
a = [1,2,3]
b = [3,4,5]
Suma de elementos
Al conjunto a se le suman los elementos del conjunto b
1
2
a + b
# [1,2,3,3,4,5]
Diferencia de elementos
Al conjunto a se le quitan los elementos del conjunto b
1
2
a - b
# [1, 2]
Unin
Los elementos del conjunto a o los elementos del conjunto b
1
2
a | b
# [1,2,3,4,5]
Intereseccin
Los elementos del conjunto a y los elementos del conjunto b
1
2
a & b
# [3]
Push
a un array podemos agregarle un dato al final ocupando el mtodo .push
1
2
3
a = [0,1,3,4]
a.push(5)
# => [0,1,3,4,5]
1
2
a << 5
# => [0,1,3,4,5]
Pop
.pop es un mtodo que nos permite obtener y sacar el ltimo elemento de un array
1
2
a = [0,1,3,4,5]
a.pop
3
4
5
# => 5
a.inspect
# => 0,1,3,4
1
2
3
4
5
6
super_array = [[1,2,3],[4,5,6],[7,8,9]]
super_array.each do |array|
array.each do |ele|
puts l
end
end
Es posible aplanar un array multidimensional para convertirlo en un array normal ocupando el mtodo .flatten
1
2
super_array.flatten
# => [1, 2, 3, 4, 5, 6, 7, 8, 9]
El ltimo punto que tenemos que cubrir es particularmente delicado, en ruby no existe un deep dup (en rails si
existe), por lo que debemos ser extra cuidadoso al copiar arrays multidimensionales.
1
2
3
4
5
6
7
8
super_array = [[1,2,3],[4,5,6],[7,8,9]]
new_array = super_array.dup
new_array[0] = [11,12,13]
super_array.inspect
[[1,2,3],[4,5,6],[7,8,9]]
new_array.inspect
[[11,12,13],[4,5,6],[7,8,9]]
hasta ah vemos que se comporta segn lo esperado, pero que pasa si en lugar de haber cambiado un arreglo
dentro hubisemos cambiado un elemento dentro dentro del arreglo.
1
2
super_array = [[1,2,3],[4,5,6],[7,8,9]]
new_array = super_array.dup
3
4
5
6
o de forma ms corta
new_array = super_array.map(&:dup)
Preguntas
Cul es el peligro al copiar una variable que contiene un arreglo?
Cul es el problema que pueda ocurrir al copiar un arreglo multidimensional?
Qu hace el mtodo dup?
Cmo se puede saber el largo de un array?
Qu devuelve el mtodo group_by?
Cul es la principal diferencia entre .each y .map?
Cul es la diferencia entre map y collect?
Que recibe como parmetro el mtodo .inject?
Qu devuelve el mtodo inject?
Cul es la diferencia entre select y reject?
Si a = [1,2,3,4,5] y b = ["hola", 1,2,5 ]
Qu devuelve a + b?
Qu devuelve a - b?
Es lo mismo a - b que b - a?
Qu devuelve a & b?
Qu devuelve a | b?
9) Hashs
Los hash son contenedores en el cual sus elementos en lugar de ser accedidos por un ndice se acceden por una
clave.
Creando un hash
Hay dos formas de crear un hash
con Hash.new
1
a = {}
la segunda es con :
a = Hash.new
1
2
3
a = {clave1:5, clave1:7}
(irb):94: warning: duplicated key at line 94 ignored: :clave1
=> {:clave1=>7}
Se suele utilizar smbolos como claves ya que estos no pueden ser cambiados y no tendra ningn sentido
cambiar una clave, ya que en ese caso sera otra clave.
a = {llave1: 5}
Para agregar un elemento a un hash basta con utilizar una clave nueva.
1
2
3
a[:llave2] = 7
a["llave2"] = 9
puts a
Hay que tener muchos cuidado con los claves en los hash, pues los smbolos y los strings son cosas
distintas.
Por ejemplo en el caso anterior en lugar de remplazar el valor de la :llave2 agregamos una nueva llave llamada
"llave2"
1
2
3
a = {k1: 5, k2:7}
a.delete(:k1)
puts a
Iterando un hash
los hash tiene clave y valor por lo tanto ocuparemos dos variables, una contendr la clave y la otra el valor.
1
2
3
4
1
2
3
require 'pp'
a = {k1: 5, k2:7}
pp a.to_a
Ocupamos la libreria pp pretty print para mostrar el arreglo, pero la transformacin del hash en un arreglo se
logra nicamente con el mtodo .to_a
1
2
[[1,2],[3,4]].to_h
# => {1=>2, 3=>4}
Si tenemos ms elementos podemos agruparlos, la parte de izquierda es la clave as que va sola, la parte de al
derecha.
1
2
[[1,[2,3]],[2,[3,4]]].to_h
# => {1=>[2, 3], 2=>[3, 4]}
Preguntas
Puede un hash tener dos claves iguales?
Puede un hash tener dos claves iguales pero una es un smbolo y la otra un string?
Cuntas variables se necesitan para iterar un hash?
Qu caractersticas tiene que tener un arreglo para convertirlo en hash?
Cmo se itera un hash?
Conceptos bsicos
Algunos conceptos que vamos a aprender en este captulo:
Clase
Objeto
Instancia
Herencia
Mtodo de clase
Mtodo de instancias
Atributos
Getters y Setters
Self
En ruby todo es un objeto, los nmeros son objetos del tipo Fixnum, y existen objetos de diversos tipos, como
por ejemplo: los Strings, los Array, y tanto True como False son objetos.
Los objetos nos permiten crear nuestros propios tipos de datos y nuestras propias operaciones para resolver los
problemas que necesitemos, adems nos permiten reutilizar el cdigo que hagamos para resolver problemas
similares.
Entonces tenemos la clase que es un molde de legos, que es capaz de imprimir objetos lego, los cuales tienen un
identificador (por ejemplo un nmero de serie), propiedades como el color y tamao; y comportamientos como
acoplarse y causar dolor infinito si uno los pisa.
En ruby para definir una clase:
1
2
class MoldeLego
end
Para crear un objeto a partir de ese molde hay que instanciarlo, para instanciar basta con utilizar el nombre de la
clase con el mtodo new, o sea:
lego1 = MoldeLego.new
lego1.object_id
Recordar que en ruby el uso de parntesis en los mtodos es opcional, por lo que
lego1.object_id es lo mismo que lego1.object_id()
Comportamientos
Ahora el secreto para entender bien esta parte es que tanto el molde (la clase) como el objeto pueden tener
comportamientos. Cuando los comportamientos son del molde se llaman mtodos de clase, cuando lo son del
objeto se llaman mtodos de instancia.
En este captulo abordaremos principalmente los mtodos de instancia.
Para agregar un mtodo de instancia, (o sea un comportamiento de un objeto) lo hacemos dentro de la
definicin de la clase.
class Vehiculo
2
3
4
5
def encender()
puts "rrmrmmmrmrmrm"
end
end
Los estados
Para guardar estados de un objeto ocuparemos variables de instancia, esta se distinguen porque empiezan con @
1
2
3
4
5
6
7
8
9
10
11
class Vehiculo
def encender()
@encendido = on
end
def apagar()
@encendido = off
end
def estado()
@encendido
end
end
Ahora con esta clase de vehculo podemos crear todos los que queramos y cada uno tendr un estado encendido
independiente de los otros.
Ejemplo
1
2
3
4
5
a1 = Vehiculo.new
a2 = Vehiculo.new
a1.apagar
a1.estado
a2.estado
Podemos ver que mientras uno de los estados es prendido, el otro es apagado.
Mtodos de instancia
1
2
class MoldeLego
def initialize()
3
4
5
1
2
lego1 = MoldeLego.new
# Este Lego ahora tiene tamao 1
lego1.instance_variables
Pero no podemos acceder a esta porque los estados internos de los objetos estn protegidos, o sea si hacemos
lego1.size obtendremos un error.
Getters y Setters
Volviendo a nuestro ejemplo, si queremos rescatar el tamao tenemos un problema, no se puede acceder
directamente a los estados internos de un objeto debido al principio de encapsulamiento, para acceder a los
estados tenemos que crear getters y setters, o sea mtodos para obtener los estados (getters) y mtodos para
cambiarlos (setters), as que nuestro MoldeLego quedara as:
1
2
3
class MoldeLego
def initialize()
@size = 1 # las variables de instancia se definen con una @
4
5
6
7
8
9
10
11
end
def getSize()
return @size
end
def setSize(new_size)
@size = new_size
end
end
1
2
3
Exista una mejor forma de definir los getters y setters, que es hacerlo de tal manera que pareciera que estamos
trabajando directamente sobre el atributo.
o sea:
1
2
3
4
5
6
7
8
9
10
11
class MoldeLego
def initialize()
@size = 1 # las variables de instancia se definen con una @
end
def size()
return @size
end
def size=(new_size)
@size = new_size
end
end
1
2
3
4
Ahora en ruby es posible definir de forma simultnea la variable, los getters y setters a travs del attr_accessor:
1
2
3
4
5
6
class MoldeLego
attr_accessor :size
def initialize()
@size = 1 # las variables de instancia se definen con una @
end
end
Y para ocuparlo sera exactamente que la forma anterior, ahora volvamos a la idea de acoplar:
1
2
3
4
5
6
7
8
9
10
11
12
class MoldeLego
attr_accessor:size
# Pasamos un valor al constructor
# para poder crear legos de otros tamaos
# en caso de que se omita es de tamao 1
def iniatilize(size_orig = 1)
@size = size_orig
end
def acoplar(otro_lego)
return MoldeLego.new(self.size + otro_lego.size) # devolvemos un lego de la s
end
end
Cmo se usa?
1
2
3
lego1 = MoldeLego.new
lego2 = MoldeLego.new
lego3 = lego1.acoplar(otro_lego)
Todava podemos hacer una mejor ms, en ruby al igual que otros lenguajes se puede redefinir una operacin, en
este caso vamos a redefinir la suma (slo para los legos):
1
2
3
4
5
class MoldeLego
attr_accessor :size
# Pasamos un valor al constructor
# para poder crear legos de otros tamaos
# en caso de que se omita es de tamao 1
6
7
8
9
10
11
12
13
14
def initialize(size_orig = 1)
@size = size_orig
end
def +(otro_lego)
# devolvemos un lego de la suma del tamao de los otros legos
MoldeLego.new(@size + otro_lego.size)
end
end
1
2
3
lego1 = MoldeLego.new
lego2 = MoldeLego.new
lego3 = lego1 + lego2
Antes de avanzar vamos ver un mtodo de clase ms, este lo tienen todos los objetos y se llama to_s y permite
mostrar el objeto como si fuera un string, por ejemplo si mostramos un lego, ocupando:
puts lego3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MoldeLego
attr_accessor :size
# Pasamos un valor al constructor
# para poder crear legos de otros tamaos
# en caso de que se omita es de tamao 1
def initialize(size_orig = 1)
@size = size_orig
end
def +(otro_lego)
# devolvemos un lego de la suma del tamao de los otros legos
MoldeLego.new(@size + otro_lego.size)
end
15
16
17
18
19
def to_s
"soy un lego de tamao #{@size}"
end
end
1
2
3
4
lego1 = MoldeLego.new
lego2 = MoldeLego.new
lego3 = lego1 + lego2
puts lego3
1
2
3
4
class MoldeLego
attr_accessor :size
@@cuenta_legos = 0
end
A diferencia de la variables de instancias estas no requieren definirse en el constructor, pues existen para la clase,
independiente de si existen legos o no.
Ahora si quisiramos contar cada vez que se crea un lego nuevo, podramos hacerlo as:
1
2
3
4
class MoldeLego
attr_accessor:size
@@cuenta_legos = 0
def iniatilize(size_orig = 1)
5
6
7
8
@size = size_orig
@@cuenta_legos += 1
end
end
Si quisiramos obtener las variables de clase toparamos con el mismo problema de encapuslamiento, tenemos
que definir getters y setters para estas, ahora lamentablemente la sintaxis en ruby no es tan bonita, se hace as:
1
2
3
4
5
6
7
8
9
class MoldeLego
class << self; attr_accessor :cuenta_legos end
attr_accessor:size
@@cuenta_legos = 0
def iniatilize(size_orig = 1)
@size = size_orig
@@cuenta_legos += 1
end
end
Slo una cosa est quedando en el tintero, Cmo crear mtodos de clase ms all de los getters y setter?
Para eso vamos a ocupar self, para este ejemplo voy a definir un getter de la forma no tan bonita para la cuenta
legos, sera as:
1
2
3
4
5
6
class MoldeLego
class << self; attr_accessor :cuenta_legos end
def self.getCuentaLegos()
return @@cuenta_legos
end
end
lego.class.cuentaLegos
MoldeLego.cuentaLegos
Es importante no confundir esta metfora con la herencia, esta ltima es una caracterstica de los objetos que les
permite obtener los mtodos y propiedades de sus padres, pero este tema lo abordaremos posteriormente.
las coordenadas con complejos las podemos describir de la forma x + i * y, donde x es la coordenada horizontal,
e i*y es la coordenada vertical, siguiendo el dibujo y ocupando notacin compleja el punto A estara en -5, 3i, el
punto B en 6, 5i
Ruby ya tiene una forma de manejar nmeros complejos con la clase Complex, pero para ilustrar la utilizacin de
objetos nosotros crearemos la clase Z
Para nuestra clase necesitamos dos coordenadas, que llamaremos a y b, esas sern las variables de nuestra clase
que crearemos a continuacin:
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
class Z
attr_reader :a, :b
def initialize(a, b)
@a = a
@b = b
end
def +(z)
if z.class == Fixnum
Z.new(@a + z, @b)
else
Z.new(@a + z.a, @b + z.b)
end
end
def to_s
return "#{@a} + #{@b}i"
end
end
a = Z.new(2,3)
b = Z.new(2,3)
a + b
Preguntas
Cul es la diferencia entre una clase y un objeto?
Qu hace el operador new?
Qu significa instanciar?
Para qu sirven los getters?
Qu hace def x=(x)?
Qu hace attr_reader :x?
1
2
3
4
5
6
class Fraccion
def initialize(x,y)
@x = x
@y = y
end
end
Los alumnos tendrn que reescribir la suma y la resta para que se respete la suma siempre y cuando sea la misma
base (no es necesario implementar mnimo comn mltiplo u simplificar los resultados), en caso de que sea
distinta base se debe devolver un arreglo con las fracciones
ejemplo:
1 1
2
+ =
4 4
4
pero:
1 1
1 1
+ =[ , ]
4 3
4 3
1
2
3
class Fraccion
attr_reader :num, :denom
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Alumnos y notas
Crear la clase alumno, cada alumno tiene un arreglo de calificaciones (enteros) y un nombre
Crear un arreglo con al menos 4 alumnos, cada alumnos.
Se pide:
Calcular el promedio de notas del curso
Encontrar al alumno que tiene le promedio de notas ms alto y devolver su nombre.
Revisin del mtodo inject. Sumatoria, Concatenacin de nombres
Solucin:
1
2
3
4
class Student
def initialize(name, grades)
@name = name
@grades = grades
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
end
def average
@grades.inject(&:+) / @grades.count.to_f
end
end
students
students
students
students
= []
<< Student.new("Efran", [9, 5, 7, 8])
<< Student.new("Martn", [10, 10, 10, 10])
<< Student.new("Gonzalo", [3, 5, 2, 3])
Zombies
Crear el objeto Zombie con coordenadas x,y y un mtodo para caminar
Crear el objeto Personas, las personas no se mueven pero si tienen posicin x,y
Los zombies si estn a una coordenada de distancia se comen a la persona y esta es convertida en zombie.
Para hacerlo ms sencillo en cada iteracin cada zombie recibir un arreglo con todas las persona verificar si se
cumplen las condiciones para ser comido y devolver a la persona devorada, con eso hay que sacarla del arreglo
1
2
3
4
5
#hint:
class Zombie
def eat(personas)
personas.each {}
# tambin es posible ocupar delete_if
Se pide: Crear 10 zombies, y 5 personas Correr el programa hasta que todas las personas sean devoradas en el
apocalipsis. Mostrar cuantas iteraciones requiri el proceso
Solucin temporal
class Zombie
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
zombies.each do |z|
z.walk
end
puts "--------"
end
Baraja de cartas
Se pide construir la clase carta, que debe guardar la pinta y el nmero de la carta.
Se pide construir un arreglo con todas las cartas, para conformar la baraja
Tambin se debe construir la clase mano, esta tiene 5 cartas siempre, y debe haber un mtodo que reciba la
baraja, y permite poner la primera carta de la mano al final de la baraja y la primera carta de la baraja al final de la
mano.
Despus de hacer el intercambio se debe barajar hint, ocupar el mtodo shuffle del array.
Batalla naval
Se pide:
Hacer una diagrama con objeto de las componentes
Un diagrama posible es Flota
Cada flota tiene naves y estado donde est viva si tiene al menos una nave funcional.
Cada nave tiene partes y tamao (donde tamao es la cantidad de partes) y estado que indica si est
hundida o no. Las naves son de distintos tamaos, (eso se refleja en la cantidad de componentes que
tienen), las naves pueden estar alineadas verticalmente o horizontalmente, pero no diagonalmente ni nada
con forma de L o cosas raras.
Cada parte tiene un set de coordenadas y un indicador que muestra si est golpeada (si ya le han dado o
no), cuando todas las partes son golpeadas la nave queda marcada como hundida
Un jugador controla una flota.
No pueden haber dos partes distintas en las mismas coordenadas
Preguntas
Cmo podemos agregar un mtodo de instancia a todos los arreglos?
Cmo podemos crear un contador de instancias?
Si creramos una librera de funciones matemticas, sera mejor ocupar mtodos de clase o de instancia?
Si creramos una clase para manejar una baraja de cartas, sera mejor ocupar mtodos de clase o de
instancia?
Si una clase tiene definido dentro mtodos de clase, puede tener mtodos de instancia?
Cmo se define un mtodo de clase?
Se puede definir una variable de clase en el constructor?
Si se tiene un arreglo con n objetos, cmo se puede hacer para aplicarle un mtodo a cada uno.?
Cul es la diferencia entre inject y map?
1
2
3
4
5
6
7
8
9
a = [1,2,3,1]
# bloque con brackets
a.each {|x| x + 1}
# bloque con do
a.each do |x|
x + 1
end
Adems los bloques son closures, o sea, lo que pasa dentro del bloque queda dentro del bloque, si declaramos
una variable dentro de un bloque fuera del bloque esta no existe.
1
2
3
4
5
6
7
a = [1,2,3,1]
a.each do |x|
x + 1
z = 1
end
puts z
# => NameError: undefined local variable or method `z' for main:Object
No todos los bloques son exactamente iguales, algunos aceptan inputs, como en el caso anterior con el
parmetro x y otros no los aceptan, adems algunos devuelven informacin y otros no, son muy parecidos a los
mtodos en este mbito, pero funcionan en conjunto.
Ahora para mayor entendimiento vamos a crear nuestro primer mtodo que acepta un bloque.
1
2
3
def mi_bloque()
yield "respuesta"
end
1
2
mi_bloque() {}
# => nil
Y si le pasamos un parmetro:
1
2
mi_bloque() {|x| x}
# => "respuesta"
Una cosa muy interesante que tienen los bloques es que cuando haces un mtodo no le tienes que decir que va a
recibir un bloque, ruby esperar encontrar el yield para ejecutarlo.
Probmoslo:
1
2
3
4
5
def mi_bloque2()
return "soy un bloque" # y no tengo yield
end
mi_bloque2 {|x| x = 0; puts x} #si se ejecutara deberamos ver un cero
# => "soy un bloque"
Pero no vemos el cero porque no se ejecuta, y cul es el motivo?, la respuesta es que no hay un yield en el
mtodo.
Y si lo hubiese?
def mi_bloque()
1
2
3
4
5
6
7
8
yield "hola"
return "soy un bloque"
end
mi_bloque {|x| x = 0; puts x} #si se ejecutara deberamos ver un cero
# => 0
# => "soy un bloque"
1
2
3
4
5
6
7
8
9
class Array
def mi_each
i = 0
while i < self.size
yield(self[i])
i+=1
end
end
end
Aqu vemos algo nuevo, yield acepta parmetros, ese parmetro es lo que devuelve, entonces en este caso est
devolviendo el array en el ndice i.
Para probarlo, necesitaramos hacer:
1
2
a = [1,2,3,4]
a.mi_each {|x| puts x}
1
2
3
4
5
#
#
#
#
#
1
2
3
4
=> nil
Ahora el each de ruby devuelve el array completo al final, eso lo podemos lograr devolviendo self.
1
2
3
4
5
6
7
8
9
10
class Array
def mi_each
i = 0
while i < self.size
yield(self[i])
i+=1
end
self
end
end
Procs
Tambin es posible guardar un bloque en una variable utilizando procs, utilizando el mtodo mi_each que
creamos previamente podramos hacer:
1
2
3
Adems como bonus podemos ejecutar el contenido dentro de un proc ocupando el mtodo call.
ejemplo:
1
2
3
1
2
3
4
5
6
7
8
9
la funcin mi_bloque devolver 40, porque el ltimo en ejecutarse es proc2 y proc2 devuelve x multiplicado
por 4.
Lambdas
Existe otro tipo de proc llamado lambda, el cual se utiliza poco y por el mismo motivo no ahondaremos, las
diferencias con el proc son sutiles, mientras el proc no revisa los parmetros, lambda si lo hace
1
2
3
4
5
6
7
8
9
10
Una diferencia importante entre proc y lambda es que manipulan de manera distinta la palabra return. Mientras
un proc rompe la ejecucin en ese punto del cdigo, una lambda no
1
2
3
4
5
6
def test_proc
Proc.new { return "se regresa del bloque de proc"}.call
return "se regresa del metodo"
end
def test_lambda
7
8
9
10
11
12
13) Archivos
Leyendo archivos
Ruby tiene la Clase File que permite abrir, leer, escribir, rebobinar y cerrar archivos.
Para abrir un archivo como lectura:
1
2
file = File.new("archivo.txt","r")
buffer = file.read
y listo ya trajimos todo el contenido del archivo como un string a memoria en una variable llamada buffer.
Guardando archivos
Si quisieramos ahora guardar esta informacin en otro archivo podemos hacer:
1
2
3
output_file = File.new("archivo2.txt","w")
output_file.write(buffer) # o cualquier string
output_file.close
Es importante cuando estemos guardando informacin en archivos que hagamos el close, si no existe la
posibilidad de que perdamos la informacin.
1
2
3
4
Alumno1,
Alumno2,
Alumno3,
Alumno4,
10, 7, 10
10, 8, 10
9, 9, 10
0, 5, 9
Vamos a abrir el archivo y separarlo por saltos de lnea, de esa forma vamos a quedar con un arreglo final donde
la lnea cero corresponde a la primera lnea del archivo y as sucesivamente.
1
2
3
file = File.new("archivo.txt","r")
buffer = file.read
lines = buffer.split("\n")
Ahora dependiendo de lo que queramos hacer podramos trabajar directamente con el arreglo, pero una forma
muy cmoda de trabajar es mapear los datos a un objeto.
1
2
3
4
5
6
class Alumno
def initialize(name, n1, n2, n3)
@name = name
@notas = [n1,n2,n3]
end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Alumno
def initialize(name, n1, n2, n3)
@name = name
@notas = [n1,n2,n3]
end
def to_s
"Alumno #{@name} con notas #{@notas[0]}, #{@notas[1]}, #{@notas
end
end
alumnos = []
file = File.new("secreto","r")
buffer = file.read
16
17
18
19
20
21
lines = buffer.split("\n")
lines.each do |l|
data = l.split(',')
alumnos << Alumno.new(data[0], data[1], data[2], data[3])
end
puts alumnos
Preguntas
Cmo se declara un bloque, un Proc y un Lambda?
Si ejecutamos el mtodo each de un Array , de qu manera podemos enviar un Proc?
Cul es la diferencia entre un bloque y un Proc?
Cules son la principales diferencias entre Proc y Lambda?
Qu expresin requiero declarar dentro del cuerpo de un mtodo ( def ... ) para ejecutar el bloque
que se enva como argumento o parmetro?
Qu clase dentro del API de Ruby nos permite manipular archivos?
Qu mtodo podemos aplicar para leer un archivo?
Cuntos niveles y cules son los niveles de acceso que contamos para el manejo de archivos?
Qu tipo de dato se obtiene despus de la lectura de un archivo?
Cul es la expresin que representa el salto de lnea?
Para qu sirve el mtodo split de la clase String?
Si ejecutamos la sentencia open("libro.txt") y el archivo no existe en nuestro sistema, qu
recibimos al ejecutarla?
Qu mtodo sirve para cerrar el flujo de informacin cuando escribimos/creamos un archivo?
Qu clase tenemos en Ruby que representa carpetas o folders de nuestro filesystem?
1
2
3
4
Si observamos el error veremos que dice RuntimeError, hay varios tipos de errores.
Pero errores ms o errores menos es cmo utilizamos esto para nuestro beneficio, bueno el manejo de errores
tiene dos utilidades.
1. Ser capaz de interceptar los errores.
2. Ser capaz de levantar errores.
En un captulo anterior habamos creado la clase Z para nmeros complejos, ahora vamos a aprovecharla
1
2
class Z
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
attr_reader :a, :b
# attr_writer :a, :b
# attr_accessor :a, :b
def initialize(a, b)
@a = a
@b = b
end
def +(z)
if z.class == Fixnum
Z.new(@a + z, @b)
else
Z.new(@a + z.a, @b + z.b)
end
end
def to_s
return "#{@a} + #{@b}i"
end
end
En esta clase tenemos la capacidad de sumar nmeros complejos, an cuando uno de los valores no fuera
complejo, pero que pasa si nos pasan un string, vamos a obtener un error, porque va a intentar sumar la parte a y
un string no tiene un mtodo .a, pero eso no lo tiene por que saber la persona que ocupe esta librera.
La solucin es indicar que el tipo de datos es incorrecto.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Z
attr_reader :a, :b
# attr_writer :a, :b
# attr_accessor :a, :b
def initialize(a, b)
@a = a
@b = b
end
def +(z)
if z.class != Fixnum or x.class != Z
15
16
17
18
19
20
21
22
23
24
25
26
27
1
2
3
4
5
El cual es mucho ms claro para alguien que est ocupando nuestro cdigo.
1
2
3
4
5
6
7
begin
raise 'Soy un error'
rescue
puts 'pero fui salvado'
end
puts 'y ahora el programa corre de forma normal'
end
Debemos de tener cuidado de no caer en el antipatrn de meter todo nuestro cdigo dentro de un rescue, los
errores que lanzamos tienen que ser coherentes con nuestra aplicacin, por ejemplo como lo hicimos en el caso
de los nmeros complejos.
Es mejor utilizar las excepciones con casos especficos, como sera por ejemplo:
1
2
3
4
5
6
7
begin
# rescue Excepcion_especifica
# else # Otras excepciones
# end
touch secreto
file = File.new("secreto","r")
1
2
3
4
5
1
2
3
4
5
begin
file = File.new("secreto","r")
rescue
puts "El archivo no ha podido ser abierto"
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def read_students(filename)
file = File.new(filename,"r")
rescue
puts "El archivo no ha podido ser abierto"
return nil
end
buffer = file.read
lines = buffer.split("\n")
alumnos = []
lines.each do |l|
data = l.split(',')
alumnos << Alumno.new(data[0], data[1], data[2], data[3])
end
return alumnos
end
Qu ganamos?, ahora el programa no se cae, pero tambin perdimos la capacidad de ver el error, y esto es
importante para que otros programadores (o el usuario) puedan hacer uso de la informacin. Para evitar esto
vamos a capturar la excepcin en una variable para luego mostrarla.
1
2
def read_students(filename)
begin
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
file = File.new(filename,"r")
rescue Exception => e
puts "El archivo no ha podido ser abierto debido a #{e}"
return nil
end
buffer = file.read
lines = buffer.split("\n")
alumnos = []
lines.each do |l|
data = l.split(',')
alumnos << Alumno.new(data[0], data[1], data[2], data[3])
end
return alumnos
end
1
2
3
4
2,3
1,1
2,2
2,2
4,5
4,4
2,5
2,8
Incluir un mtodo para la clase lnea que permite preguntar si es una linea vertical, o una lnea horizontal y un
mtodo que permita calcular el largo.
1
2
3
4
5
6
7
8
9
10
class Linea
...
def horizontal?
...
end
def vertical?
...
end
11
12
end
Luego crear un mtodo que permita mostrar todas las lneas horizontales y todas las verticales.
Preguntas
Qu es una excepcin?
Qu ocurre con el flujo de la ejecucin de un programa en el momento que se lanza una excepcin?
Qu expresin nos permite lanzar una excepcin manualmente?
Cules son las partes que conforman un bloque que maneja una excepcin?
Cuntos rescue podemos declarar dentro de un bloque begin ... end ?
De qu manera obtenemos el objeto de la excepcin generada si cae en un rescue ?
Para qu sirve el bloque ensure ... end ?
Cul es la diferencia entre escribir un texto entre comillas simples y otra entre comillas dobles?
Qu se muestra en la terminal despus de ejecutar el siguiente bloque de cdigo?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
begin
puts "trying to execute process..."
raise NameError, "wrong name of process!"
else
rescue StandardError => e
puts "process must end because: #{e}"
begin
raise "Exiting..."
rescue Exception => e
puts "can't exit yet"
end
rescue NameError => e
puts "process must end because: #{e}"
ensure
puts "closing opened stream stuff"
end
/hol/.match('hola')
Patrones
Tiene vocales?
RegEx no solo nos permite buscar palabras exacta, nos permite buscar patrones
1
2
3
4
5
6
/[aeiou]/.match "zrk"
nil
/[aeiou]/.match "zerg"
#<MatchData "e">
/[aeiou]/.match "murcielago"
#<MatchData "u">
Los patrones que estn entre [] son uno o el otro, o sea se el patron es que tenga la letra a, o la e, o la i, o la o, o
la u.
Tiene nmeros?
Es posible saber si un texto tiene nmeros ocupando:
1
2
3
4
el signo guin permite realizar rangos siempre y cuando se est dentro de los corchetes []
1
2
3
4
1
2
patron_patente = /[A-Z][A-Z][A-Z]-[0-9][0-9][0-9]/
patron_patente.match "QTZ-410"
En este ejercicio aprendimos dos cosas ms, una es que las expresiones regulares se pueden guardar en variables,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/[0-9][0-9]+/.match "10"
# => #<MatchData "10">
/[0-9][0-9][0-9]+/.match "10"
# => nil
/[0-9][0-9]+/.match "100"
# => #<MatchData "100">
/[0-9][0-9]+/.match "1000"
# => #<MatchData "1000">
/[0-9][0-9]*/.match "10"
# => #<MatchData "10">
/[0-9][0-9][0-9]*/.match "10"
# => #<MatchData "10">
16) Scrapping
Scrapping es una tcnica de extraccin de datos de la web, y es muy similar a leer archivos, slo que en lugar de
abrir un archivos abriremos una pgina web.
Primero necesitamos incorporar la librera open-uri, eso lo hacemos con:
require 'open-uri'
Luego:
1
2
3
open(url) do |f|
page_string = f.read
end
1
2
3
4
5
6
7
8
require 'open-uri'
url = "https://twitter.com/desafiolatam"
open(url) do |f|
page_string = f.read
end
puts page_string
Pero ahora como leemos este string gigante que consiste en todo el HTML y del cual cuesta mucho sacar
informacin?.
Hay dos opciones buenas, una es Nokogiri, la otra Mechanize esta ltima ocupa a Nokogiri.
Introduccin a Mechanize
Como primera prueba vamos a hacer un mini scrap de la wikipedia.
Paso 1) instalar la gema Mechanize desde el terminal
1
2
3
4
5
6
7
8
9
10
11
require "mechanize"
def main()
agent = Mechanize.new
agent.user_agent_alias = 'Mac Safari'
agent.get("http://es.wikipedia.org/wiki/2014") do |page|
puts page.parser.css('#content').text
end
end
main()
Cmo funciona?
Primero crea una agente, este agente es una instancia de mechanize, se suele usar como nombre agent, o
mechanize,este agente ser el que se conecte con la pgina y la descargue.
En lnea 5 se especifica un user_agent as el servidor que te entrega la pgina creer que el navegador que la est
viendo es Safari, es posible tambin ocupar user_agents de Firefox, Chrome, o incluso Internet Explorer.
Finalmente el agente se conecta con la pgina de la wikipedia y muestra el contenido que est debajo dentro de la
etiqueta con id="content", o sea que este script no muestra la informacin que est en las barras de navegacin.
Para saber donde tienes que apuntar puedes dentro de una pgina web puedes entrar a ella y ocupar el inspector
de elementos.
1
2
3
4
require "mechanize"
def parse(url, tag)
agent = Mechanize.new
5
6
7
8
9
10
11
Navegando en google
1
2
3
4
5
6
7
8
9
10
11
12
13
require 'mechanize'
agent = Mechanize.new
agent.user_agent_alias = 'Mac Safari'
page = agent.get('https://www.google.com/')
form = page.forms.first
form['q'] = 'desafiolatam'
page = form.submit
page.search('a').each do |link|
puts link.text
puts link
puts "----"
end
Lyrics
Para ejercitar con un caso ms difcil vamos a crear un buscador de lyrics de canciones haciendo scrapping de la
pgina http://www.azlyrics.com/
Para eso tenemos que entrar al sitio, llenar el formulario, leer entre los resultados posibles, entrar al link del
resultado y finalmente mostrar los lyrics.
Una buena tcnica para la resolucin de problemas es dividirlos en componentes pequeas que podamos
resolver, para eso primero leeremos los lyrics de una pgina en especfico.
Para eso vamos a buscar Hello de Lionel Ritchie en la pgina, encontrar la url, y hacer un get de esa pgina con
mechanize.
1
2
3
4
5
6
7
8
require 'mechanize'
agent = Mechanize.new
agent.user_agent_alias = 'Mac Safari'
page = agent.get('http://www.azlyrics.com/lyrics/lionelrichie/hello.html'
page.search('div').each do |div|
puts div.text
end
XPATH
El problema del mtodo anterior es que vamos a obtener mucho ruido de la pgina que no nos interesa, lo
bueno es que el inspector de elementos de chrome (y el de otros navegadores) nos puede ayudar a seleccionar un
elemento especfico a travs del XPATH y como Mechanize est construido sobre Nokogiri lo soporta.
1
2
3
page.search('/html/body/div[3]/div/div[2]/div[6]').each do |div|
puts div.text
end
1
2
3
4
5
6
7
8
9
require 'mechanize'
agent = Mechanize.new
agent.user_agent_alias = 'Mac Safari'
page = agent.get('http://www.azlyrics.com/lyrics/scorpions/stilllovingyou.html'
)
page.search('/html/body/div[3]/div/div[2]/div[6]').each do |div|
puts div.text
end
Al probarlo veremos los lyrics, eso quiere decir que llegando a la pgina podemos leerlo, ahora tenemos que
lograr los pasos previos, o sea utilizar el formulario para buscar, y seleccionar la cancin respectiva.
El formulario de bsqueda
La parte del formulario es similar a la que hicimos en google, en la pgina principal tenemos que tomar el
formulario y pasarle un parmetro, con el inspector podemos ver que el nombre del input al igual que en google
es q
.
Luego de hacer la bsqueda llegaremos a otra pgina distinta que tambin tendremos que analizar.
1
2
3
4
5
6
7
8
9
require 'mechanize'
agent = Mechanize.new
agent.user_agent_alias = 'Mac Safari'
page = agent.get('http://www.azlyrics.com/') # Cargamos la pagina inicia
form = page.forms.first
form['q'] = 'hello lionel richie'
page = form.submit # Enviamos el formulario y recibimos la pgina resultante
puts results_page.body
Logramos llevar bien hasta la pgina de resultados de la bsqueda, el ltimo paso es llevarlo a la pgina de los
lyrics siguiendo el link, para este caso vamos a escoger siempre el primer resultado.
1
2
3
4
5
6
7
8
9
require 'mechanize'
agent = Mechanize.new
agent.user_agent_alias = 'Mac Safari'
page = agent.get('http://www.azlyrics.com/') # Cargamos la pagina inicia
form = page.forms.first
form['q'] = 'hello lionel richie'
page = form.submit # Enviamos el formulario y recibimos la pgina resultante
puts results_page.body
Vamos a ver que existen muchos links, algunos apuntan a resultados, pero otros son como la navegacin,
paginacin, publicidad, etc apuntan fuera y no nos sirven, ahora por suerte existe un patrn, todos los
resultados dentro de la pgina apuntan dentro de http://www.azlyrics.com/lyrics/
1
2
3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
require 'mechanize'
agent = Mechanize.new
puts "Qu lyrics deseas buscar?"
query = gets.chomp()
agent.user_agent_alias = 'Mac Safari'
page = agent.get('http://www.azlyrics.com/') # Cargamos la pagina inicia
form = page.forms.first
form['q'] = query
Una ltima mejora, en caso de no encontrar resultados lo notificaremos, y guardaremos la lgica en una funcin
para futura reutilizacin.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def scrap_lyrics(query)
require 'mechanize'
agent = Mechanize.new
agent.user_agent_alias = 'Mac Safari'
page = agent.get('http://www.azlyrics.com/') # Cargamos la pagina inicia
form = page.forms.first
form['q'] = query
Un gran problema del que debemos estar conscientes con el scrapping es que si en algn momento la pgina
cambia nosotros tendremos que cambiar nuestro algoritmo.
Desafo
Generar un archivo que contenga el listado de las 100 mejores pelculas de Netflix que aparecen en este post.
http://www.pastemagazine.com/articles/2015/10/the-100-best-movies-streaming-on-netflix-october-2.html
Preguntas
Para qu sirve el user agent?
Que devuelve form.submit?
1
2
self
# main
Main y top-level
Es en main donde es donde comnmente definen las variables y mtodos que no le pertenecen a ningn objeto,
aqu al igual que en cualquier otro objeto podemos guardar variables locales, de instancia y de clase.
1
2
3
4
5
@x = 5
instance_variables
# => [:@prompt, :@x]
self.instance_variables
# => [:@prompt, :@x]
Hay que recordar que el intrprete funciona distinto a un script, si usamos el mismo cdigo dentro de un archivo
.rb necesitas ocupar puts o inspect
1
2
3
4
@x =
@y =
puts
# =>
5
7
instance_variables.inspect()
[:@x, :@y]
1
2
3
4
5
6
def prueba
self
end
puts prueba()
# => main
1
2
3
4
5
6
7
8
9
10
11
12
class PruebaObjeto
def initialize
end
def returnself
self
end
end
o = PruebaObjeto.new()
# => #<PruebaObjeto:0x007fd8c107e7b0>
o.returnself
De este cdigo podemos aprender dos cosas, uno es que podemos devolver una instancia del objeto desde
cualquier mtodo y otra es que initialize siempre devuelve self, para dejarlo completamente claro vamos a
devolver otra cosa y ver que pasa.
1
2
3
4
5
6
7
8
9
class PruebaInitialize
def initialize
return 2
end
end
a = PruebaInitialize.new()
puts a
#<PruebaInitialize:0x007fd8c2045ea0>
Si initialize devolviera el valor pedido, en este caso 2, a debera tener el valor 2, sin embargo observamos que
devuelve self.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Foo
def initialize()
bar # forma implcita
self.bar # forma explcita
end
def bar()
puts "yeea"
end
end
Foo.new()
# yeaa
# yeaa
# => #<Foo:0x007fb1e286cba8>
O sea podemos llamar a los mtodos tanto de forma implcita o explcita y obtendremos los mismos resultados,
pero veremos ahora que esta diferencia tiene implicancias.
1
2
3
4
5
6
7
8
9
10
11
12
13
class Persona
attr_reader :x, :y
def initialize(x, y)
@x = x
@y = y
no_tocar
end
private
def no_tocar
puts "heeey !!!"
end
end
Si intentamos ocupar este mtodo desde dentro del constructor (o cualquier otro mtodo de instancia) veremos
que la clase existe, pero si lo intentamos ocupar directamente desde dentro de un instancia veremos que no
existe.
1
2
3
4
5
a = Persona.new(2,3)
# heeey !!!
a.no_tocar
# NoMethodError: private method `no_tocar' called for
<Persona:0x007fb8b4870860 @x=2, @y=3>
Existe otra restriccin, un mtodo privado tampoco puede ser llamado de forma explcita dentro de un objeto.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Persona
attr_reader :x, :y
def initialize(x, y)
@x = x
@y = y
no_tocar # llamado implcito funciona
self.no_tocar # pero llamado explcito no funciona
end
private
def no_tocar
puts "heeey !!!"
end
end
16
17
heeey !!!
NoMethodError: private method `no_tocar' called for #<Persona:0x007fb6a113d968 @
En el ejemplo anterior vimos que cuando se llama al mtodo implcitamente funciona, pero cuando se hace
explcitamente falla.
Mtodos protegidos
Los mtodos protegidos son parecidos a los privados en el sentido que una instancia no puede llamarlos
directamente, pero a diferencia de los privados estos si se pueden llamar de forma explcita.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Persona
attr_reader :x, :y
def initialize(x, y)
@x = x
@y = y
self.no_tocar ## llamado de forma explcita
end
protected
def no_tocar
puts "heeey !!!"
end
end
Persona.new(1,1)
# heeey !!!
Herencia
La herencia es una relacin entre clases, donde una clase hereda el comportamiento de otra, y sirve
especialmente para no tener que reescribir cdigo.
En un captulo anterior hicimos un ejercicio con personas y zombies, ahora vamos a refactorizarlo utilizando
herencia
1
2
class Persona
attr_reader :x, :y
3
4
5
6
7
8
9
10
11
12
13
14
def initialize(x, y)
@x = x
@y = y
end
end
class Zombie < Persona
end
z = Zombie.new(2,3)
z.class
# => Zombie
La herencia nos permite tratar al zombie como si fuera una persona, y de esta forma entregarle todo el
comportamiento que tiene la clase padre, aunque en este caso el comportamiento corresponde exclusivamente a
la clase Persona.
1
2
Zombie.new(2,3).class
# => Persona
1
2
3
4
Zombie.class
# => class
Zombie.superclass
# => Persona
1
2
Zombie.ancestors
# => [Zombie, Persona, Object, Kernel, BasicObject]
Ruby ocupa mucho la herencia internamente, de hecho todos los objetos heredan del objeto objeto
1
2
Persona.new.superclass
# => object
Tambin es posible preguntar si un objeto pertenece a alguna clase, o hereda de algunas clase, por ejemplo:
1
2
3
4
5
6
7
z = Zombie.new(1,1)
z.is_a?(Zombie)
# => true
z.is_a?(Persona)
# => true
z.is_a?(Fixnum)
# => false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Persona
attr_reader :x, :y
def initialize(x, y)
@x = x
@y = y
end
end
class Zombie < Persona
attr_reader :days_since_zombie
def initialize(x,y)
super(x,y)
@days_since_zombie = 0
end
end
Por ejemplo en este caso un Zombie tiene su propio mtodo initialize, por lo que cuando se cree Zombie.new se
llamar a este, pero dentro se llama al mtodo initialize del padre, el mtodo super llama al mtodo padre que se
llama igual. y luego asigna la variable @days_since_zombies = 0
La idea de la herencia es evitar reescribir cdigo, o sea haber hecho esto sera errneo:
1
2
3
4
5
6
Ya que Zombie ya incluye el mtodo initialize de Persona, reescribirlo para llamarlo sera intil.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Persona
attr_reader :x, :y
def initialize(x, y)
@x = x
@y = y
end
private
def no_tocar
puts "heeey !!!"
end
end
class Zombie < Persona
attr_reader :days_since_zombie
def initialize(x,y)
super(x,y)
no_tocar # se llama al mtodo e imprime heeey
@days_since_zombie = 0
end
end
z = Zombie.new(2,4)
Preguntas
Qu keywords generan la diferencia entre un receptor implcito y uno explcito
Cul es la diferencia entre private y protected?
Qu devuelve self fuera de cualquier objeto?
Qu devuelve self a nivel de una clase?
Qu devuelve self dentro de un mtodo de instancia de una clase?
Qu devuelve self dentro de un mtodo de clase?
Qu devuelve siempre el constructor independiente del return?
Para qu sirven los mtodos protegidos?
Qu sucede si una clase hereda de otra pero redefine un mtodo, cual manda?
Qu hace la instruccin super?
Mdulos vs clases
Por ejemplo supongamos que estamos construyendo un software matemtico donde hay diversas frmulas, si
bien podramos guardar las frmulas en una clase e implementar los mtodos como mtodos de clases, tambin
lo podramos hacer dentro de un mdulo.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Formula
@@PI = 3.1415
def self.PI
@@PI
end
def self.diameter(r)
2*r
end
def self.perimeter(distance)
diameter(distance) * PI
end
end
1
2
3
4
5
6
module Formula
PI = 3.1415
def diameter(r)
2*r
end
7
8
9
10
11
def perimeter(distance)
diameter(distance) * PI
end
end
Pero ahora para utilizar la constante PI fuera del mdulo deberemos hacer:
Formula::PI
Formula.PI
En la utilizacin son tcnicamente iguales, pero la definicin es ms limpia en el caso de los mdulos, adems
tienen distintos usos, y eso es lo que veremos a continuacin.
1
2
require_relative 'formula'
Formula::PI
Debemos tener cuidado de especificar la ruta correcta al archivo, la extensin .rb no es necesaria, pero no afecta
si se pone.
Includes
Hasta ahora los mdulos como los hemos estudiado no presentan mayor utilidad, pero es porque todava nos
faltan estudiar los mixins.
Tenemos un mdulo Formula, ahora vamos a construir la clase Crculo en el archivo circulo.rb en la misma
carpeta.
1
2
3
4
5
6
7
require_relative 'formula.rb'
class Circulo
include Formula
end
puts Circulo::PI
Y aqu podemos ver que los mdulos funcionan similar a la herencia pero con la ventaja de que es posible incluir
diversos simultneamente, todo los mtodos del mdulo sern incluidos como mtodos de instancia de la clase
que los incluya.
veamos el siguiente ejemplo:
1
2
c = Circulo.new
puts c.diameter(5)
o sea nuestro objeto ahora tiene un mtodo de instancia que fue importado del mdulo Formula a travs de
includes.
Cabe destacar que Ruby no tiene multiherencia pero a travs de los includes podemos implementar muchas
funcionalidades de otras clases, un ejemplo clsico de esto son los arrays de ruby que implementan el mdulo
enum.
Extends
Adems de los includes existen los extends, son muy similares pero incluyen los mtodos del mdulo como
mtodos de clase.
1
2
3
4
5
require_relative 'formula.rb'
class Circulo
extend Formula
end
1
2
3
4
5
6
7
require_relative 'formula.rb'
class Circulo
extend Formula
end
Circulo.diameter(5)
Herencia + includes
A modo de repaso vamos a crear un pequeo ejemplo donde ocupemos simultneamente herencia e includes,
para eso vamos a ocupar el mdulo de formulas, crearemos una clase punto y una clase crculo que hereda de
punto, este punto sera el punto central del crculo.
1
2
3
4
5
6
7
module Formula
PI = 3.1415
1
2
3
4
5
6
7
class Punto
attr_accessor :x, :y
def initialize(x,y)
@x = x
@y = y
end
end
1
2
3
4
5
6
7
8
def diameter(r)
return 2*r
end
end
9
10
11
12
13
1
2
def diameter()
super(@r)
end
end
c = Circulo.new(2,3,4)
puts c.diameter
Lo interesante que hicimos fue reescribir el mtodo diameter, y llamamos a super utilizando nuestro radio, en
este caso ese mtodo no corresponde al punto, si no que corresponde a la frmula.
1
2
3
4
5
module Foo
class Bar
@@a = 10
end
end
1
2
3
4
5
6
7
8
9
10
11
module Foo
class Bar
@@a = 10
def self.get_a
return @@a
end
end
end
puts Foo::Bar.get_a
1
2
3
4
5
6
7
8
9
10
11
12
13
module Formula
module Matematicas
PI = 3.1415
def diameter(r)
return 2*r
end
def perimeter(distance)
return diameter(distance) * PI
end
end
end
puts Formula::Matematicas::PI
Desafio
Crear un modulo que tenga mtodos que permitan leer un archivo csv, y guardar el archivo csv, dado un arreglo
de objetos. Si el tamao del arreglo no cumple con el layout de nuestro archivo, lanzar una excepcin.
Preguntas
Cules son las diferencia entre un mdulo y una clase?
Cul las diferencias entre herencia e incluir un mdulo?
Cul es la diferencia entre require_relative e include?
Cul es la diferencia entre include y extends?
Cul es la diferencia entre Formula::PI y Formula.PI?
19) Testing
Testing es una tcnica que consiste en crear pruebas para verificar que nuestro cdigo funciona o mejor dicho
que no falla
La clave para entender testing es que no se trata de validacin, se trata de invalidacin, o sea en hacer pequeos
scripts que intenten probar que nuestro cdigo va a fallar.
Un buen test detecta fallas, uno malo no aporta informacin.
Ruby al igual que muchos otros lenguajes trae herramientas incorporadas para crear tests automatizados que
corran bajo una sola lnea de comando, y de esta forma podemos revisar cualquier set de cambios corriendo esta
lnea.
Realizar los test es sencillo, lo difcil es saber que es lo que hay que testear, esto lo vamos a ir aprendiendo con la
prctica
Mi primer test
Supongamos que queremos calcular la hipotenusa de un tringulo, esta es una frmula muy sencilla, la
hipotenusa al cuadrado es igual a la suma de los catetos al cuadrado:
1
2
3
1
2
3
require hipotenusa
hipotenusa(3,4)
end
Estructura de un test
1
2
3
4
5
6
7
8
9
10
require 'test/unit'
require_relative '../hipotenusa'
class TestHipotenusa < Test::Unit::TestCase #Hereda de la clase test
def test_resultado_pitagorico # Cada mtodo es un test
# cada mtodo debe incluir un assert
assert_equal 5, hipotenusa(3,4), "Suma Positivos Funciona"
end
end
Un test case contiene varios tests, cada uno de estos vienen con un assert, que es la prueba, pero cada test tiene
un slo assert, (por ahora) es por eso que a este tipo de testing se le llama Unit testing estamos probando el
cdigo a travs de unidades mnimas funcionales.
Hay diversos tipos de assert para distintos Test, por ejemplo nos podra interesar slo que el resultado fuera
mayor que, menor que, distinto que, o si un arreglo contiene un elemento.
Normalmente todos los tests se guardan dentro de una carpeta llamada tests, es por eso mismo que el
require_relative hace un '../hipotenusa' , es porque est cargando el archivo hipotenusa de la carpeta
padre.
Ahora si el archivo de test est dentro de la carpeta de la test, y nosotros estamos ubicado en la carpeta del
proyecto entonces podemos correr los tests con:
ruby test/test_hipotenusa.rb
1
2
3
4
5
Hagamos ahora que nuestro test falle, para eso vamos a comentar la lnea de cdigo que devuelve la hipotenusa
1
2
3
1
2
3
4
5
6
7
8
9
10
Failure: test_result(TestHipotenusa)
test/test_hipotenusa.rb:6:in `test_result'
3:
4: class TestHipotenusa < Test::Unit::TestCase
5:
def test_result
=> 6:
assert_equal 5, hipotenusa(3,4)
7:
end
8: end
<5> expected but was
<nil>
Qu testear?
La prctica hace al maestro, pero lo que no hay que perder de vista es que hay que tratar de que nuestro cdigo
falle en lugar de comprobar que funcione, hay algunos tips para eso:
1. Revisar las condiciones, o sea si hay un if o while probar ambas condiciones
2. Revisar los bordes, por ejemplo si hay un <= o un <, == o > probar como entrada con un nmero menor al
borde, el borde, y luego un nmero posterior.
3. Probar parmetros invlidos, por ejemplo en la hipotenusa si le pasamos nmeros negativos como
parmetros al elevarlos al cuadrado nos dar una respuesta positiva y real, pero, existe un tringulo de lados
negativos?, que matemticamente se pueda resolver no necesariamente quiere decir que tenga sentido.
4. Debe ser un nmero?, que pasara si le pasamos un objeto o un string?
5. Debe funcionar con nmeros muy grandes?, probar con esos nmeros
6. Es una palabra?, qu pasa si la palabra es muy larga?, qu pasa si la palabra no existe o si el string viene
vaco?, qu pasa si el string es un nmero?,
7. Es un arreglo?, revisar si no altera el orden, (quizs no importa, quizs si, revisar si al agregar un nmero
este se incluye. Revisar que los valores que no deban ser cambiados no sean cambiados.
TDD
TDD es una filosofa de testing que consiste en realizar los tests antes del cdigo. Cmo funciona?, construimos
un par de casos de prueba, por ejemplo no tenemos el cdigo de la hipotenusa, pero sabemos que 3 y 4 da como
resultado 5, entonces tenemos un caso de uso que funciona, adems buscamos un caso de uso que no funciona,
y luego escribimos el cdigo y vamos corriendo los tests hasta que obtener xito.
TDD es una filosofa que se puede trabajar en conjunto con Unit Testing
BDD
BDD esa una filosofa con la que no vamos a trabajar en este curso, pero permite testear requerimientos (en
lugar de cdigo), entonces BDD se encarga de que el cdigo realizado resuelva el problema que dice resolver, en
cambio TDD slo se encarga de que el cdigo funcione. BDD es ms completo pero agrega una carga de trabajo
extra que no siempre se transforma en valor, en cambio TDD nos da la seguridad de que el cdigo realizado
funcione bien y que los nuevos cambios hechos a nuestro proyecto no rompan funcionalidades existentes.
Ejercicios prcticos
Ejercicio Suma
Ya que revisamos la parte terica vamos a resolver diversos casos prcticos para ejercitar.
Partamos con uno bsico, una suma, pero hagamos el test primero:
/test/test_suma.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
require 'test/unit'
require_relative '../suma'
class TestSuma < Test::Unit::TestCase
def test_suma
assert_equal 5, suma(3,2)
end
def test_suma_negativos
assert_equal -5, suma(-3,-2)
end
end
El error se debe a que todava no hemos implementado el mtodo suma, entonces claramente un error que dice
undefined method suma for #<TestSuma:0x007f8c6212b038>
Ahora creamos en el archivo suma.rb en la carpeta padre.
1
2
3
1
2
3
4
5
Ahora implementa un test para revisar si la suma de cero con cero da como resultado cero.
Ejercicio Arreglo
Implementa una funcin para contar los elementos de un arreglo (no puedes ocupar count), crea tests para
cuando el arreglo tenga cero elementos, y para cuando tenga 5 elementos.
contar(a)
Ejercicio Arreglo 2
Implementa una funcin para contar los elementos que sean iguales a x, donde x es una parmetro de la funcin,
ejemplo:
1
2
3
contar(a, 2) # contara
# todas las apariciones del elemento
# 2 dentro del arreglo a
Ejercicio Arreglo 3
Combina lo aprendido en el ejercicio arreglo, y arreglo 2 para que sea una nica funcin que se pueda ocupar de
ambas formas, o sea con un slo parmetro, o con dos.
1
2
z.no_tocar
# => NoMethodError: private method `no_tocar' called for #<Zombie:0x007fb8b482ad