AA1 · Ejercicios de arquitectura y estado¶
Cada ejercicio parte del esqueleto que se te da. Escribe la solución en un archivo .kt aparte (en IntelliJ IDEA o con kotlinc) y pega el main del enunciado para comprobarla: tu salida debe ser idéntica al resultado esperado, que se ha obtenido ejecutando la solución modelo de verdad. Los marcados con ⭐ son más difíciles.
Ejercicio AA1.1¶
Contador con reductor. Escribe reducir para que Sumar sume 1, Restar reste 1 sin bajar nunca de 0 y Reiniciar vuelva a 0.
data class ContadorState(val valor: Int = 0)
sealed interface EventoContador {
data object Sumar : EventoContador
data object Restar : EventoContador
data object Reiniciar : EventoContador
}
fun reducir(estado: ContadorState, evento: EventoContador): ContadorState = TODO()
Para comprobarlo:
fun main() {
var e = ContadorState()
val eventos = listOf(EventoContador.Sumar, EventoContador.Sumar, EventoContador.Restar, EventoContador.Restar,
EventoContador.Restar, EventoContador.Sumar, EventoContador.Reiniciar, EventoContador.Restar)
for (ev in eventos) { e = reducir(e, ev); print("${e.valor} ") }
println()
}
Resultado esperado:
1 2 1 0 0 1 0 0
Solución modelo (bloqueada)
🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:
data class ContadorState(val valor: Int = 0)
sealed interface EventoContador {
data object Sumar : EventoContador
data object Restar : EventoContador
data object Reiniciar : EventoContador
// ... (resto de la solución bloqueado)Ejercicio AA1.2¶
Estado derivado. Completa CarritoUiState con tres propiedades calculadas (sin guardarlas): total (precio × cantidad de todas las líneas), unidades (suma de cantidades) y vacio.
data class Linea(val nombre: String, val precio: Int, val cantidad: Int) // el precio, en euros enteros
data class CarritoUiState(val lineas: List<Linea> = emptyList()) {
// total, unidades y vacio
}
Para comprobarlo:
fun main() {
val vacio = CarritoUiState()
println("vacío: total=${vacio.total}, unidades=${vacio.unidades}, vacio=${vacio.vacio}")
val lleno = CarritoUiState(listOf(Linea("Cuaderno", 3, 2), Linea("Bolígrafo", 1, 5), Linea("Mochila", 25, 1)))
println("lleno: total=${lleno.total}, unidades=${lleno.unidades}, vacio=${lleno.vacio}")
}
Resultado esperado:
vacío: total=0, unidades=0, vacio=true
lleno: total=36, unidades=8, vacio=false
Solución modelo (bloqueada)
🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:
data class Linea(val nombre: String, val precio: Int, val cantidad: Int) // el precio, en euros enteros
data class CarritoUiState(val lineas: List<Linea> = emptyList()) {
val total: Int get() = lineas.sumOf { it.precio * it.cantidad }
val unidades: Int get() = lineas.sumOf { it.cantidad }
val vacio: Boolean get() = lineas.isEmpty()
// ... (resto de la solución bloqueado)Ejercicio AA1.3¶
Las fases de una pantalla. Escribe mensaje(p) con un when sin else: Cargando → «Cargando el perfil...»; Datos → «Marta (17 años)»; SinConexion con menos de 3 intentos → «Sin conexión. Reintentando (1/3)» y con 3 o más → «Sin conexión. Inténtalo más tarde».
sealed interface PerfilUi {
data object Cargando : PerfilUi
data class Datos(val nombre: String, val edad: Int) : PerfilUi
data class SinConexion(val intentos: Int) : PerfilUi
}
Para comprobarlo:
fun main() {
listOf(PerfilUi.Cargando, PerfilUi.Datos("Marta", 17), PerfilUi.SinConexion(1), PerfilUi.SinConexion(3))
.forEach { println(mensaje(it)) }
}
Resultado esperado:
Cargando el perfil...
Marta (17 años)
Sin conexión. Reintentando (1/3)
Sin conexión. Inténtalo más tarde
Solución modelo (bloqueada)
🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:
sealed interface PerfilUi {
data object Cargando : PerfilUi
data class Datos(val nombre: String, val edad: Int) : PerfilUi
data class SinConexion(val intentos: Int) : PerfilUi
}
// ... (resto de la solución bloqueado)Ejercicio AA1.4¶
Sin perder incrementos. Implementa sumar(n) de forma que 50 corrutinas sumando 1000 veces cada una den siempre 50 000.
class ContadorCompartido {
val total = MutableStateFlow(0)
fun sumar(n: Int) { /* ... */ }
}
Para comprobarlo:
fun main() = runBlocking {
val c = ContadorCompartido()
withContext(Dispatchers.Default) {
coroutineScope {
repeat(50) { launch { repeat(1000) { c.sumar(1) } } }
}
}
println("total = ${c.total.value}")
}
Resultado esperado:
total = 50000
Solución modelo (bloqueada)
🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:
import kotlinx.coroutines.flow.*
class ContadorCompartido {
val total = MutableStateFlow(0)
fun sumar(n: Int) {
total.update { it + n }
// ... (resto de la solución bloqueado)Ejercicio AA1.5¶
Un aviso que se consume. enviar(correo) debe dejar en mensaje «Correo no válido» si no hay @, o «Enviado a » seguido del correo si lo hay. mensajeMostrado() borra el aviso.
data class CorreoUiState(val mensaje: String? = null)
class CorreoModelo {
private val _estado = MutableStateFlow(CorreoUiState())
val estado: StateFlow<CorreoUiState> = _estado.asStateFlow()
fun enviar(correo: String) { /* ... */ }
fun mensajeMostrado() { /* ... */ }
}
Para comprobarlo:
fun main() {
val m = CorreoModelo()
m.enviar("ana.ejemplo.com")
println(m.estado.value.mensaje)
m.mensajeMostrado()
println(m.estado.value.mensaje)
m.enviar("ana@ejemplo.com")
println(m.estado.value.mensaje)
}
Resultado esperado:
Correo no válido
null
Enviado a ana@ejemplo.com
Solución modelo (bloqueada)
🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:
import kotlinx.coroutines.flow.*
data class CorreoUiState(val mensaje: String? = null)
class CorreoModelo {
private val _estado = MutableStateFlow(CorreoUiState())
val estado: StateFlow<CorreoUiState> = _estado.asStateFlow()
// ... (resto de la solución bloqueado)Ejercicio AA1.6¶
⭐ Deshacer. Escribe el reductor: Sumar(n) suma n y recuerda el valor anterior en pasado; Deshacer vuelve al último valor recordado y, si no hay nada que deshacer, no cambia nada.
data class HistState(val valor: Int = 0, val pasado: List<Int> = emptyList())
sealed interface EventoHist {
data class Sumar(val n: Int) : EventoHist
data object Deshacer : EventoHist
}
Para comprobarlo:
fun main() {
var e = HistState()
val eventos = listOf(EventoHist.Sumar(5), EventoHist.Sumar(3), EventoHist.Deshacer, EventoHist.Sumar(10),
EventoHist.Deshacer, EventoHist.Deshacer, EventoHist.Deshacer)
for (ev in eventos) {
e = reducir(e, ev)
println("valor=${e.valor}, pasos que se pueden deshacer=${e.pasado.size}")
}
}
Resultado esperado:
valor=5, pasos que se pueden deshacer=1
valor=8, pasos que se pueden deshacer=2
valor=5, pasos que se pueden deshacer=1
valor=15, pasos que se pueden deshacer=2
valor=5, pasos que se pueden deshacer=1
valor=0, pasos que se pueden deshacer=0
valor=0, pasos que se pueden deshacer=0
Solución modelo (bloqueada)
🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:
data class HistState(val valor: Int = 0, val pasado: List<Int> = emptyList())
sealed interface EventoHist {
data class Sumar(val n: Int) : EventoHist
data object Deshacer : EventoHist
}
// ... (resto de la solución bloqueado)