Tracks
/
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 conste de varios tipos. Esto se conoce como tipo unión. En Crystal, es común que el compilador infiera un tipo unión.

Note

Un tipo unión sigue siendo un solo tipo en tiempo de ejecución, aunque conste de varios tipos. Esto significa que si un tipo unión está formado por String e Int32, no será ambos a la vez. En su lugar, será un String o 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 común es (T | Nil), donde T es un tipo, y puede aparecer en métodos que pueden devolver Nil. Esto también se conoce como que una variable sea nilable.

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

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

No se limita a solo 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 maneras de obtener el tipo de una variable. Ya sea usando typeof o usando 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, por ejemplo, si una variable es un tipo unión, Object#class no podrá decirte eso, ya que solo devolverá el tipo en tiempo de ejecución, que es un solo 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 solo 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 estructurado de tal manera que solo pueda ser uno de los tipos cuando quieras usar las operaciones específicas de ese tipo.

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

Crystal sí tiene un método particular para los tipos unión: el método is_a?, que toma un tipo como argumento y devuelve un Boolean. El método nil? es un atajo para 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, por lo tanto, garantizará que sea 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 tener un solo tipo como argumento, sino que también puede tener 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 ser una variable de instancia ni una variable de clase. En su lugar, estas deben asignarse primero a una variable local.

as

Una forma de convertir un tipo unión en un solo tipo es lograr que solo se pueda entrar a una rama si el tipo es un tipo específico. Otro enfoque es usar el método as. Esto convertirá un tipo unión en un solo tipo haciendo 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 está pensado solo para cuando estás seguro de que el tipo es el esperado o si quieres lanzar una excepción cuando no lo sea.

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

as?

as? funciona de manera 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 del tipo esperado y Nil.

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

foo.as?(Int32) # => nil

Atajo para nilable

Nilable significa que una variable puede ser un tipo o Nil. Esto se puede escribir como (T | Nil). Pero como los tipos nilable son relativamente comunes, existe un atajo: 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, con un sistema de cámara totalmente nuevo, un diseño nuevo y un procesador revolucionario que funciona con un sistema operativo nuevo llamado smartyOS.

El nuevo procesador tiene la capacidad de manejar contraseñas más seguras que nunca, y la empresa ha decidido aprovecharlo. Te han pedido que implementes un sistema de bloqueo por contraseña para el teléfono que permita al usuario establecer una contraseña y luego 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 se debe usar en ninguna aplicación del mundo real.

1. Establece una contraseña

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

Estos diferentes 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 digital usa un Float64.

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

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

2. Cifra 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 forma única 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 recibe argumentos y modifica 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. Comprueba 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 recibe la contraseña que se va a comprobar como argumento. 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 una pestaña nuevas
Crystal Exercism

¿Todo 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.