Conceptos básicos de programación II

Aprende a programar con Karel

Ahora, antes de comenzar un tema más avanzado (estructuras de control), considera este ejemplo:

Carga el mundo mundo001 y ejecuta el siguiente código:

library(karel)

cargar_super_karel() # cargar funciones especiales de karel

generar_mundo("mundo014")

avanzar()
avanzar()
avanzar()
avanzar()
avanzar()
avanzar()
avanzar()
avanzar()
avanzar()
avanzar()
avanzar()
avanzar()
avanzar()
avanzar()
avanzar()
avanzar()
avanzar()
avanzar()
avanzar()
avanzar()
avanzar()
ejecutar_acciones()

Estrucutras de control

  • Frecuentemente, cuando ejecutamos un programa, queremos que ciertas partes del código se ejecuten solo si se cumple una condición específica, o queremos que se repitan ciertas partes del código varias veces.
  • Esto se logra con estructuras de control, que son el conjunto de reglas que permiten controlar el flujo de las acciones de un algoritmo o programa.
  • Se dividen en secuenciales, condicionales e iterativas.

Estructuras de control secuenciales

  • Estas estructuras están compuestas por un número definido de acciones que se ubican en un orden específico y se suceden una tras otra.
  • Los ejemplos anteriores están conformados por este tipo de estructura.

Estructuras de control iterativas

  • A veces, queremos que ciertas partes del código se repitan varias veces.
  • Para esto, usamos estructuras de control iterativas, que permiten repetir un bloque de código mientras se cumpla una condición específica.
  • Las estructuras de control iterativas más comunes son for y while.
  • for se usa cuando sabemos cuántas veces queremos que se repita un bloque de código.
  • while se usa cuando no sabemos cuántas veces queremos que se repita un bloque de código.
  • La estructura de for en R es la siguiente:
for (i in <valor_inicial>:<valor_final>) {
    ...Acción/es...
}
  • La letra i se usa para representar la cantidad de repeticiones.
  • Por ejemplo, si queremos repetir un bloque de código 5 veces, escribimos for (i in 1:5).
  • Ahora, vamos a ver un ejemplo de cómo usar un for para que Karel avance 19 veces en el mundo mundo014.
generar_mundo("mundo014")
for (i in 1:19) {
  avanzar()
}
ejecutar_acciones()

  • En este caso, Karel avanzará 19 veces en lugar de tener que escribir avanzar() 19 veces.

  • Por otro lado, la estructura de while en R es la siguiente:

while (condición) {
    ...Acción/es a repetir...
}
  • Estas iteraciones pueden continuar mientras que se verifique alguna condición.

  • el conjunto de instrucciones se repite mientras que se siga evaluando como VERDADERO una condición declarada al inicio del bloque.

  • Cuando la condición ya no se cumple, el proceso deja de ejecutarse.

  • En vez de usar un for en el cual hay que especificar la cantidad de veces que el proceso debe repetirse, podemos usar un while para que Karel siga avanza hasta que encuentre una pared en el mundo mundo014.

  • El paquete de Karel nos da algunas condiciones que podemos evaluar:

    • frente_abierto() …no hay una pared enfrente de Karel
    • frente_cerrado() … hay una pared enfrente de Karel
    • izquierda_abierto() …no hay una pared a la izquierda de Karel
    • izquierda_cerrado() …hay una pared a la izquierda de Karel
    • derecha_abierto() …no hay una pared a la derecha de Karel
    • derecha_cerrado() …hay una pared a la derecha de Karel
    • hay_cosos() …hay cosos donde se encuentra Karel
    • no_hay_cosos() …no hay cosos donde se encuentra Karel
    • karel_tiene_cosos() …Karel tiene cosos en su mochila
    • karel_no_tiene_cosos() …Karel no tiene cosos en su mochila
    • mira_al_este() …Karel está mirando al este
    • mira_al_norte() …Karel está mirando al norte
    • mira_al_oeste() …Karel está mirando al oeste
    • mira_al_sur()…Karel está mirando al sur
  • Vamos a ver un ejemplo de cómo usar un while para que Karel avance hasta que encuentre una pared en el mundo mundo014.

generar_mundo("mundo014")
while (frente_abierto()) {
  avanzar()
}
ejecutar_acciones()
Tip

A veces es más facil visualizar el flujo de un script con un diagrama de flujo. Por ejemplo, el siguiente diagrama de flujo representa el código anterior:

  • En este caso, Karel avanzará hasta que encuentre una pared en lugar de tener que escribir avanzar() 19 veces.
  • Este código es más flexible ya que Karel seguirá avanzando hasta que encuentre una pared, sin importar cuántas veces tenga que avanzar.
  • Si desconociéramos cuántas veces Karel tiene que avanzar, sería más conveniente usar un while en lugar de un for.
Warning
  • Es importante tener cuidado con las estructuras de control iterativas, ya que si no se especifica una condición de salida, el programa podría entrar en un bucle infinito.
  • Observa lo que pasa en el siguiente código.
  • Como es un loop infinito, tendrás que parar el código manualmente. Esto se hace presionando el botón de interrupción en la consola de R (botón rojo o tecla escape).
# No correr! (o sí, para ver que pasa)
generar_mundo("mundo003")
while (no_hay_cosos()) {
    girar_izquierda()
}
ejecutar_acciones()
  • Qué pasa en este código? Karel girará a la izquierda hasta que encuentre un coso, pero en este mundo no hay cosos, por lo que Karel girará indefinidamente.
  • Ahora, imagina que Karel está en un mundo totalmente rodeado de paredes.
  • Qué pasa si ejecutamos el siguiente código?
generar_mundo("mundo015")

while (frente_abierto()) {
  avanzar()
  ejecutar_acciones()
}

  • Como ves, Karel no puede avanzar porque no hay un espacio libre frente a él.
  • En este caso, algo que nos resultaría útil es primero preguntar si hay un espacio libre frente a Karel.
  • Esto es un ejemplo de una estructura de control condicional, que veremos a continuación.

Estrucutras de control condicionales

  • Para resolver problemas, podemos necesitar que ciertas partes del código se ejecuten solo si se cumple una condición específica.
  • Podemos pedirle al código que este se ejecute solo si una condición es verdadera (TRUE) o falsa (FALSE).

if

  • La estructura if es la más básica de las estructuras condicionales.
  • Se usa para ejecutar un bloque de código si una condición es verdadera.
  • La sintaxis básica de un if en R es la siguiente:
if (condición) {
  # Código que se ejecuta si la condición es TRUE
}
  • Si la condición es verdadera, el código dentro de las llaves {} se ejecutará.
  • Karel tiene las siguientes evaluaciones que podemos usar en un if:
    • frente_abierto(): Evalúa que no exista una pared frente a Karel.
    • izquierda_abierto(): Evalúa que no exista una pared a la izquierda de Karel.
    • hay_cosos() : hay cosos donde se encuentra Karel
    • karel_tiene_cosos(): Evalúa si Karel tiene al menos un coso en su mochila.
    • mira_este(): Evalúa si Karel está mirando hacia el este.
  • Mira como podemos modificar el código anterior para que Karel avance solo si hay un espacio libre frente a él.

generar_mundo("mundo013")
if (frente_abierto()) {
  avanzar()
  ejecutar_acciones()
}

  • Observa que Karel no avanza porque la condición de frente_abierto() es falsa. En este caso, no tenemos error en el código, simplemente no se ejecuta la acción de avanzar.
  • Algo útil que podríamos hacer es decirle a Karel que, si no hay un espacio libre frente a él, nos diga que no puede avanzar.

if else

  • A veces, queremos que se ejecute un bloque de código si una condición es verdadera y otro bloque si es falsa.
  • Para esto, usamos la estructura if else, que significa “si no es verdadero, entonces realiza esto”.
  • la sintaxis básica de un if else en R es la siguiente:
if (condición) {
  # Código si la condición es TRUE
} else {
  # Código si la condición es FALSE
}
generar_mundo("mundo013")

if (frente_abierto()) {
  avanzar()
  ejecutar_acciones()
} else {
  print("no tengo espacio")
}

  • Ahora Karel nos dirá que no tiene espacio para avanzar si no hay un espacio libre frente a él.

if else if

  • A veces, queremos evaluar múltiples condiciones.
  • Para esto, usamos la estructura if else if, que significa “si no es verdadero, entonces evalúa esta otra condición”.
  • La sintaxis básica de un if else if en R es la siguiente:
if (condición1) {
  # Código si condición1 es TRUE
} else if (condición2) {
  # Código si condición2 es TRUE
} else {
  # Código si ninguna condición es TRUE
}
  • Considera el siguiente ejemplo
  • en este mundo, podemos hacer lo siguiente:
  • Primero, preguntar si hay frente_abierto(), si es verdadero, avanzar.
  • Si no es verdadero, preguntar si hay derecha_abierto(), si es verdadero, girar_derecha() y avanzar.
  • Si no es verdadero, girar_izquierda() y avanzar.

generar_mundo("mundo024")

if (frente_abierto()) {
  avanzar()
} else if (derecha_abierto()) {
  girar_derecha()
  avanzar()
} else {
  girar_izquierda()
  avanzar()
}

ejecutar_acciones()

NOTA. En la condición Derecha abierta le falta un girar derecha antes de avanzar, como en el código
  • Cómo podemos mejorar este código para que Karel avance hasta que encuentre una pared y en el proceso recolecte todos los cosos que encuentre?
  • En este caso, podemos usar un while para que Karel avance hasta que encuentre una pared y un if para que recolecte los cosos que encuentre y revise que haya frente libre.
  • Vamos a solucionar los problemas por partes y luego los juntaremos.
  • El primer problema que tenemos que resolver es que Karel avanze mientras tenga frente abierto.
generar_mundo("mundo024")

while (frente_abierto()) {
  avanzar()
  ejecutar_acciones()
}
  • Como en este mundo no hay frente abierto, karel simplemente no avanzará.
  • Ahora, vamos a resolver el segundo problema, que Karel revise si tiene el frente_izquierdo() abierto y si es cierto, avanzar.
  • Primero, podemos poner crear una función que nos permita avanzar hasta que encuentre una pared (código pasado). Depsués, podemos decirle a Karel que gire izquierda y avance hasta que encuentre una pared.
generar_mundo("mundo024")

moverse_hasta_pared <- function() {
  while (frente_abierto()) {     # Mientras el frente esté abierto
    avanzar()                    # Avanza
  }
}

girar_izquierda()
moverse_hasta_pared()

ejecutar_acciones()

  • Podemos hacer más eficiente el código anterior con una condición if que evalúe si hay izquierda_abierto() y si es verdadero, girar_izquierda() y avanzar().
generar_mundo("mundo024")

moverse_hasta_pared <- function() {
  while (frente_abierto()) {     # Mientras el frente esté abierto
    avanzar()                    # Avanza
  }
}

if (frente_abierto()) {
  moverse_hasta_pared()
} else {
  girar_izquierda()
  moverse_hasta_pared()
}


ejecutar_acciones()

  • Pero ahora, considera que podemos mejorar este código. Imagina que Karel no tiene el frente izquierdo libre, no podría avanzar.
  • Podemos hacer esto con un if else if.
  • En el siguiente código, primero preguntamos si la condición frente_abierto() es verdadera, si es así, avanzamos. Si no, giramos a la izquierda. Ahora, antes de avanzar, preguntamos si tenemos el frente abierto, si es así, avanzamos. Esto evita errores si Karel no tiene la izquierda abierta.
generar_mundo("mundo024")

moverse_hasta_pared <- function() {
  while (frente_abierto()) {     # Mientras el frente esté abierto
    avanzar()                    # Avanza
  }
}

if (frente_abierto()) {
  moverse_hasta_pared()
} else {
  girar_izquierda()
  if (frente_abierto()) {
    moverse_hasta_pared()
  }
}


ejecutar_acciones()
  • Ahora, nos falta que Karel recolecte los cosos que encuentre en el camino.
  • Podemos hacer esto con un if que evalúe si hay cosos y si es así, recolectarlos.
  • Esto lo podemos hacer dentro de la función moverse_hasta_pared().
generar_mundo("mundo024")


# Función para avanzar y recolectar coso si hay frente abierto
moverse_hasta_pared <- function() {
  while (frente_abierto()) {     # Mientras el frente esté abierto
    avanzar()                    # Avanza
    if (hay_cosos()) {            # Si hay un coso, lo recolecta
      juntar_coso()
    }
  }
}

# Función principal que controla el movimiento de Karel
explorar_laberinto <- function() {
  if (frente_abierto()) {  # Si el frente está abierto
    moverse_hasta_pared() # Avanza y recolecta cosos hasta que el frente se cierre
  } else {                 # Si el frente está cerrado
    girar_izquierda()      # Gira a la izquierda para verificar
    if (frente_abierto()) {  # Si el lado izquierdo está abierto
      moverse_hasta_pared() # Avanza y recolecta cosos en esa dirección
    }
  }
}

# Ejecución del programa
generar_mundo("mundo024")
explorar_laberinto()
ejecutar_acciones()  # Ejecuta las acciones en el mundo de Karel

  • Como observarás, este es un caso en el que se combinan estructuras de control secuenciales, condicionales e iterativas.

  • Construir programas complejos a partir de estructuras de control más simples es una de las claves de la programación.

  • Sin embargo, muchas veces las soluciones más eficientes no son las más complejas, sino las más sencillas y fáciles de entender.

  • Regresemos al ejemplo del laberinto del final de la lección pasada:

  • Karel está en un laberinto como el siguiente, donde el coso indica la salida.

  • Tip: Hay una estrategia para resolver cualquier laberinto: si cuando entrás tocás con tu mano la pared a tu derecha y caminás sin dejar de tocarla nunca, eventualmente llegarás a la salida.
  • implementa el algoritmo de la mano derecha que le ayude a Karel salir del laberinto del mundo mundo009.

Tip

El flujo del código debe representar la siguiente lógica:

Mostrar Respuesta
generar_mundo("mundo009")
while (no_hay_cosos()) {
  girar_derecha()
  while (frente_cerrado()) {
    girar_izquierda()
  }
  avanzar()
}
ejecutar_acciones()

Ejercicios Extra

  • en el mundo ‘mundo021’, crea una función que le permita a karel recolectar todos los cosos.
  • Intenta lo mismo en el mundo018
  • Haz que Karel recorra los bordes del mundo020.
  • Vuelve a realizar los ejercicios de las últimas dos lecciones y trata de resolverlos con distintas estructuras de control.
  • Si tienes problemas, trata de resolver el código por pequeñas partes. Una vez que tengas una parte del código funcionando, puedes agregar más partes. En cada paso puedes usar la función ejecutar_acciones() para ver cómo se comporta Karel en cada paso.
  • Adicionalmente, trata de construir un diagrama de flujo para cada uno de los ejercicios que realices.