Skip to content

Commit

Permalink
Inclusión de la parte 4 - pattern-matching - tanto teoría como ejerci…
Browse files Browse the repository at this point in the history
…cios.
  • Loading branch information
david committed Jun 30, 2022
1 parent 99d24bd commit f504701
Show file tree
Hide file tree
Showing 6 changed files with 607 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package ejercicios.parte4Patrones

import lecciones.parte4Patrones.PatronesDondeNoLosEsperas


/**
* Ejercicio del bloque teórico de Patter Matching [[PatronesDondeNoLosEsperas]].
*
* Construir un método del tipo Expresión => String.
* El String es una representación de como una persona escribiría una operación de suma y multiplicación.
* Por ejemplo:
* - 3 por 5 más 2 => 3 * 5 + 2
* - 2 + 4 y después por 5 => (2 + 4) * 5
*
* Solución disponible en [[PatternMatchingSolucion]].
*/
object PatternMatchingEnunciado {

def main(args: Array[String]): Unit = {
val test_1 = Suma(Multiplicación(Número(2), Número(1)), Número(3))
//Debería resultar en 2 * 1 + 3
val test_2 = Suma(Multiplicación(Suma(Número(3), Número(2)), Número(4)), Multiplicación(Número(1), Número(2)))
// Debería resultar en (3 + 2) * 4 + 1 * 2
}

sealed trait Expresión

case class Número(n: Int) extends Expresión

case class Suma(e1: Expresión, e2: Expresión) extends Expresión

case class Multiplicación(e1: Expresión, e2: Expresión) extends Expresión

def transcripciónHumana(expresión: Expresión): String = ???


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package ejercicios.parte4Patrones

import lecciones.parte4Patrones.PatronesDondeNoLosEsperas


/**
* Solución de [[PatternMatchingEnunciado]].
*/
object PatternMatchingSolucion {

def main(args: Array[String]): Unit = {
println(transcripciónHumana(Suma(Multiplicación(Número(2), Número(1)), Número(3))))
println(transcripciónHumana(Suma(Multiplicación(Suma(Número(3), Número(2)), Número(4)), Multiplicación(Número(1), Número(2)))))
}

sealed trait Expresión

case class Número(n: Int) extends Expresión

case class Suma(e1: Expresión, e2: Expresión) extends Expresión

case class Multiplicación(e1: Expresión, e2: Expresión) extends Expresión

def transcripciónHumana(expresión: Expresión): String = {
// Una solución
método(expresión)
// Otra solución
función(expresión)
}

/**
* Definido como una función
*/
val función: Expresión => String = {
case Número(n) => n.toString
case Suma(e1, e2) => s"${función(e1)} + ${función(e2)}"
case Multiplicación(e1, e2) =>
val añadeParéntesis: Expresión => String = {
case e@Multiplicación(_, _) => función(e)
case e@Número(_) => función(e)
case e => s"(${función(e)})"
}
s"${añadeParéntesis(e1)} * ${añadeParéntesis(e2)}"

}

/**
* Definido como un método
* @param expresión expresión en clases
* @return expresión humana
*/
def método(expresión: Expresión): String = {
// Método auxiliar para los paréntesis
def quizásAñadeParéntesis(expr: Expresión) = expr match {
case Multiplicación(_, _) => método(expr)
case Número(_) => método(expr)
case _ => "(" + método(expr) + ")"
}

expresión match {
case Número(n) => n.toString
case Suma(e1, e2) => método(e1) + " + " + método(e2)
case Multiplicación(e1, e2) => quizásAñadeParéntesis(e1) + " * " + quizásAñadeParéntesis(e2)
}
}



}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@

package lecciones.parte4Patrones

import ejercicios.parte4Patrones.PatternMatchingEnunciado

/**
* Lección 3 de 4.
*
* El Pattern Matching está presente dentro del propio lenguaje y que habilita muchas de las sintaxis dulces.
* Ejercicios en [[PatternMatchingEnunciado]].
*/
object PatronesDondeNoLosEsperas extends App {

/**
* Los bloques de código try - catch - finally es pattern matching
*/
try {
println(10 / 0)
} catch {
case r: RuntimeException => println("error durante ejecución")
case npe: NullPointerException => println("puntero nulo")
case _ => println("otra cosa")
}

/**
* Las for - comprehensions utilizan la descomposición de case clases y los if de seguridad (if guards)
*/

val listaDeNúmeros = List(1, 2, 3, 4, 5, 6)
val númerosImpares = for {
x <- listaDeNúmeros if x % 2 == 0
} yield 10 * x
println(númerosImpares)

/**
* La asignación de valores a val definidas al mismo tiempo también usa patter matching
*/
val tupla123 = (1, 2, 3)
val (uno, dos, tres) = tupla123
println(uno)
println(dos)
println(tres)

/**
* La descomposición de una lista en cabeza y cola sigue la misma lógica que con los valores asignados a val
*/
val cabeza :: cola = listaDeNúmeros
println(cabeza)
println(cola)

/**
* El pattern matching se puede utilizar como una función anónima definida dentro de un bloque de código.
*/
val listaTransformada = listaDeNúmeros.map {
case v if v % 2 == 0 => s"$v es impar"
case 1 => "el primero"
case _ => "otro número"
}
println(listaTransformada)

val listaTransformadaConNotaciónExplicita = listaDeNúmeros.map { //Aquí está el pattern matching
case v if v % 2 == 0 => s"$v es impar"
case 1 => "el primero"
case _ => "otro número"
}
println(listaTransformadaConNotaciónExplicita)
println(
s"Las listas transformadas son iguales? ${listaTransformada.equals(listaTransformadaConNotaciónExplicita)}"
)

}
97 changes: 97 additions & 0 deletions src/main/scala/lecciones/parte4Patrones/PatternMatching.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@

package lecciones.parte4Patrones

import scala.util.Random

/**
* Lección 1 de 4.
*
* El pattern matching es usado de forma intensiva por los desarrollados Scala profesionales.
* @note La validación se realiza de forma secuencial. Podemos establecer un orden preferencial.
* @see En la sección [[PatronesDondeNoLosEsperas]] veremos como Scala lo usa sin que nosotros nos demos cuenta.
*/
object PatternMatching extends App {

/**
* El pattern matching funciona como un switch en esteroides.
*/
val generadorAleatorio: Random = new Random()
val númeroAleatorio = generadorAleatorio.nextInt(10)

val descripción = númeroAleatorio match {
case 1 => "el primero"
case 2 => "doble o nada"
case 3 => "a la tercera va la vencida"
case _ => "otro número" // _ representa cualquier otro caso no contemplado de forma específica
}

println(númeroAleatorio)
println(descripción)

/**
* Cuando usamos pattern matching con case classes, Scala hace uso del método unapply que genera para nosotros,
* sin que nos demos cuenta, en la case class. Esto permite hacer descomposición.
*/

case class Persona(nombre: String, edad: Int)
val roberto: Persona = Persona("Roberto", 20)

def saludo(p: Persona) = p match {
case Persona(nombre, edad) if edad < 18 => s"Hola, me llamo $nombre y tengo $edad años. No puedo conducir coches."
case Persona(nombre, edad) => s"Hola, me llamo $nombre y tengo $edad años"
case null => "No sé quien soy"
}

def saludo_mal(p: Persona) = p match {
// Si escribimos primero el caso general, nunca llegaremos al caso particular
case Persona(n, a) => s"Hola, me llamo $n y tengo $a años"
// Este es el caso particular. Solo se cumple en el caso de que el if sea true
// El compilador nos avisa de que este caso es "unreachable"
case Persona(n, a) if a < 21 => s"Hola, me llamo $n y tengo $a años y no puedo emborracharme en EEUU - USA"
case null => "No sé quien soy"
}

println(saludo(roberto))
println(saludo(null))
println(saludo_mal(roberto))

/**
* El pattern matching también se utiliza en jerarquías de clases.
* Esto permite definir métodos que reciben instancias de un tipo elevado en la jerarquía y que serán evaluadas
* a un valor diferente en función del subtipo.
*/
sealed class Animal

case class Perro(raza: String) extends Animal

case class Loro(saludo: String) extends Animal

val animal: Animal = Perro("Terra Nova")

def tipoAnimalMensaje(animal: Animal): Unit = animal match {
case Perro(r) => println(s"Este animal es un perro de la raza $r")
// Si utilizas es tipo de patrón y comentas uno de los casos, el compilador te advertirá de que no es exhaustivo
case Loro(s) => println(s"Este animal es un loro que te dice: $s")
}

println(tipoAnimalMensaje(animal))

/**
* Aviso a navegantes
* Aunque el pattern matching es muy poderoso, hay que tener cuidado de cómo lo usamos.
*/

// Esta implementación devuelve el resultado que queremos, pero es muy ineficiente
val númeroImpar_pm: Boolean = númeroAleatorio match {
case n if n % 2 == 0 => true
case _ => false
}

// Podemos implementarlo con if else, pero se puede mejorar
val númeroPar_condicional: Boolean = if (númeroAleatorio % 2 == 0) true else false

// Esta es la implementación correcta en Scala
val númeroPar_bien: Boolean = númeroAleatorio % 2 == 0
// "númeroAleatorio % 2 == 0" es una expresión que devuelve un valor de tipo Boolean

}
Loading

0 comments on commit f504701

Please sign in to comment.