Notas Sobre Ruby Básico
Tipos de datos en ruby
Number(float,integer) String Boolean(true,false,nil) Symbol
Antes de empezar , un literal es una notación que nos permite representar un valor fijo en código.
Operaciones básicas con tipos de datos numéricos
- Suma,resta,multiplicación : como lo habitual.
- División : El resultado será la división entera , si queremos que nos de un float alguno de los operandos tiene que ser float.
- Módulo : El operador módulo %, no es exactamente el resto de la división. Si queremos saber el resto de la división lo seguro es usar remainder().Esto es así porque % tiene un manejo distinto cuando los operandos no son ambos positivos.Ahora bien , si ambos son positivos entonces % y remainder son equivalentes.
Casteo de tipos
Ruby tiene métodos para castear , por ejemplo :
my_variable1 = 102.45
my_variable1.to_i # Castea a Integer , ojo que trunca los decimales sin redondear
my_variable2 = 25
my_variable.to_s # Castea a String
Strings
- Se puede concatenarlos con “+”.
- Se pueden escribir entre “ “ o ‘ ‘ . Pero si queremos hacer string interpolation debe ser con comillas dobles.
String interpolation
name = "Ricky"
puts "My name is #{name}"
Substrings
Podemos obtener substrings usando slicing
name = "Ricky"
puts name[0..3] # El límite derecho se incluye.
puts name[0,3] # El límite derecho no se incluye.
# [] es en realidad un alias para slice()
Despueś además tenemos métodos para muchas cosas como cambiar a mayúsculas,ver si incluye cierto caracter,ordenar , invertir,etc.
Symbols
Los symbols son una especie particular de string. Mientras que un string literal tendrá una espacio de memoria usado para cada vez que se lo cree independientemente de si ya hay otra variable con el mismo literal, el symbol en cambio siempre está guardado en la misma posición de memoria.
“Algo como un stirng , pero que no necesitamos imprimirlo , ni modificarlo.”
El uso del symbol es para cuando no necesitamos modificar el string, por ejemplo en las keys de los hash
# Ejemplos de symbols
:iamsymbol
:"hello world"
Ejemplo de lo explicado
name1 = "Ricky"
name2 = "Ricky"
# Hay dos variables que tienen el mismo valor , pero eso no significa que ambas resusan ese valor guardado en memoria , sino que cada una creo su propia copia.
# Si fuera que comparten el valor , entonces la identidad de los objetos sería la misma
name1.object_id == name2.object_id # => false
name1.equal?(name2) # => false
Valores de retorno en ruby
En ruby todo es un objeto y toda expresión siempre retorna algo. Si no tiene que retornar nada , entonces retorna nil
Variables boolean
true,false,nil(significa nada)
false == nil # => false
# si bien para comparar nil se toma como false , en realidad no son lo mismo.
Chequear igualdad
== Chequea la igualdad entre valores de objetos .equal? Chequea si la identidad de dos objetos es la misma.
Estructuras de datos básicas
-
Arrays : Colección ordenada . A diferencia de otros lenguajes , puede contener elementos de distinto tipo en el mismo. Se accede por índices.Tiene los métodos típicos (a investigar cuando sea necesario , no me los acuerdo todos de memoria.)
countries = ["Arg","Bra","Fra"] puts countries[1] # show Bra ages_and_names = ["Lucas",120,"Juan",45] # Esto en otros lenguajes no va
-
Hashes : Colección no ordenada . Estructura de key,value. Las keys suelen ser tipo symbol.
cats = {:marte => 3, :jupiter => 2, :sol => 1} puts cats[:sol] # 1
Expressions and returns
Una expresión es cualquier cosa que puede ser evaluada. Toda expresión en ruby siempre devuelve algo
Objetos.
Envíamos mensajes a los objetos mediantes operadores (. (dot) + - * ,etc).
Variables
Podemos obtener input del usuario usando gets
name = gets.chomp # chomp elimina el \n que se agrega
puts name
Tipos y scope de las variables
Ruby tiene distintos niveles de visibilidad para las variables según su tipo , y un concepto interesante es el de bloque
- Variables de clase :
@@name_class_variable
Solo son visibles por los objetos que son instancia de la clase donde se definió la variable y dentro de la misma clase en sí.
Tienen que inicializarse o de lo contrario dará error al tratar de usarla.
Se deben inicializar fuera de la definición de métodos.
Si una instancia la modifica , entonces todas las demás verán ese cambio.
- Variables de instancia:
@name_instance_variable
Son visibles por los métodos de los objetos donde está creada.
No es necesario inicializarse, por default se inicializa con nil.
- Variables locales
name_local_variable
Sólo visibles dentro del bloque donde se las define. No se necesita inicializarlas.
- Variables globales
$name_global_variable
Es el tipo de variable que más visibilidad tiene.Es visible en cualquier parte del programa.
Por default, si no se las inicializa , tienen el valor nil.
No es recomendable su uso , ya que vuelve el código difícil de mantener . Al poder ser accedidas desde cualquier lado, si son modificadas se hace hard encontrar desde donde vino el cambio.
- Constantes
NAME_CONSTANT_VARIABLE # Tiene que empezar con mayusculas.
Dependiendo de si se definen adentro o afuera de una clase o módulo , su alcance puede ser global o solo dentro de esa estructura en cuestión.
No se pueden declarar dentro de definiciones de métodos.
Si no se inicializa , al tratar de accederla obtendremos un error.
Si tratamos de modificarla , ruby nos dará un warning
¿Qué son? y formas de encontrar bloques
Un bloque en ruby se crea cuando cuando se hace la definición de un método.Es decir , todo lo que está en su cuerpo forma parte de un bloque.
La otra manera en que se puede encontrar un bloque es cuando nos topamos con { } o do/end inmediatamente después de la llamada a un método.
Las variables que se definen dentro de un bloque solo tienen visibilidad dentro de él.
Variables como referencias o punteros.
En realidad las variables no son más que referencias o punteros a ciertas posiciones de memoria. El valor está guardado en cierta posición de memoria y la variable es una etiqueta que guarda el valor de esa dirección de memoria. Teniendo en cuenta esto , se debe tener cuidado de no modificar indirectamente cosas cuando hacemos operaciones que modifiquen el espacio de memoria, como los métodos bang
Ejemplo :
a = "juan" # a apunta al espacio de memoria que guarda el valor 10
b = a # b apunta hacia donde apunta a.
puts "a = #{a} ; b = #{b}" # a = 10 , b = 10
¿Qué pasa si ahora modificamos el espacio de memoria apuntado por a?
a = "juan" # a apunta al espacio de memoria que guarda el valor 10
b = a # b apunta hacia donde apunta a.
b.upcase! # método bang que modifica el espacio de memoria apuntado por b
puts "a = #{a} ; b = #{b}" # a = 10 , b = ?
# Se modificó indirectamente a través del puntero b , lo que había guardado en el lugar que apuntaba a.
Ahora si en cambio usamos el método upcase no bang , no modifica el espacio de memoria , por lo que b no modificará indirectamente lo que apunta “a”.
a = "juan" # a apunta al espacio de memoria que guarda el valor 10
b = a # b apunta hacia donde apunta a.
b = b.upcase # método que NO modifica el espacio de memoria apuntado por b
puts "a = #{a} ; b = #{b}" # a = 10 , b = ?
- Tip
Para ver la identidad de dos objectos y saber si están apuntando al mismo espacio de memoria podemos usar
.object_id
User input and output
Input
Usamos get
combinado con chomp
para sacar el \n
puts "give me the name: "
name = gets.chomp
puts name
Tip : si queremos escribir varios comandos en la misma línea en irb usamos “;”
Output
print : Muestra por pantalla sin agregar “\n” .Muestra array en una sola fila.Siempre retorna nil
puts : Muestra por pantalla el dato y agrega un “\n”.Muestra array en varias filas. Siempre retorna nil.
puts siempre trata de convertir a string lo que se le pasa como parámetro, de manera que si le pasamos un array que tiene algún elemento nil , lo imprimirá como un espacio en blanco.
Ejemplo
a = [10,nil,20,nil,40] puts a # 10 # # 20 # # 40
p : Muestra por pantalla de manera más raw, con caracteres de control.Retorna el objeto que se le pasó como parámetro.
pp (pretty print) : Muestra de manera más “linda” hash y arrays.
Conditional logic
- Los únicos valores que son evaluados como falso , son nil y false . Todo lo que no sea ellos se considera verdadero . Notar que en C++ por ejemplo 0 se toma como false , pero en ruby es true.
- Shortcircuit : Siempre se evalua de izquierda a derecha en las condiciones lógicas. Si el valor de verdad de lado derecho ya permite definir el resultado global de la condición , entonces lo de la izquierda no se evalua.
Operadores lógicos
-
Cualquier operador lógico ( , && , !) aplicado a valores booleanos , dará un valor booleano , es decir , true o false. - Pero si aplicamos operadores lógicos a valores no booleanos , puede que el resultado no sea un booleano.
if
if a > 2
puts "mayor a 2"
else
puts "menor a 2"
end
Si en el cuerpo del if hay una sola expresión a ejecutar , entonces se puede escribir en una línea
puts "hola" if a > 2
if- elif - else
if a > 2
puts "mayor a 2"
elsif a > 3
puts "mayor a 3"
elsif a > 5
puts "mayor a 5"
else
puts "menor a 2"
end
# es un ejemplo inútil y estúpido
El else es opcional tanto en el if , como en el if - elif.
Ternary operator
a = 10
puts a > 2 ? "mayor a 2" : "menor a 2"
unless
El bloque de código se ejecuta cuando la condición es falsa.
a = 120
unless a > 100
puts "something"
end
case
Cuando encuentra la opción que matchea, inmediatamente retorna el valor del case y termina la ejecución del case statement.
option = "red"
option_selected = case option
when "red"
puts "rojo"
"se eligio red"
when "blue"
puts "blue"
"se eligio blue"
else
puts "otro color"
"se eligio otro color"
end
puts option_selected
# Output del código :
# rojo
# se eligio red
Operador ovni <=>
Compara si lo que está al lado izquierdo es menor , igual o mayor que lo de la derecha y devuelve -1 , 0 o 1 según sea el caso
puts 4 <=> 9
puts 4 <=> 4
puts 4 <=> 1
Operadores booleanos sobre valores no booleanos
a = "hola"
b = nil
c = 0
d = false
e = "chau"
f = 120
puts a || 0 # true
puts b && f # false
puts !!!(d && a) # true
Loops
while
n = 0
while n < 4
puts "hola"
n += 1
end
until
n = 1
until n > 4
puts "hola"
n += 1
end
times
n = 4
n.times do
puts "hola"
end
loop
n = 1
loop do
puts "hola"
if(n > 3)
break
end
n += 1
end
for
# Para los rangos tener en cuenta que :
# (a..b) incluye el extremo derecho
# (a...b) no incluye el extremo derecho
for n in (0..4)
puts "hola"
end
each (iterador)
names = ["john","mike","louis"]
names.each do
|name|
puts "#{name}"
end
upto
Itera hasta cierto valor de contador es alcanzado sumando
# Hace |final_counter - initial_counter| +1 iteraciones
initial_counter = 5
final_counter = 10
initial_counter.upto(final_counter) {puts "hola"}
downto
Itera hasta cierto valor de contador es alcanzado decrementando
# Hace |final_counter - initial_counter| +1 iteraciones
initial_counter = 7
final_counter = 3
initial_counter.downto(final_counter) {puts "hola"}
Arrays
Crear un array
Podemos hacerlo con Array.new(size,default_value)
a = Array.new(4,1)
p a # [1,1,1,1]
Acceder a un elemento de un array
- Si tratamos de acceder a un índice que no existe , retorna
nil
a = [10,6,6] p a[1] # 6 p a[9] # nil
Iterar un array
Podemos hacerlo tanto como each como con each_with_index
a.each {|element| puts element}
a.each_with_index {|element,index| puts "a[#{index}] = #{element}"}
Agregar elemento a un array
Podemos hacerlo con push o « , ambos luego de agregar el elemento (al final) , retornan el array actualizado.
a << 10 # [1,1,1,1,10]
a.push(9) # [1,1,1,1,10,9]
a.push(100,200) # [1,1,1,1,10,9,100,200]
Eliminar un elemento de un array
Podemos hacerlo con :
my_array.delete(value)
borra todas las coincidencias y devuelve el valor borrado .my_array.delete_at(index)
borra el elemento que está en la posiciónindex
. También devuelve el elemento eliminado.my_array.pop
borra el último elemento del arrray y lo devuelve.
a = [1,1,1,1,10,9,4]
a.delete(1) # => 1
p a # [10,9,4]
a.delete_at(1) # => 9
p a # [10,4]
a.pop
p a # [10]
Algunos métodos para arrays
Obtener intersección de dos arrays
-
first_array.intersection(second_array)
Devuelve un array con los elementos comunes entre
first_array
ysecond_array
a = [19,51,22,19,8,14,6,8] b = [8,4,2,19] a.intersection(b) # [19,8]
Concatenar (sumar) dos arrays
La concatenación da como resultado otro array con los elementos de ambos
a = [10,5,2]
b = [1,4,2]
a + b # [10,5,2,1,4,2]
Restar dos arrays
` a - b tiene como resultado los elementos del array
a, sacándole los que estén en el array
b`
a = [19,51,22,19,8,14,6,8]
b = [8,4,2,19]
a - b # [51,22,14,6]
Otros métodos importantes
my_array.empty?
my_array.include?(value)
my_array.join(separator)
# no sepador es lo mismo que sin espaciosmy_array.reverse
Hash
- Si se trata de acceder a una key que no existe retorna nil
- Para saber que la key no existia mediante un error podemos usar fetch
- Para eliminar un un elemento del hash le pasamos la key al método
delete
- Con los métodos
key
yvalue
obtenemos respectivos arrays con las keys y values del hash.
Crear hash
# Hash vacío
my_hash = {}
# Como hash literal
my_hash = {name1:"john",name2:"louis"}
# Usando la clase Hash
my_hash = Hash.new # Hash vacío .{}
Notación con symbol para hash
Se usan generalmente symbols para las key en los hash , por lo ya visto respecto a como maneja ruby la memoria respecto a crear copias de un mismo string cada vez que una variable lo define. Entonces en los hash hay una notación que combina el symbol y la => .
my_hash = {:name1 => "john",:name2 => "louis"}
my_hash_pretty = {name1:"john",name2:"louis"}
Manejando key y values
Con .keys
podemos obtener un array con todas las keys del hash.
Con .values
podemos obtener un array con todos los values del hash
my_hash = {:name1 => "john",:name2 => "louis"}
my_hash.keys # [:name1,name2]
my_hash.values # ["john","louis"]
- ¿Qué pasa si trato de acceder a una key que no existe?
my_hash = {:name1 => "john",:name2 => "louis"} my_hash[:name3] # nil
Si la key no existe , retorna nil.
Pero si justamente existe en el hash algún value con nil
, entonces podría existir una ambiguedad entre sí el nil
devuelto es por no haber encontrado la key o porque si existía y su value correspondiente era nil
.
Para evitar esto se puede usar fetch(key,value_to_return_if_no_key)
o si queremos obtener el KeyError
solo hacemos fetch(key
)
my_hash = {:name1 => "john",:name2 => "louis"}
my_hash.fetch(:name3,"key not found") # "key not found"
my_hash.fetch(:name34,"blablabla") # "blablabla"
my_hash.fetch(:name3) # KeyError
- Otra opción para personalizar el value que se retorna al intentar acceder a una key inexistente.
my_hash = Hash.new("no existe") my_hash[:name1] = "john" my_hash[:name2] = "louis" # En este momento el hash quedo así : {:name1 => "john",:name2 => "louis"} # Ahora si tratamos de acceder a una key que no existe. my_hash[:alberto] # key no existe,retorna el value por default. # => "no existe"
Agregar y borrar elementos del hash
Podemos agregar un par key,value simplemente asignando a la nueva key un valor.Al hacerlo se retorna el value asignado a esa nueva key.
my_hash = {name1:"john",name2:"louis"}
my_hash[:name3] = "joey"
# Ahora quedó como { name1:"john",name2:"louis",name3:"joey"}
Podemos eliminar un elemento con delete(key)
y al hacerlo retorna el value que estaba asociado a la key borrada.
my_hash = { name1:"john",name2:"louis",name3:"joey"}
my_hash.delete(:name1) # "john"
¿Cómo iterar un hash?
Con el iterador each por ejemplo
my_hash = { name1:"john",name2:"louis",name3:"joey"}
my_hash.each do
|key,value|
puts "#{key}:#{value}"
end
Métodos útiles para hash
.key?
Averiguar si una key determinada está en el hash
my_hash = { name1:"john",name2:"louis",name3:"joey"}
my_hash.key?("pepe") # false
my_hash.key?(:name1) # true
.select { #something }
Aplica el hash el bloque pasado como parámetro y devuelve un hash con los pares key,value del hash original que cumplen la condición del bloque.Si ninguno lo cumple devuelve un hash vacío.
my_hash = { name1:"john",name2:"louis",name3:"joey"}
my_hash.select { |k,v| v.length > 4 && k[-1] == "2"}
to_a
Convierte el hash a un representación en array , donde por cada par key,value hay un array.Y dentro de cada uno de esos array sus primer elemento es la key y el segundo el value.Devuelve esa representación y no modifica el hash original.
my_hash = { name1:"john",name2:"louis",name3:"joey"}
my_hash.to_a # => [[:name1, "john"], [:name2, "louis"], [:name3, "joey"]]
first_hash.merge(second_hash)
Hace un merge entre first_hash
y second_hash
y lo devuelve . Si en second_hash
hay algún elemento que tiene una key que ya está en first_hash
, entonces pisa su value con el de second_hash
first_hash = {name1:"alex",name2:"ari",name3:"shubs"}
second_hash = {name4:"phix",name5:"nick",name2:"rick"}
first_hash.merge(second_hash) # {name1:"alex",name2:"rick",name3:"shubs",name4:"phix",name5:"nick"}
Métodos
Terminología sobre métodos (obvio igual)
Parámetros se usa para referirse a lo que él método recibe, en términos de la definición del método.
Argumentos se refiere a los valores que se pasan en la llamada a un método.
Parámetros por default
Podemos definir parámetros que estén por default y sean usados cuando se llama sin argumentos al método.
def saludo(nombre = "")
if(nombre.empty?)
puts "Hola desconocido"
else
puts "Hola #{nombre}!"
end
end
Predicate and bang methods
Predicate methods son aquellos que terminan con ?
. La idea es indicar que son métodos que devuelven un valor booleano.
empty?
es un ejemplo
Bang methods son aquellos que modifican al objeto y se indican terminando con !
Por ejemplo capitalize!
aplica el cambio al objeto permanentemente , en cambio capitalize
devuelve un copia con la modificación del objeto y no afecta al original.
Return explícito e implícito
En ruby siempre un método devuelve el valor de la última expresión que se haya evaluado (siguiendo el orden de ejecución del bloque del método), a menos que se ponga la instrucción explícita return
.
Métodos con argumentos que son métodos
A los métodos también les podemos pasar llamadas a otros métodos como argumentos.
def suma(a,b)
a+b
end
def promedio_sumas(suma1,suma2)
(suma1 + suma2)/2
end
puts promedio_sumas suma(1,2),suma(3,4)
Top level scope
Hay métodos que son standalone y pareciera que no dependen de ser aplicados sobre ciertos objetos.Bueno en realidad, esos métodos si le pertenecen , pero al objeto anónimo que se crea cuando comienza a ejecutarse un código ruby. Ese método genera un top level scope.
Un ejemplo de este tipo de métodos es is_a
que sirve para indicar si un objeto de cierta clase o no.
"sddsfs".is_a?(String) # true
Módulo Enumerable y algunos métodos interesantes
Enumerable es un módulo en el que hay métodos importantes
each
Es un método iterador, nos permite recorrer una colección y ejecutar el bloque que se le pasa como argumento, tantas veces como elementos tenga la colección.Devuelve siempre la colección original.
people_plus_age = {john:51,louis:99,harry:19}
people_plus_age.each { |p,a| puts "Name:#{p},age:#{a}"}
# => {john:51,louis:99,harry:19}
each_with_index
Funciona de manera similar que each
, pero hace disponible en el bloque una variable más que guarda el índice de cada elemento de la colección.También devuelve la colección original.
people_plus_age = {john:51,louis:99,harry:19}
people_plus_age.each_with_index { |(p,a),i| puts "Index:#{i} - Name:#{p} - age:#{a}"}
# => {john:51,louis:99,harry:19} (sale con la notación => en realidad)
map (mapea , transforma a otra cosa)
Aplica el bloque que se le pasa como parámetro a cada elemento de la colección.Devuelve un array con los resultados de la transformación (si queremos que sea hash usar to_h aplicado al bloque)
people_plus_age = {john:51,louis:99,harry:19}
people_plus_age.map { |p,a| [p.capitalize,a]}.to_h
# => {John:51,Louis:99,Harry:19} (sale con la notación => en realidad)
select
Testea si cada elemento de la colección satisface la condición impuesta en el bloque pasado como argumento. Devuelve la colección pero solo con aquellos elementos que pasaron la condición.
people_plus_age = {john:51,louis:99,harry:19}
people_plus_age.select { |p,a| a < 70 && p[0] != "j"}
# => {John:51,Louis:99,Harry:19} (sale con la notación => en realidad)
reduce
Transforma la colección en un único objeto.Se le pasa a un bloque y el resultado de ese bloque se almacena en la variable acumulador. Luego se continua la iteración con el próximo elemento y así sucesivamente.Si no se especifica un valor inicial para el acumulador , toma el primer valor de la colección.Devuelve el valor final del acumulador
# my_hash.reduce(valor_inicial_acumulador) { something}
people_plus_age = {john:51,louis:99,harry:19}
# Reduce para obtener la suma de edades.
people_plus_age.reduce(0){ |sum,(key,value)| sum + value}
Otro ejemplo :
Tenemos el array word_list
, y queremos obtener un hash donde sus key sean las palabras de
word_list
y sus values la cantidad de letras de cada palabra.
word_list = ["apple","banana","orange","potato","bean"]
word_list.reduce(Hash.new(0)) {|acum,word| acum[word] = word.length ; acum}
# => {"apple"=>5, "banana"=>6, "orange"=>6, "potato"=>6, "bean"=>4}
Métodos bang
La notación metodo!
indica que ese método modifica el objeto sobre el que se aplica
Varios métodos de Enumerable
tienen su versión bang, pero es mejor evitarlos por la obvia razón de perder la versión original del objeto.
Retornar el resultado de aplicar un método Enumerable sin usar variable
Para no perder el resultado de un método del módulo Enumerable lo podríamos guardar en un variable , por ejemplo :
odd_values = {item1:1,item2:2,item3:3}
a.select{|k,v| v % 2 != 0}
# a =
Pero podemos hacer algo más lindo , wrappeando el método en una función.
def odd_values(test_hash)
test_hash.select{|k,v| v % 2 != 0}
end
my_hash = {a:10,b:9,c:90,d:1}
puts odd_values(my_hash)
Predicate methods in Enumerable
any?
Devuelve un booleano indicando si al menos un elemento de la colección cumplió la condición impuesta en el bloque que se le pasa como argumento.
a = [11,45,6]
a.any?{|n| n > 50} # false
a.any?{|n| n > 20} # true
all?
Devuelve un valor booleano que nos indica si todos los elementos de la colección cumplen la condición en el bloque que se la pasa por argumento.Si la colección que sobre la que se aplica este método está vacía , devuelve true
por default.Tener en cuenta para evitar problemas.
a = [11,45,6]
a.all?{ |n| n%2 == 0} # false
a.all?{|n| n > 1} # true
include?
Devuelve un booleano indicando si la colección tiene el elemento que se le pasa como argumento true si está y false si no está.
a = [11,45,6]
a.include?(45) # true
h = {item1:1,item2:2,item3:3}
# En el caso de los hash el argumento es la key a saber si existe en el hash.
h.include?(:item2) # true
none?
Devuelve un booleano indicando si ningún elemento de la colección satifasce la condición que está definida en el bloque que se le pasa como argumento.True si ninguno cumplió , false si al menos uno la cumplió.
a = [11,45,6]
a.none?{ |n| n%2 == 0} # false
a.none?{|n| n > 100} # true
Nested collections
Podemos generar arrays de arrays , hash de hash y la combinación de ambos.
Nested arrays
También llamados matrices.
Crear un nested array inicializado.
Tiene que hacerse de esta manera y no con Array.new(size,default_value)
, porque default_value
tiene que ser un inmutable.Ya que de otra manera esos default value quedarían referidos a si mismos y luego al modificar uno se modificarían todos los demás. Ver acá la explicación
# Ejemplo matriz 3X4
cant_filas = 3
cant_columnas = 4
nested_array = Array.new(cant_filas) { Array.new(cant_columnas)}
p nested_array
Acceder a un elemento de un nested array
La forma de acceder a un elemento de ellos es mediante índices como sigue
m = [
[1,5,7],
[3,6,1],
[4,7,9]
]
# Accedemos el tercer elemento del segundo array.
puts m[1][3] # 1
- ¿Qué pasa si tratamos de acceder a un índice que no está en el array?
Si tratamos de acceder a un índice no existente de las filas (array externo) entonces tendremos un error del tipo
NoMethod [] for NilClass
. En cambio , si tratamos de acceder a un índice inexistente del array anidado (interno) en ese caso si nos devolveránil
Esto es un problema , pero la solución es : en vez de usar la notación my_array[fila][col]
usar el método dig(fila,col)
, que en caso de que fila o columna sean índices no válidos para alguno de los arrays retornará nil
p m[1,4] # Excepcion NoMethod
p m.dig(1,4) # nil
Agregar elementos a un nested array
Para hacerlo usamos los métodos para agregar elementos a un array en una dimensión ya vistos , solo que siendo cuidadosos respecto a la posición donde insertamos.
m = [
[1,5,7],
[3,6,1],
[4,7,9]
]
# Vamos a agregar un elemento a la segunda fila.
m[1].push(10)
p m # [[1, 5, 7], [3, 6, 1, 10], [4, 7, 9]]
Eliminar un elemento de un nested array
Mismos métodos que para eliminar un elemento de un array en una dimensión.
# Eliminamos el segundo elemento de la primera fila.
m[0].delete_at(1) # con delete(value) borramos por valor y no índice.
p m # [[1, 7], [3, 6, 1, 10], [4, 7, 9]]
Iterar sobre un nested array
Se usan los iteradores each
y each_with_index
prestando atención a que se tienen que usar uno por cada fila y por cada columna.
m = [
[1,5,7],
[3,6,1],
[4,7,9]
]
# vamos a iterar para mostrar fila x : col 1 : col 2 : col 3 , etc
m.each_with_index do
|fila,indice|
puts "\nfila #{indice} "
fila.each_with_index {|columna,indice| print "col #{indice}:#{columna} "
}
end
Nested hashes
Es anidar hashes , hash de hashes. Las formas de manipularlos se basan en los métodos ya vistos.
Crear nested hash
nested_hash = {item1:{a:1,b:2,c:3},item2:{d:4,e:5,f:6}}
Agregar elementos a un nested hash
nested_hash = {item1:{a:1,b:2},item2:{d:3,e:4}}
nested_hash[:item1][:new_item] = 99
p nested_hash # {:item1=>{:a=>1, :b=>2, :new_item=>99}, :item2=>{:d=>3, :e=>4}}
Eliminar elementos de un nested hash
nested_hash = {item1:{a:1,b:2,new_item:99},item2:{d:3,e:4}}
# Eliminamos la primer key del primer hash
nested_hash[:item1].delete(:a)
p nested_hash # {:item1=>{:b=>2,:new_item=>99}, :item2=>{:d=>3, :e=>4}}
Iterar un nested hash
nested_hash = {item1:{a:1,b:2,new_item:99},item2:{d:3,e:4}}
nested_hash.each do
|key,hash_value|
puts "#{key}"
hash_value.each {|key,value| puts "#{key}/#{value}"}
puts "-------"
end
Ejemplos de nested hashes y métodos de Enumerable
langs =
{
ruby: { facts: ['fact 0', 'fact 1'],
initial_release: 'December 25, 1996',
is_beautiful?: true },
javascript: { facts: ['fact 0', 'fact 1'],
initial_release: 'December 4, 1995',
is_beautiful?: false }
}
# Se nos pide que a partir de un lang_name y un fact_index obtengamos el fact correspondiente. Si lang_name no es una key de langs, retornar nil.
# Test
lang_name = :python
fact_index = 0
# Solución
resultado = langs.key?(lang_name) ? langs[lang_name][:facts][fact_index] : nil
# => nil
# Take langs and return a hash containing only lang which have the key/value pair { is_beautiful?: true } listed in their information
langs.select { |lang,info_lang| info_lang.include?(:is_beautiful?) && info_lang[:is_beautiful?] == true}
# => {:ruby=>
# {:facts=>["fact 0", "fact 1"],
# :initial_release=>"December 25, 1996",
# :is_beautiful?=>true}}