if 2 + 3 == 5:
    x = 5 + 3
    mensaje = "Verdadero!"
else:
    x = 5 - 3
    mensaje = "Falso!"
    
print(x)
print(mensaje)

"""
Explanation: <a href="https://www.python.org/"><img src="./Imagenes/python-logo.png" alt="Python Logo" style="width: 200px; display:inline;"/></a>
Python es un lenguaje de programación:
- De Alto nivel, es decir, lidiamos con abstracciones y no nos preocupamos de los detalles internos de las computadoras, tal como utilizar direcciones de memoria y registros.
- Multipropósito, pues es utilizado para construir: websites, programas de escritorio, videojuegos, análisis de datos, etc.
- <a href="https://en.wikipedia.org/wiki/Interpreter_(computing)">Interpretado</a>, ya que Python incluye algo llamado intérprete, lo que va traduciendo el texto escrito por el usuario en instrucciones que la computadora comprende a medida que lo va leyendo.
- Dinámico
- Multiparadigma
    - Imperativo
    - Orientado a objetos
    - Procedural
    - Funcional
<a href="https://www.continuum.io/"><img src="./Imagenes/anaconda-logo.svg" alt="Anaconda Logo" style="width: 200px; display:inline;"/></a>
Plataforma de data science. Incluye:

Lenguaje de programación Python
Entorno de programación tradicional Spyder
Entorno de programación multipropósito Jupyter Notebook
Librerías de data science (sklearn, numpy, pandas, nltk, matplotlib, etc.)

<img src="./Imagenes/spyder-logo.png" alt="Spyder Logo" style="height: 100px;"/>

Particularidades de Python
Python tiene ciertas características que lo definen, y lo diferencian de otros lenguajes.
Indentación para bloques de código
En python, las estructuras de control no están delimitadas por caracteres como los tradicionales corchetes:
c
if (2 + 3 == 5) {
    x = 5 + 3;
    y = 2 + 3;
} else {
    x = 5 - 3;
    y = 2 - 3;
}
En Python los caracteres de espacio importan. En lugar de los corchetes, Python usa una indentación o sangría, por defecto de 4 espacios, para denotar un bloque de código subordinado a una estructura de control:
End of explanation
"""


type(True)

"""
Explanation: De esta manera, Python estandariza el aspecto del código desde la definición del lenguaje.
Nota:
 - Como se puede observar en el código anterior, en Python, las estructuras de bloque no requieren paréntesis, y se delimitan por el caracter de dos puntos ":"
 - No es necesario el caracter punto y coma ";" al final de cada expresión si es la única en la linea. Pero puede ser usada para colocar múltiples en una sola línea.
 python
 x = 1 + 1; print(x);
Palabras Reservadas
Adicionalmente Python cuenta con un conjunto de palabras, llamadas palabras clave o reservadas que tienen un significado especial dentro del lenguaje, las cuales no pueden ser utilizadas como nombre de variables:
<pre>False      class      finally    is         return
None       continue   for        lambda     try
True       def        from       nonlocal   while
and        del        global     not        with
as         elif       if         or         yield
assert     else       import     pass
break      except     in         raise</pre>

Tipos de dato
Python maneja varios tipos de dato predefinidos. Mencionaremos algunos tipos de dato comúnmente utilizados:
Booleanos
Tipos de datos que expresan valor de verdad.
End of explanation
"""


type(5)

type(3.1416)

"""
Explanation: Los operadores para variables booleanas son sólo 3
<table border="1" class="docutils">
<colgroup>
<col width="25%">
<col width="62%">
</colgroup>
<thead valign="bottom">
<tr class="row-odd"><th class="head">Operation</th>
<th class="head">Result</th>

</tr>
</thead>
<tbody valign="top">
<tr class="row-even"><td><code class="docutils literal"><span class="pre">x</span> <span class="pre">or</span> <span class="pre">y</span></code></td>
<td>si <em>x</em> es falso, entonces <em>y</em>, en caso contrario <em>x</em></td>

</tr>
<tr class="row-odd"><td><code class="docutils literal"><span class="pre">x</span> <span class="pre">and</span> <span class="pre">y</span></code></td>
<td>si <em>x</em> es falso, entonces <em>x</em>, en caso contrario <em>y</em></td>

</tr>
<tr class="row-even"><td><code class="docutils literal"><span class="pre">not</span> <span class="pre">x</span></code></td>
<td>si <em>x</em> es falso, entonces <code class="docutils literal"><span class="pre">True</span></code>,
en caso contrario <code class="docutils literal"><span class="pre">False</span></code></td>

</tr>
</tbody>
</table>

Fuente: Python docs.
Numéricos
End of explanation
"""


lista_vacia = []
print(lista_vacia)

#O equivalentemente
lista_vacia = list()
print(lista_vacia)

"""
Explanation: La siguiente tabla muestra las operaciones posibles entre variables numéricas:
<table border="1" class="docutils">
<colgroup>
<col width="33%">
<col width="67%">
</colgroup>
<thead valign="bottom">
<tr class="row-odd"><th class="head">Operación</th>
<th class="head">Resultado</th>
</tr>
</thead>
<tbody valign="top">
<tr class="row-even"><td><code class="docutils literal"><span class="pre">x</span> <span class="pre">+</span> <span class="pre">y</span></code></td>
<td>suma de <em>x</em> e <em>y</em></td>

</tr>
<tr class="row-odd"><td><code class="docutils literal"><span class="pre">x</span> <span class="pre">-</span> <span class="pre">y</span></code></td>
<td>diferencia de <em>x</em> e <em>y</em></td>

</tr>
<tr class="row-even"><td><code class="docutils literal"><span class="pre">x</span> <span class="pre">*</span> <span class="pre">y</span></code></td>
<td>producto de <em>x</em> e <em>y</em></td>

</tr>
<tr class="row-odd"><td><code class="docutils literal"><span class="pre">x</span> <span class="pre">/</span> <span class="pre">y</span></code></td>
<td>cociente de <em>x</em> e <em>y</em></td>

</tr>
<tr class="row-even"><td><code class="docutils literal"><span class="pre">x</span> <span class="pre">//</span> <span class="pre">y</span></code></td>
<td>cociente of <em>x</em> e <em>y</em> con redondeo hacia abajo</td>

</tr>
<tr class="row-odd"><td><code class="docutils literal"><span class="pre">x</span> <span class="pre">%</span> <span class="pre">y</span></code></td>
<td>resto de <code class="docutils literal"><span class="pre">x</span> <span class="pre">/</span> <span class="pre">y</span></code></td>

</tr>
<tr class="row-even"><td><code class="docutils literal"><span class="pre">-x</span></code></td>
<td>cambio de signo de <em>x</em></td>

</tr>
<tr class="row-odd"><td><code class="docutils literal"><span class="pre">+x</span></code></td>
<td><em>x</em> sin modificación</td>

</tr>
<tr class="row-even"><td><code class="docutils literal"><span class="pre">abs(x)</span></code></td>
<td>valor absoluto o magnitud de <em>x</em></td>

</tr>
<tr class="row-odd"><td><code class="docutils literal"><span class="pre">int(x)</span></code></td>
<td><em>x</em> covnertido a entero (integer)</td>

</tr>
<tr class="row-even"><td><code class="docutils literal"><span class="pre">long(x)</span></code></td>
<td><em>x</em> convertido a entero largo (long integer)</td>

</tr>
<tr class="row-odd"><td><code class="docutils literal"><span class="pre">float(x)</span></code></td>
<td><em>x</em> convertido a punto flotante (floating point)</td>

</tr>
<tr class="row-even"><td><code class="docutils literal"><span class="pre">complex(re,im)</span></code></td>
<td>un número complejo con parte real
<em>re</em>, parte imaginaria <em>im</em>.
<em>im</em> es por defecto cero.</td>

</tr>
<tr class="row-odd"><td><code class="docutils literal"><span class="pre">c.conjugate()</span></code></td>
<td>la conjugación del número complejo <em>c</em>.</td>

</tr>
<tr class="row-even"><td><code class="docutils literal"><span class="pre">divmod(x,</span> <span class="pre">y)</span></code></td>
<td>el par <code class="docutils literal"><span class="pre">(x</span> <span class="pre">//</span> <span class="pre">y,</span> <span class="pre">x</span> <span class="pre">%</span> <span class="pre">y)</span></code></td>

</tr>
<tr class="row-odd"><td><code class="docutils literal"><span class="pre">pow(x,</span> <span class="pre">y)</span></code></td>
<td><em>x</em> elevado a la <em>y</em></td>

</tr>
<tr class="row-even"><td><code class="docutils literal"><span class="pre">x</span> <span class="pre">**</span> <span class="pre">y</span></code></td>
<td><em>x</em> elevado a la <em>y</em></td>

</tr>
</tbody>
</table>

Fuente: Python docs.
Secuencias
Python maneja distintos dipos de secuencias: listas, tuplas y rangos. Cada una tiene sus características, sin embargo la gran mayoría de las siguientes operaciones puede ser utilizada con ellas.
<table class="docutils" id="index-20">
<colgroup>
<col width="38%">
<col width="62%">
</colgroup>
<thead valign="bottom">
<tr class="row-odd"><th class="head">Operación</th>
<th class="head">Resultado</th>

</tr>
</thead>
<tbody valign="top">
<tr class="row-even"><td><code class="docutils literal"><span class="pre">x</span> <span class="pre">in</span> <span class="pre">s</span></code></td>
<td><code class="docutils literal"><span class="pre">True</span></code> si un elemento de <em>s</em> es
igual a <em>x</em>, en caso contrario <code class="docutils literal"><span class="pre">False</span></code></td>

</tr>
<tr class="row-odd"><td><code class="docutils literal"><span class="pre">x</span> <span class="pre">not</span> <span class="pre">in</span> <span class="pre">s</span></code></td>
<td><code class="docutils literal"><span class="pre">False</span></code> si un elemento de <em>s</em> es
igual a <em>x</em>, en caso contrario <code class="docutils literal"><span class="pre">True</span></code></td>

</tr>
<tr class="row-even"><td><code class="docutils literal"><span class="pre">s</span> <span class="pre">+</span> <span class="pre">t</span></code></td>
<td>la concatenación de <em>s</em> y
<em>t</em></td>

</tr>
<tr class="row-odd"><td>
<code class="docutils literal"><span class="pre">s</span> <span class="pre">*</span> <span class="pre">n</span></code> ó
<code class="docutils literal"><span class="pre">n</span> <span class="pre">*</span> <span class="pre">s</span></code></td>
<td>equivalente a añadir <em>s</em> a sí mismo <em>n</em> veces</td>

</tr>
<tr class="row-even"><td><code class="docutils literal"><span class="pre">s[i]</span></code></td>
<td><em>i</em>-ésimo elemento de <em>s</em>, con origen 0</td>

</tr>
<tr class="row-odd"><td><code class="docutils literal"><span class="pre">s[i:j]</span></code></td>
<td>subsecuencia (slice) de <em>s</em> desde posición <em>i</em> hasta posición <em>j</em></td>

</tr>
<tr class="row-even"><td><code class="docutils literal"><span class="pre">s[i:j:k]</span></code></td>
<td>subsecuencia (slice) de <em>s</em> desde posición <em>i</em> hasta posición <em>j</em> con pasos de longitud <em>k</em></td>

</tr>
<tr class="row-odd"><td><code class="docutils literal"><span class="pre">len(s)</span></code></td>
<td>longitud de <em>s</em></td>

</tr>
<tr class="row-even"><td><code class="docutils literal"><span class="pre">min(s)</span></code></td>
<td>elemento de menor valor en <em>s</em></td>

</tr>
<tr class="row-odd"><td><code class="docutils literal"><span class="pre">max(s)</span></code></td>
<td>elemento de mayor valor en <em>s</em></td>

</tr>
<tr class="row-even"><td><code class="docutils literal"><span class="pre">s.index(x[,</span> <span class="pre">i[,</span> <span class="pre">j]])</span></code></td>
<td>índice de la primera ocurrencia de <em>x</em> en <em>s</em> (en o después del índice 
<em>i</em> y antes del índice <em>j</em>)</td>

</tr>
<tr class="row-odd"><td><code class="docutils literal"><span class="pre">s.count(x)</span></code></td>
<td>cantidad total de ocurrencias de
<em>x</em> en <em>s</em></td>

</tr>
</tbody>
</table>

Fuente: Python docs.
Adicionalmente las secuencias pueden ser mutables o inmutables. Las secuencias mutables permiten las siguientes operaciones:
| Operación | Resultado |
| --- | --- |
| s[i] = x | elemento i de s es reemplazado por x |
| s[i:j] = t | slice (subsección) de s desde i hasta j es reemplazada por los componentes del iterable t |
| del s[i:j] | igual que s[i:j] = [] |
| s[i:j:k] = t | los elementos de s[i:j:k] son reemplazados por los de t |
| del s[i:j:k] | remueve los elementos de s[i:j:k] de la lista |
| s.append(x) | añade x al final de la secuencia (igual que s[len(s):len(s)] = [x]) |
| s.clear() | remueve todos los elementos de s (igual que del s[:]) |
| s.copy() | crea una copia superficial de s (igual que s[:]) |
| s.extend(t) ó s += t | extiende s con los contenidos de t (por lo general igual que s[len(s):len(s)] = t) |
| s *= n | actualiza s con sus contenidos repetidos n veces |
| s.insert(i, x) | inserta x en s en la posición i inserts (igual que s[i:i] = [x]) |
| s.pop([i]) | obtiene y remueve el elemento en la posición i de s |
| s.remove(x) | remueve el primer elemento de s donde s[i] == x |
| s.reverse() | invierte los lugares de los elementos de s |
Fuente: Python docs.
a) Listas (Secuencias)
Las listas son la estructura de datos básica de Python. Se pueden definir de la siguiente manera:
End of explanation
"""


semana = ["Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado", "Domingo"]
print(semana)

"""
Explanation: También pueden crearse directamente con valores adentro:
End of explanation
"""


cosas_aleatorias = [1+2, "Donald Trump", None, 3.5]
print(cosas_aleatorias)

"""
Explanation: Las listas no están restringidas a tener elementos del mismo tipo:
End of explanation
"""


#En Python, los índices inician en 0
print (cosas_aleatorias[0])
print (cosas_aleatorias[1])

"""
Explanation: Para acceder a algun valor, solo hace falta indizarlo haciendo uso de la siguiente sintaxis con corchetes:
End of explanation
"""


print (semana[0:3]) #Desde el primero hasta el tercer elemento
print (semana[0:7:2]) #Toda la lista, pero cada dos elementos
print (semana[:3]) #Desde el inicio de la lista hasta el tercer elemento
print (semana[5:]) #Desde el quinto elemento hasta el final de la lista
print (semana[:-2]) #Desde el primer elemento hasta 2 espacios antes del final de la lista
print (semana[:]) #Toda la lista

"""
Explanation: Adicionalmente se pueden seleccionar subsecciones de las listas haciendo slicing.
Para el slicing, el primer índice indica el inicio de la sublista (inclusivo), el segundo indica el fin (exclusivo), y el tercer número opcional indica cada cuantos elementos coger.
La selección de sublistas adicionalmente tienen reglas adicionales ilustradas a continuación:
End of explanation
"""


cosas_aleatorias[:3] = [1, 2, 3]
print(cosas_aleatorias)

"""
Explanation: Nota: Hay que tener cuidado con un detalle. Las sublistas seleccionadas de esta manera son un reflejo de la lista original. En otras palabras, si se modifica esta sublista, se modifica la lista original.
End of explanation
"""


dias_en_diciembre = list(range(1, 32))
print (dias_en_diciembre)

"""
Explanation: Una forma rápida de definir una lista conteniendo una secuencia de enteros es haciendo uso de los rangos.
End of explanation
"""


[2**x for x in range(5)]

[x for x in dias_en_diciembre if x % 2 == 0] #Días pares en diciembre

"""
Explanation: Pero si las reglas son un poco más complejas, es necesario hacer uso de los llamados list comprehensions. Las list comprehensions son expresiones que son usadas para describir un conjunto de valores, de forma similar a como se describen los conjuntos por comprensión en matemática:
$S = {x^2 : x > 0 \wedge x \le 5}$
Lo cual, expresado por extensión, resulta: 
$S = {1, 2, 4, 8, 16}$
De la misma manera podemos definir una lista haciendo uso de list comprehensions:
End of explanation
"""


#Las tuplas se crean haciendo uso de paréntesis en lugar de corchetes
semana = ("Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado", "Domingo")
semana[0] = "Enero" #Intentar modificar un valor de una tupla genera un error

"""
Explanation: Los list comprehensions tienen tres partes:

La función
El dominio
La condición

Expresado de la siguiente manera:
python
[{Funcion(x)} for x in {dominio} if {condicion}]
Y se puede entender como "aplica la función a todo elemento del dominio que cumpla con la condición y devuelve los resultados en una nueva lista".
b) Tuplas (Secuencias)
Las tuplas son muy similares a las listas, con la crucial distinción de que son inmutables. En otras palabras, no se pueden modificar los valores contenidos en las tuplas:
End of explanation
"""


pangrama = "El veloz murciélago hindú comía feliz cardillo y kiwi. La cigüeña tocaba el saxofón detrás del palenque de paja"
print(pangrama[3:8])

pangrama[3:8] = "lento"

"""
Explanation: Cadenas de caracteres (Secuencias)
Las cadenas de caracteres en Python son secuencias inmutables, y se pueden aplicar las mismas operaciones comunes a todos los tipos de dato secuencia inmutables, a la vez que no es permitido modificarlos.
End of explanation
"""


print(pangrama.upper())

"""
Explanation: Adicionalmente tienen muchos métodos adicionales (que pueden ver en la referencia oficial), como por ejemplo upper()
End of explanation
"""


numeros_pares = {0, 2, 4, 6, 8, 10, 12, 14, 16, 18} #La creación de conjuntos es con llaves o con la funcion set()
print ("numeros_pares:", numeros_pares)
multiplos_de_tres = set(range(0, 20, 3)) #La función set puede recibir otros iterables o secuencias
print ("multiplos_de_tres:", multiplos_de_tres, "\n")

print("Intersección:", numeros_pares & multiplos_de_tres)
print("Union:", numeros_pares | multiplos_de_tres)
print("Diferencia:", numeros_pares - multiplos_de_tres)

"""
Explanation: Sets o Conjuntos
Los sets o conjuntos son colecciones de elementos sin orden particular. Los usos comunes de los sets es realizar operaciones de verificación de pertenencia, intersecciones, uniones y diferencias.
End of explanation
"""


semana = ["Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado", "Domingo"]
semana *= 3
print("Lista semana: ", semana, "\n")

semana = set(semana)
print("Set semana:", semana)

"""
Explanation: Adicionalmente, dentro de un set no pueden existir elementos duplicados. Transformar una lista o tupla en set remueve los duplicados, lo cual a menudo resulta muy útil.
End of explanation
"""


#Todas estas maneras de crear diccionarios son equivalentes, definiendo la capital de tres países
capitales_1 = dict(Peru = 'Lima', Ecuador = 'Quito', Argentina = 'Buenos Aires')
capitales_2 = {'Peru': 'Lima', 'Ecuador': 'Quito', 'Argentina': 'Buenos Aires'}
capitales_3 = dict(zip(['Peru', 'Ecuador', 'Argentina'], ['Lima', 'Quito', 'Buenos Aires']))
capitales_4 = dict([('Ecuador', 'Quito'), ('Peru', 'Lima'), ('Argentina', 'Buenos Aires')])
capitales_5 = dict({'Argentina': 'Buenos Aires', 'Peru': 'Lima', 'Ecuador': 'Quito'})
capitales_1 == capitales_2 == capitales_3 == capitales_4 == capitales_5

"""
Explanation: Diccionarios
Los diccionarios son estructuras de datos que guardan colecciones de pares de elementos. Cada par es conocido como llave/valor.

Los diccionarios pueden crearse de múltiples maneras equivalentes, cada una útil en su propio contexto:
End of explanation
"""



print("'Peru' ->", capitales_1['Peru'])
print("'Ecuador' ->", capitales_1['Ecuador'])
print("'Argentina' ->", capitales_1['Argentina'])

"""
Explanation: Los valores de los diccionarios se acceden a través de su llave usando la sintaxis
python
diccionario[llave]
End of explanation
"""


for capital in capitales_1: #En breve veremos que significa 'for'
    print(capital)

"""
Explanation: Es posible recorrer un diccionario, valor por valor, pero cabe mencionar que no se garantiza ninguna clase de orden, a diferencia de las secuencias:
End of explanation
"""


x = int(input("Por favor ingrese un entero: "))
if x < 0:
    print("El numero ingresado es negativo")
elif x == 0:
    print("El numero ingresado es cero")
else:
    print("El numero ingresado es positivo")

"""
Explanation: Estructuras de control
El verdadero poder de los lenguajes de programación imperativo se encuentra en la posibilidad de utilizar las llamadas estructuras de control, lo que nos permite escribir programas más complejos y más útiles de los que podríamos crear sin ellas.

Hasta ahora sólo hemos podido escribir programas secuenciales, donde cada estructura se ejecuta una tras de otra hasta el final del programa (salvo que se encuentre un error).
Flujo Condicional: If-Else
Mediante las palabras clave if y else podemos escribir instrucciones condicionales. Es decir, partes del programa que se ejecutarán únicamente si se cumple con cierta condición. De esta manera podemos responder a situaciones no conocidas antes de ejecutar un programa (por ejemplo: el input recibido, los valores que se lean de un archivo o base de datos, etc.)
La estructura de un condicional puede ser de tres maneras:
1. if
python
if ({expresion condicional}):
    {codigo a ejecutar si la condicion evalua a True}
    {mas codigo...}
2. if-Else
python
if ({expresion condicional}):
    {codigo a ejecutar si la expresion condicional evalua a True}
    {mas codigo...}
else:
    {codigo a ejecutar en caso contrario (si la expresion condicional evalua a False)}
    {mas codigo...}
3. Encadenar if con if-elif
La palabra reservada elif es una abreviación de else if, lo cual traducido significa en caso contrario, si, y sirve para encadenar condiciones adicionales en caso las anteriores no se hayan cumplido.
python
if ({expresion condicional 1}):
    {codigo a ejecutar si la expresion condicional 1 evalua a True}
    {mas codigo...}
elif ({expresion condicional 2}):
    {codigo a ejecutar si la expresion condicional 1 evalua a False
                        y la expresion condicional 2 evalua a True}
    {mas codigo...}
elif ({expresion condicional 3}):
    {codigo a ejecutar si la expresion condicional 1 evalua a False
                        y la expresion condicional 2 evalua a False
                        y la expresion condicional 3 evalua a True}
else:
    {codigo a ejecutar si ninguna de las expresiones condicionales anteriores evalua a True}
    {mas codigo...}
Notar el uso de indentación para delimitar el código que se ejecutará en cada caso.
End of explanation
"""


#Fibonacci
a, b = 0, 1
while b < 1000:
    print(b, end=',')
    a, b = b, a + b

"""
Explanation: Flujo Iterativo: While
Adicionalmente, podemos contar con código que repita un grupo de instrucciones una cantidad de veces, dependiendo de una condición, utilizando la palabra clave while. De esta manera, al igual que con if y else, podemos crear programas que funcionen de forma distinta dependiendo de las condiciones en las que se ejecuta el programa.
La sintaxis es similar a la de if.
python
while({expresion condicional}):
    {(a) codigo a ejecutar mientras la expresion condicional evalua a True}
    {(b) mas codigo}
{(c) codigo exterior}
El flujo del programa seguiría de la siguiente manera:
1. Se evalúa la expresión condicional.
    1. Si la expresión condicional evalua a False, el código interno no se ejecuta. En otras palabras, la próxima instrucción a ejecutar en el caso anterior será {(c) código exterior} y se habrá salido del bucle.
    2. Si la expresión condicional evalúa a True, se ejecuta el código interno (a) y (b).
2. Si se ejecutó el código interno (en el paso B.), se regresa al paso 1: evaluar la expresión condicional.
End of explanation
"""


password = ""
while True: # <- Con True como condición, el bucle se ejecutaría permanentemente
    password = input("Por favor ingrese la contraseña: ")
    if password == "secret":
        print("Gracias. Ha ingresado la contraseña correcta.")
        break # <- Pero con break podemos salir del bucle
    else:
        print("Lo sentimos, la contraseña es incorrecta - inténtelo de nuevo.")

"""
Explanation: También podemos salir de un bucle en cualquier momento utilzando la palabra reservada break. Ejecutar esta instrucción implica terminar la ejecución del bucle más cercano.
End of explanation
"""


tareas = []
while True:
    #Formateo de strings https://docs.python.org/3/library/string.html#format-string-syntax
    tarea = input("%d tareas ingresadas. Ingrese una tarea o 'exit' para terminar: " % len(tareas))
    
    if len(tarea) == 0:
        print ("Por favor ingrese una tarea")
        continue # <- Esta palabra reservada termina inmediatamente la actual iteración y continúa con la siguiente
    
    if tarea == "exit":     #
        break               # Todo este código es ignorado durante la actual iteración si
                            # anteriormente se ejecuta una instrucción continue
    tareas.append(tarea)    #

print("Su lista de tareas:")
print("\r\n".join(tareas))

"""
Explanation: Asimismo podemos utilizar la palabra reservada continue para terminar temparanamente una iteración y continuar con la siguiente
End of explanation
"""


#Añdadir número de tarea a la lista definida anteriormente
i = 1
for tarea in tareas:
    print ("%d. %s" % (i, tarea))
    i += 1

"""
Explanation: Documentación de formateo de strings
Flujo Iterativo: For
La palabra reservada for es similar a while en cuanto nos permite ejecutar código de forma iterativa. La diferencia está en que for nos permite realizar fácilmente operaciones por cada elemento de una colección iterable (listas, tuplas, sets, etc).
La sintaxis es como sigue:
python
for ({elemento} in {coleccion}):
    {(a) codigo a ejecutar por cada elemento en la coleccion}
    #Aquí se puede acceder al elemento actual bajo el nombre dado en {elemento}
Nota: Todo lo que se puede hacer con for se puede hacer con while, sin embargo, for es más apropiado para las tareas para las que se ha creado, por ser más comprensible y elegante.
End of explanation
"""


for (i, tarea) in enumerate(tareas):
    print ("%d. %s" % (i+1, tarea))

"""
Explanation: El bucle anterior puede ser resumido con el uso de la función predefinida enumerate
End of explanation
"""


#Diez primeros dígitos de la secuencia Fibonacci

a, b = 0, 1
for _ in range(10):
    print(b, end=',')
    a, b = b, a + b

"""
Explanation: Asimismo podemos utilizar for para realizar una operación una cantidad determinada de veces haciendo uso de la función predefinida range()
End of explanation
"""


#Diez primeros dígitos de la secuencia Fibonacci, o hasta encontrar un múltiplo de 7

a, b = 0, 1
for _ in range(10):
    print(b, end=',')
    if b % 7 == 0:
        break
    a, b = b, a + b

"""
Explanation: También es factible usar break y continue dentro de los bucles for.
End of explanation
"""


#Función que devuelve el elemento n de la serie Fibonacci. Tiene un solo parámetro, el número de elemento deseado.
def fib(n):
    """Escribe la serie Fibonacci hasta el número n."""
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a+b
    return a

"""
Explanation: Funciones
Python permite definir funciones, las cuales son bloques de código reutilizable (tales como las ya utilizadas print(), input(), list(), dict(), set(), range() y enumerate()).
En algunos lenguajes de programación las funciones están obligadas a devolver valores, pero en python pueden no hacerlo. Adicionalmente las funciones en Python no están obligadas a devolver un solo valor, sino que pueden devolver más de uno.
La sintaxis básica para definir una función es como sigue:
python
def nombre_de_funcion( {parametros} ):
    """Documentación del código"""
    {Codigo ejecutable}
    return {expresion}
Por ejemplo, definimos una función que devuelva el número n de la secuencia de Fibonacci.
End of explanation
"""


x = fib(20)
print("Nnúmero Fibonacci #20:", x)

"""
Explanation: Una vez definida la función podemos llamarla como sigue:
End of explanation
"""


print(b)

"""
Explanation: Es importante notar que todas las variables declaradas dentro de una función son accesibles únicamente dentro de la misma función. Por ejemplo si intentamos obtener el valor de b, obtendremos un error que indica que la variable no está definida:
End of explanation
"""


import math

def magnitud(x, y):
    x = x**2
    y = y**2
    mag = x + y #Declarando variable interna mag
    mag = math.sqrt(mag)
    return mag

mag = 0 #Declarando variable externa mag para demostrar que es una variable completamente diferente a la interna
print ("Magnitud del vector [4, 3]:", magnitud(4, 3))

#Aquí podemos notar que las variables x e y son independientes de aquellas declaradas en la función
print ("Las variables declaradas y manipuladas fuera de la función siguen teniendo sus valores originales: mag =", mag)

"""
Explanation: Esto nos permite mantener los contextos de declaración y llamada de la función separados, de tal forma de que no haya que preocuparse por el conflicto de nombres de variables en dichas situaciones. Esto se ilustra en el siguiente ejemplo, donde además se declara una función con más de una variable:
End of explanation
"""


def saludar(nombre, saludo="Hola"):
    print("{0} {1}!".format(saludo, nombre))

saludar("Kenyi") #Llamando a la función sin el parámetro saludo hace que la función se ejecute con el valor por defecto "Hola"
saludar("Kenyi", "Buenos días") #Pero si especificamos el valor, se utiliza ese en su lugar

"""
Explanation: Podemos también definir funciones con valores por defecto. De esta manera volvemos dichos parámetros opcionales, y cuando no se ingrese un valor específico durante una llamada, la función se ejecute con el valor indicado en lugar de echar un error.
End of explanation
"""


print( magnitud(x = 12, y = 9) )

#Otro beneficio de llamar funciones de esta forma es que no es necesario llamar a los argumentos en orden
saludar(saludo = "Buenos días", nombre = "Kenyi")

#Pero si se especifican parámetros con nombre, todas los parámetros subsecuentes deben ser llamados con nombre también
saludar(saludo = "Buenos días", "Kenyi")

"""
Explanation: Cuando la función tiene más de un parámetro, es posible llamar a la función nombrando específicamente a cual parámetro va cada valor, para facilitar la lectura en caso de que hayan muchos.
End of explanation
"""


#En este caso el parámetro sumandos contendrá todos los parámetros ingresados
def sumatoria(*sumandos):
    total = 0
    #sumandos es una tupla
    for elemento in sumandos:
        total += elemento
    return total

print (sumatoria(3, 5, 6))
print (sumatoria(1, 2))

"""
Explanation: También podemos definir funciones con una cantidad variable de parámetros, de la misma forma que trabaja print(), anteponiendo un asterisco * al último nombre de variable (es importante que sea el último), el cual será tratado como tupla dentro de la función.
End of explanation
"""


def cuadrado_y_cubo(n): # devuelve el cuadrado y el cubo del número ingresado
    cuadrado = n**2
    cubo = n**3
    return cuadrado, cubo

x, y = cuadrado_y_cubo(4)
print("Cuadrado:", x, "- Cubo:", y)

"""
Explanation: La siguiente función devuelve dos valores:
End of explanation
"""


def fib(n):    # escribir los primeros n elementos de la serie Fibonacci
    """Escribe la serie Fibonacci hasta el número n."""
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a+b
        print(a, end=' ')
    print()

# Ahora podemos llamar la función que acabamos de definir:
fib(20)
#0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181

"""
Explanation: Podemos también definir funciones que no devuelvan un valor (Nota: Estrictamente hablando, las funciones sin valor de retorno especificado devuelven None).
End of explanation
"""


def mutabilidad(entero, lista):
    entero += 10
    lista.append("Hola")
    lista.append("Mundo")

entero = 0
lista = []

mutabilidad(entero, lista)

print(entero) #Entero sigue conteniendo el valor 0, y no 10
print(lista) #Lista ahora tiene 2 nuevos elementos, añadidos dentro de la función

"""
Explanation: Una última nota, si se modifica un parámetro de tipo mutable dentro de una función, se modificará tambien fuera de ella. No así con las variables de tipo inmutable:
End of explanation
"""


#En este caso, la variable al_cuadrado contendrá una función, y podrá ser llamada posteriormente como tal
al_cuadrado = lambda x: x**2

print(al_cuadrado(4))
print(al_cuadrado(10))

"""
Explanation: Lambdas
Adicionalmente se pueden definir funciones anónimas cortas haciendo uso de la sintaxis lambda:
python
lambda {variables} : {expresion de retorno}
Cuyo equivalente declarativo sería:
python
def funcion_anonima ( {variables} ): 
    return {expresion de retorno}
Por ejemplo, consideremos esta función que duplica el número ingresado:
End of explanation
"""


semana_mayusculas = []
for i in semana:
    semana_mayusculas.append(i.upper())
    
print(semana_mayusculas)

"""
Explanation: Map, Filter, Reduce
Python nos ofrece tres funciones muy útiles para manejar secuencias de datos de forma concisa y legible: map(), filter() y reduce(). Estas funciones forman parte del paradigma de programación funcional que python contiene. Usar estas funciones puede ser complicado al principio, pero funciones de este tipo son utilizadas ampliamente en entornos de alta paralelización, especialmente en manejos de grandes cantidades de datos.
Map
La función map() aplica una función a todos los elementos de una colección de datos iterables (por ejemplo listas, tuplas, sets, diccionarios), y devuelve una lista con todos los valores modificados.
python
map({funcion_a_aplicar}, {coleccion_iterable})
Por ejemplo si deseamos volver mayúsculas todas las cadenas de caracteres en una lista, podríamos utilizar un bucle for:
End of explanation
"""


semana_mayusculas = []
semana_mayusculas = map(str.upper, semana)
print (list(semana_mayusculas))

"""
Explanation: O podríamos utilizar map.
End of explanation
"""


enteros = [1, 2, 3, 4, 5]
cuadrados = map(lambda x: x**2, enteros)
print(list(cuadrados))

"""
Explanation: Comúnmente también veremos map utilizado con lambdas.
End of explanation
"""


lista = [3, -5, -6, 1, 2, -9, 7, -2]

lista_filtrada = filter(lambda x: x <= 0, lista) 
#La función lambda devuelve True si es que el número en cuestión es menor o igual a 0

print(list(lista_filtrada))

"""
Explanation: Filter
La función filter() permite, como indica su nombre, filtrar elementos de una colección iterable que no cumplan con determinada condición.
python
filter({funcion_condicional}, {coleccion_iterable})
La funcion_condicional debe ser una función que devuelva True, en caso el elemento cumpla con la condición, o False, en caso contrario.
Por ejemplo, si deseamos filtrar los números positivos de una lista.
End of explanation
"""


from functools import reduce
lista = [47, 11, 42, 13]
sumatoria = reduce(lambda x, y: x + y, lista)

print(sumatoria)

"""
Explanation: Reduce
La función reduce() es un poco más complicada, pero extremadamente útil. Reduce aplica una función a dos elementos de una colección iterable, y luego aplica la misma función al resultado del cálculo anterior con el siguiente elemento, y así sucesivamente hasta terminar.
La sintaxis es muy similar a map() y filter():
python
reduce({funcion_a_aplicar(x, y)}, {coleccion_iterable})
Por ejemplo, la función reduce() con una función de suma y una lista de enteros:
End of explanation
"""


#Styling del notebook
from IPython.core.display import HTML
def css_styling():
    styles = open("./styles/custom.css", "r").read()
    return HTML(styles)
css_styling()

"""
Explanation: Internamente el proceso que está ocurriendo es como lo muestra el gráfico:

Mayores Recursos
Aún falta mucho por ver en Python, por ejemplo programación orientada a objetos, librerías estándar de python, creación de librerías, manipulación de texto y archivos, reflectividad, etc.
Para referencia futura, no hay lugar con información más exhaustiva que la misma documentación de Python:
https://docs.python.org/3/index.html
End of explanation
"""


