Rutas
/
Crystal
Crystal
/
Ejercicios
/
Cerradura con contraseña
Cerradura con contraseña

Cerradura con contraseña

Ejercicio de aprendizaje

Introducción

Crystal permite que una variable esté formada por varios tipos. Esto se conoce como tipo unión. En Crystal, es habitual que el compilador infiera un tipo unión.

Note

Un tipo unión sigue siendo un único tipo en tiempo de ejecución, aunque esté formado por varios tipos. Esto significa que si un tipo unión está formado por String e Int32, no será ambos a la vez. En cambio, será o bien un String o bien un Int32.

Un tipo unión se declara separando los tipos con una barra vertical (|). A menudo se colocan entre paréntesis, pero no es obligatorio. El tipo unión más habitual es (T | Nil), donde T es un tipo, y puede aparecer en métodos que pueden devolver Nil. También se dice que una variable es nilable.

foo : String = "Hello"
foo = nil # Error: type must be String, not (String | Nil)

foo : (String | Nil) = "Hello"
foo = nil

No se limita a dos tipos, sino que puede tener tantos como quieras.

foo : (String | Int32 | Nil | Float64) = "Hello"
foo = 1
foo = nil
foo = 1.0

typeof vs Object#class

Hay dos formas de obtener el tipo de una variable. Ya sea con typeof o con Object#class. La diferencia es que typeof devuelve el tipo de una variable en tiempo de compilación, mientras que Object#class devuelve el tipo en tiempo de ejecución. Esto significa que, si quieres ver si una variable es un tipo unión, por ejemplo, Object#class no podrá decírtelo, ya que solo devuelve el tipo en tiempo de ejecución, que es un único tipo.

foo = 0 == 0 ? "a" : 1
typeof(foo) # => (String | Int32)
foo.class # => String

Operaciones con tipos unión

Como un tipo unión es un único tipo en tiempo de ejecución, todas las operaciones estándar funcionan con él. Pero al compilar el código, el compilador necesitará saber de qué tipo se trata. Por lo tanto, el código debe estar configurado de tal forma que solo pueda ser uno de los tipos cuando se quieran usar operaciones específicas de ese tipo.

foo : (String | Int32) = "Hello"
foo.downcase # Error: undefined method 'downcase' for (String | Int32)

Crystal tiene un método concreto para los tipos unión: el método is_a?, que recibe un tipo como argumento y devuelve un valor booleano. El método nil? es una forma abreviada de is_a?(Nil). Poner el método is_a? en una expresión de control le dirá al compilador de qué tipo se trata y, con ello, garantizará que es ese tipo. Y para una rama else, se garantizará que no es ese tipo.

foo : (String | Int32) = "Hello"
if foo.is_a?(String)
  typeof(foo)  # => String
  foo.downcase # => "hello"
end

Este is_a? no se limita a recibir un único tipo como argumento, sino que también puede recibir un tipo unión. También se puede combinar con && para varios tipos.

Note

El método is_a?, cuando se usa junto con una expresión de control, no puede aplicarse a una variable de instancia ni a una variable de clase. En su lugar, primero hay que asignarlas a una variable local.

as

Una forma de convertir un tipo unión en un único tipo es hacer que solo se pueda entrar en una rama si el tipo es un tipo concreto. Otra forma es usar el método as. Esto convierte un tipo unión en un único tipo mediante una comprobación en tiempo de ejecución. Se lanzará una excepción si el tipo no es el esperado.

foo : String | Int32 = "Hello"
foo.as(String).downcase # => "hello"

foo.as(Int32) # Error: can't cast String to Int32
Caution

Este enfoque solo está pensado para cuando estés seguro de que el tipo es el esperado, o para cuando quieras lanzar una excepción si no lo es.

Usar este enfoque con una configuración incorrecta puede provocar un comportamiento inesperado.

as?

as? funciona de forma muy similar a as, pero devolverá nil en lugar de lanzar una excepción si el tipo no es el esperado. Esto significa que devolverá un tipo unión formado por el tipo esperado y Nil.

foo : (String | Int32) = "Hello"
foo.as?(String).downcase # => "hello"

foo.as?(Int32) # => nil

Abreviatura de nilable

Nilable significa que una variable puede ser un tipo o Nil. Esto se puede escribir como (T | Nil). Pero como los tipos nilables son relativamente habituales, existe una abreviatura: T?.

# This:
foo : (String | Nil) = "Hello"
foo = nil

# Is the same as:
foo : String? = "Hello"
foo = nil

Instrucciones

La empresa para la que trabajas está a punto de lanzar su nuevo teléfono inteligente, llamado Smarty 5: incluye un sistema de cámara totalmente nuevo, un diseño nuevo y un procesador revolucionario que ejecuta un nuevo sistema operativo llamado smartyOS.

El nuevo procesador tiene potencia suficiente para gestionar contraseñas más seguras que nunca, y la empresa ha decidido aprovecharlo. Te ha pedido que implementes un sistema de bloqueo por contraseña para el teléfono que permita al usuario establecer una contraseña y, después, comprobar si una contraseña dada es correcta.

Caution

El sistema de contraseñas que se practica en este ejercicio no es seguro y solo se usa con fines educativos. NO debería usarse en ninguna aplicación del mundo real.

1. Establecer una contraseña

El teléfono permite a los usuarios usar varios tipos distintos de contraseñas, desde una sencilla contraseña numérica hasta una contraseña alfanumérica más segura e incluso una contraseña que almacena su huella dactilar.

Estos distintos tipos de contraseñas usan tipos distintos: la contraseña numérica usa un Int32, la contraseña alfanumérica usa un String y la contraseña de huella dactilar usa un Float64.

Implementa la clase PasswordLock con un inicializador que reciba cualquiera de los tres tipos de contraseñas como argumento y lo almacene en una variable de instancia llamada @password.

password_lock = PasswordLock.new(1234)
# => #<PasswordLock:0x7f8e1b8c0b80 @password=1234>

2. Cifrar la contraseña

La empresa ha decidido cifrar la contraseña para que no se almacene en texto plano y te ha pedido que implementes un método para cifrarla.

Cada tipo de contraseña tiene su propia forma de cifrarse:

  • Contraseña Int32: la contraseña se divide entre dos y se redondea al entero más cercano.
  • Contraseña String: la contraseña se invierte.
  • Contraseña Float64: la contraseña se multiplica por cuatro.

Implementa un método de instancia llamado encrypt que no reciba argumentos y modifique la variable de instancia @password para que quede cifrada.

password_lock = PasswordLock.new(1234)
# => #<PasswordLock:0x7f8e1b8c0b80 @password=1234>

password_lock.encrypt
# => #<PasswordLock:0x7f8e1b8c0b80 @password=617>

3. Comprobar si una contraseña es correcta

La empresa también te ha pedido que implementes un método que compruebe si una contraseña dada es correcta.

Quieren que el método devuelva nil si la contraseña es incorrecta y "Unlocked" si la contraseña es correcta.

Implementa un método de instancia llamado unlock? que reciba como argumento la contraseña que hay que comprobar. El método debe devolver nil si la contraseña es incorrecta y "Unlocked" si la contraseña es correcta.

password_lock = PasswordLock.new(1234)
password_lock.encrypt
password_lock.unlock?(1234)
# => "Unlocked"
Editar en GitHub El enlace se abre en una ventana o pestaña nueva
Crystal Exercism

¿Listo para empezar Cerradura con contraseña?

Regístrate en Exercism para aprender y dominar Crystal con 26 conceptos142 ejercicios y mentoría humana real, todo gratis.