<jupyter_start><jupyter_text>--- Universidad de Costa Rica IE0405 - Modelos Probabilísticos de Señales y Sistemas--- `Py0` - *Introducción*> Python es un lenguaje de programación de uso general, en la actualidad el más popular para aplicaciones web y cálculo científico. Su sintaxis fue pensada desde el inicio para ser más legible.------  InstalaciónPara este curso vamos a utilizar las herramientas provistas con la instalación de [Anaconda](https://www.anaconda.com/) en cualquier plataforma (Linux, Windows, macOS). Una vez instalado, se pueden "correr" *scripts* `.py` desde terminal:* `$ python script.py`o usarlo en modo interactivo:* `$ python`* `>>> print('Hola')`o desde cualquiera de los muchos **IDE** (entornos de desarrollo integrado, *Integrated Development Environments*) disponibles, como: Eclipse, PyCharm, Sublime, Visual Studio, Atom... Más informaciónLa información más precisa sobre los aspectos básicos del lenguaje Python están en el [manual de referencia](https://docs.python.org/3/library/) de la Librería Estándar de Python. Sin embargo, es posible encontrar muchas otras buenas referencias en internet, desde cursos en línea, páginas de referencia (ver final del documento), preguntas en foros y hasta el muy buen [Wikibook de Python](https://en.wikibooks.org/wiki/Python_Programming). Antes de empezar...**Nota 0**: Para ejecutar una celda de código en Jupyter se utilizan las teclas `shift` + `enter`, o "Run" en el panel superior cuando la celda está seleccionada.**Nota 1**: La función `print()` muestra el resultado de la evaluación de su(s) argumento(s).**Nota 2**: Los comentarios en el código fuente de Python se hacen con `` en una sola línea o con `''' (comentario) '''` en varias líneas.**Nota 3**: En Python el índice comienza en 0.**Nota 4**: La forma "pitónica" de programar en Python (*the Pythonic way*) son convenciones que hacen el código más legible y sencillo. --- 0.1 - VariablesEn Python, la asignación de un dato a una variable no requiere la indicación explícita del *tipo de dato*. Por tanto, basta con escribir:<jupyter_code>numero = 12
caracteres = 'hola'
a, b, c = 1, 2, 3
print(numero, caracteres, a + b + c)<jupyter_output>12 hola 6<jupyter_text>--- 0.2 - Valores booleanos y comparacionesLos dos valores lógicos en Python son `True` o `1` y `False` o `0`, sobre los cuales se aplican las operaciones lógicas `or`, `and` y `not`.<jupyter_code>a = True and not False
b = 0 or 0 or not 0
print(a, b)<jupyter_output><empty_output><jupyter_text>Comparaciones entre númerosLa evaluación de las comparaciones retorna `True` o `False`.| Operación   | Significado                 ||-------------|-----------------------------||     `<`     | es estrictamente menor que  ||     `<=`    | es menor o igual que        ||     `>`     | es estrictamente mayor que  ||     `>=`    | es mayor o igual que        ||     `==`    | es igual a                  ||     `!=`    | no es igual a               ||     `is`    | identidad                   ||   `is not`  | identidad negada            |**Nota 1**: `=` es de asignación, `==` es de comparación.$$e^x = $$**Nota 2**: las operaciones pueden concatenarse, por ejemplo: `x < y < z`<jupyter_code>print(12 < 24)
print(2019 != 2020)
print(34 >= 21 > 13 > 8)<jupyter_output><empty_output><jupyter_text>--- 0.3 - Tipos de datos numéricos y sus operadoresEn Python hay tres tipos distintos de datos numéricos:* `int`: número entero, positivo o negativo* `float`: número de punto flotante (decimal) positivo o negativo * `complex`: números con una "parte real" y una "parte imaginaria" de punto flotante**Nota 1**: Las funciones respectivas `int()`, `float()` y `complex(a, b)` permiten convertir de un tipo a otro.**Nota 2**: La función `type()` permite corroborar el tipo de dato numérico o de cualquier dato de una variable en Python.<jupyter_code>a = 2
print(a, 'es', type(a))

b = 3.0
print(b, 'es', type(b))

c = 5 + 8j
print(c, 'es', type(c))

d = int(b)
e = float(a)
f = complex(d, e)
print(d, e, f)<jupyter_output><empty_output><jupyter_text>Operaciones básicas sobre datos numéricos| Operación         | Resultado                         ||-------------------|-----------------------------------|| `x + y`           | suma de `x` y `y`                 | | `x - y`           | resta de `x` y `y`                || `x * y`           | producto de `x` y `y`             || `x / y`           | cociente de `x` y `y`             || `x // y`          | piso del cociente de `x` y `y`    || `x % y`           | residuo de `x / y`                || `-x`              | `x` negativo                      || `+x`              | `x` sin cambio de signo           || `abs(x)`          | magnitud de `x`                   || `int(x)`          | `x` convertido a entero           || `float(x)`        | `x` convertido a punto flotante   || `complex(re, im)` | número complejo                   || `c.conjugate()`   | conjugado del número complejo `c` || `divmod(x, y)`    | el par `(x // y, x % y)`          || `pow(x, y)`       | `x` a la potencia de `y`          || `x ** y`          | `x` a la potencia de `y`          |<jupyter_code>print(10/3)
print(10//3)
print(10%3)

a, b = divmod(10,3)
print(a, b, 3 * a + b)<jupyter_output><empty_output><jupyter_text>--- 0.4 - Números binarios, octales, hexadecimales y sus operacionesPara declarar un número **binario** se inicia con `0b`, para declarar un número **octal** se inicia con `0o`, para declarar un número **hexadecimal** se inicia con `0x`. Para convertir entre un tipo y otro y también `int` se utilizan las funciones `bin()`, `oct()`, `hex()` e `int()`.<jupyter_code>a = 42
b = 0b11111100100
c = 0o31

print(bin(a))
print(hex(a))
print(int(b))
print(int(c))<jupyter_output><empty_output><jupyter_text>Operaciones binarias (bit a bit) sobre números enteros| Operación   | Resultado                       ||-------------|---------------------------------|| `x \| y`    | **OR** de `x` y `y`                 || `x ^ y`     | **XOR** de `x` y `y`                || `x & y`     | **AND** de `x` y `y`                || `x << n`    | Desplazamiento izquierdo de `x` en `n` bits        || `x >> n`    | Desplazamiento derecho de `x` en `n` bits || `~x`        | **NOT** (inversión) de `x`          |<jupyter_code>a = 0b10101 & 0b01010
b = ~a
c = 13 | 21
d = 0x4a << 2
print(a, b, c, d)<jupyter_output><empty_output><jupyter_text>--- 0.5 - Tipos de secuencias de datos y sus operadoresLas variables del tipo "secuencia" permiten almacenar grupos de datos. También son llamados "contenedores" (ver también `dict`, más adelante).* `list`: secuencia **mutable** (*que puede cambiar*) típicamente utilizada para almacenar elementos *homogéneos* (de un mismo tipo de datos o secuencias).* `tuple`: secuencia **inmutable** (*que **no** puede cambiar*) típicamente utilizada para almacenar elementos *heterogéneos* (de diferente tipo).* `range`: secuencia **inmutable** de **números** típicamente utilizada para iterar en un bucle.**Nota**: Es útil recordarlos como: `[list]`, `(tuple)` y `{dict}`.  Creación de listas* `[]`* `[a]`, `[a, b, c]`* `[x for x in iterable]` (*list comprehension*)* `list()` o `list(iterable)`**Nota**: Como en algunos lenguajes de computación científica (Matlab, R...), las listas pueden ser utilizadas para crear "vectores" y "matrices".<jupyter_code>L1 = ['ja']
L2 = L1 * 3
L3 = ['alfa', 'beta', 'gama']
L4 = [x for x in range(6)]
L5 = [x for x in range(6) if x % 3 != 0]
L6 = [[1,2,3],[4,5,6],[7,8,9]]

print(L1, L2, L3)
print(L4)
print(L5)
print(L6)<jupyter_output><empty_output><jupyter_text>Creación de tuplas* `()`* `a`, o `(a,)`* `a, b, c` o `(a, b, c)`* `tuple()` o `tuple(iterable)`<jupyter_code>T1 = (1,)
T2 = 1, 2, 3
T3 = ('En', 'un', 'lugar', 'de', 'La', 'Mancha')
T4 = tuple([x for x in range(3)])

print(type(T1))
print(T2, T3, T4)<jupyter_output><empty_output><jupyter_text>Creación de rangos* `range(stop)` (empieza en 0 y no incluye a `stop`)* `range(start, stop[, step])` (como en `start:step:stop`)<jupyter_code>range(1000)<jupyter_output><empty_output><jupyter_text>Operaciones sobre secuencias| Operación              | Resultado                                                                                         ||------------------------|---------------------------------------------------------------------------------------------------|| `x in s`               | `True` si un elemento de `s` es igual a `x` , de lo contrario `False`                             || `x not in s`           | `False` si un elemento de `s` es igual a `x` , de lo contrario `True`                             || `s + t`                | la concatenación de `s` y `t`                                                                     || `s * n` o `n * s`      | equivalente a agregar `s` a sí mismo `n` veces                                                    || `s[i]`                 | `i`-ésimo elemento de `s` , origen en 0                                                          || `s[i:j]`               | rebanada de `s` de `i` a `j`                                                                      || `s[i:j:k]`             | rebanada de s de `i` a `j` con el paso `k`                                                        || `len(s)`               | longitud de `s`                                                                                   || `min(s)`               | elemento más pequeño de `s`                                                                       || `max(s)`               | elemento más grande de `s`                                                                        || `s.index(x[, i[, j]])` | índice de la primera aparición de `x` en `s` (en o después del índice `i` y antes del índice `j`) || `s.count(x)`           | número total de ocurrencias de `x` en `s`                                                         |**Nota**: La forma "pitónica" de acceder al último elemento de la secuencia es con el índice `-1`, es decir, `s[-1]`. De esta forma no hay que conocer *a priori* la cantidad de elementos en `s`.<jupyter_code>inicio = 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
continuacion = 55, 89, 144, 233
secuencia = inicio + continuacion

print(secuencia)
print(secuencia[4])
print(secuencia[0:-1:2])
print(secuencia.count(1))<jupyter_output><empty_output><jupyter_text>Operaciones sobre secuencias mutables| Operación             | Resultado                                                                                 ||-----------------------|-------------------------------------------------------------------------------------------|| `s[i] = x`            | el elemento `i` de `s` se reemplaza por `x`                                               || `s[i:j] = t`          | una porción de `s` de `i` a `j` se reemplaza por el contenido de la `t` iterable          || `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]`        | elimina los elementos `s[i:j:k]` de la lista                                              || `s.append(x)`         | agrega `x` al final de la secuencia (igual que `s[len(s):len(s)] = [x]`)                 || `s.clear()`           | elimina todos los elementos de `s` (igual que `del s[:]`)                                || `s.copy()`            | crea una copia superficial de `s` (igual que `s[:]`)                                      || `s.extend(t) o s += t`| extiende `s` con el contenido de `t` (en su mayor parte igual que) `s[len(s):len(s)] = t` || `s *= n`              | actualiza `s` con su contenido repetido `n` veces                                         || `s.insert(i, x)`      | inserta `x` en `s` en el índice dado por `i` (igual que ) `s[i:i] = [x]`                  || `s.pop([i])`          | recupera el elemento en `i` y también lo elimina de `s`                                   || `s.remove(x)`         | eliminar el primer elemento de `s` donde `s[i]` es igual a `x`                            || `s.reverse()`         | invierte los elementos de `s` en su lugar                                                 |<jupyter_code>frase = ['all', 'you', 'need', 'is', 'love']
print(frase)

necesidad = 'food'
frase[-1] = necesidad
print(frase)<jupyter_output><empty_output><jupyter_text>--- 0.6 - Secuencias de texto y sus operadoresLas "cadenas" de texto o *strings* `str` son secuencias inmutables de caracteres. Creación de secuencias de texto* `'hola'` (puede tener comillas dobles adentro)* `"hola"` (puede tener comillas sencillas adentro)* `'''hola'''` u `"""hola"""` (múltiples líneas)* Con la función `str()` a partir de otro tipo de dato**Nota**: Las secuencias de texto permiten todas las operaciones aplicadas sobre secuencias, pero es inmutable entonces no admite las operaciones sobre secuencias mutables (como las sustituciones o la remoción de elementos).<jupyter_code>print('Hola "mundo"' + ", " + '''saludos.''')<jupyter_output><empty_output><jupyter_text>Algunas funciones ("métodos") sobre hileras de caracteresEs posible hacer operaciones específicas de texto sobre un `str`. La lista de métodos está disponible [aquí](https://docs.python.org/3/library/stdtypes.htmlstring-methods).* `str.capitalize()`: Regresa una copia de la cadena con su primer carácter en mayúscula y el resto en minúscula.* `str.endswith( sufijo [ , inicio [ , fin ] ] )`: Regresa `True` si la cadena termina con el sufijo especificado, de lo contrario regresa `False`.* `str.lower()`: Regresa una copia de la cadena con todos los caracteres en mayúsculas convertidos a minúsculas.  Formato de textoUn [método](https://docs.python.org/3/library/string.htmlformatstrings) importante aplicado en hileras de caracteres permite insertar valores en un texto, con ciertas especificaciones.* `str.format( *args , **kwargs )`: Realiza una operación de formateo de la cadena. La cadena para la que se llama a este método puede contener texto literal o **campos de reemplazo** delimitados por llaves `{}`. Cada campo de reemplazo contiene el índice numérico de un argumento posicional o el nombre de un argumento de palabra clave. `str.format` devuelve una copia de la cadena donde cada campo de reemplazo se reemplaza con el valor de cadena del argumento correspondiente.**Nota 1**: `*args` es un número variable de argumentos que se pasan a una función. Por ejemplo, una función `multiplicar(*args)` que multiplica todos los argumentos que se le pasan puede ser `multiplicar(3,3,2,7)` o `multiplicar(2,3)`.**Nota 2**: ``**kwargs`` es un número variable de argumentos del tipo `clave = argumento`. --- 0.7 - DiccionariosUn diccionario es un tipo especial de secuencia mutable que hace un *mapeo* de valores a una clave, del tipo `clave : valor` (usualmente en inglés como `key : value`). El nombre es una analogía con los diccionarios que tienen pares **palabra: definición**. Creación de diccionarios* Los diccionarios se pueden crear colocando una lista de pares separados por comas entre llaves `{}`.* Con el constructor `dict()`.**Nota**: El orden de los pares no importa. Si dos o más diccionarios comparten exactamente los mismos pares (aunque hayan sido creados en diferente orden) entonces son iguales.<jupyter_code># Con las llaves
a = {'uno': 1, 'dos': 2, 'tres': 3}

# Con la función dict()
b = dict(uno=1, dos=2, tres=3)

# Con la función zip() que "parea" dos listas
c = dict(zip(['uno', 'dos', 'tres'], [1, 2, 3]))

# Con una lista de tuplas (distinto orden)
d = dict([('dos', 2), ('uno', 1), ('tres', 3)])

# Creación redundante, con dict({})
e = dict({'tres': 3, 'uno': 1, 'dos': 2})

# Verificar igualdad
a == b == c == d == e<jupyter_output><empty_output><jupyter_text>Algunas funciones útiles en diccionarios* `list(d)`: Regresa una lista de todas las claves utilizadas en el diccionario `d`.* `len(d)`: Regresa el número de elementos en el diccionario `d`.* `d[key]`: Regresa el artículo de `d` con la clave `key`. Genera una clave `KeyError` si no está en el mapa (diccionario).<jupyter_code>d = {'IE0305':'Matemática Superior',
     'IE0409':'Análisis de Sistemas',
     'IE0405':'Modelos Probabilísticos de Señales y Sistemas'}

print(list(d))
print(len(d))
print(d['IE0405'])
print('MA1001' in d)<jupyter_output><empty_output><jupyter_text>--- 0.8 - Controles de flujoLos controles de flujo determinan las acciones del programa ante la evaluación de "declaraciones" tales como comparaciones. La documentación [oficial](https://docs.python.org/3/tutorial/controlflow.html) explica sus detalles. Controles de flujo `if` - `elif` - `else`Posiblemente el más importante o conocido, `if` evalúa una declaración y prosigue con líneas de ejecución distintas según el resultado. En Python su sintaxis es:```pythonif :     ```Cuando el resultado es `False` puede haber otra acción posible, y la sintaxis es:```pythonif :    else:    ```Si al evaluarse la primera declaración hay otras posibilidades entonces deben evaluarse otras declaraciones con la sintaxis:```pythonif :    elif :    else:    ```**Nota**: la *indentación* es **obligatoria** en Python, y es también suficiente para delimitar qué está contenido dentro de un curso de acción. Por ejemplo, en el siguiente fragmento, `print(a)` está dentro del `if` y `print(b)` no.```pythonif a < b:    print(a)    print(b)``` --- 0.9 - BuclesUn tipo especial de controles de flujo son los que **repiten** una acción en "bucle" o "lazo" (*loop*). Bucle `while`Repite una operación **indefinidamente** mientras una condición se cumpla.```python         while :    ``` Bucle `for`Repite una operación por **un número determinado de veces**, al iterar sobre una secuencia de datos.```python         for i in (a,b,c,d,e,f):    ```**Nota 1**: la secuencia `range()` es útil cuando se quiere iterar sobre una larga consecución de números (por ejemplo, desde 10 hasta 100).**Nota 2**: El operador `a += n` es equivalente a `a = a + n`. También existen `-=` y `*=`.<jupyter_code>i = 1
while i % 5 != 0:
    print(i)
    i += 1

for j in (8,13,21,34):
    print(j)

for k in range(5):
    print(k)<jupyter_output><empty_output><jupyter_text>Otros comandos especiales* `break`: Sale del ciclo actual.* `yield`: Continúa con la siguiente iteración del bucle.  0.10 - Algunas funciones nativasYa son conocidas algunas funciones de la [Librería Estándar de Python](https://docs.python.org/3/library/functions.html) como `print()`, `type()`, `int()`, `len()`, `list()`. Algunas otras funciones útiles de interés son:* `abs(a)`: Valor absoluto de un número.* `all()`: `True` si todos los elementos de una secuencia son `True`* `enumerate(iterable[, start=n])`: "adhiere" numeración a una secuencia iterable, empezando en `start=n` (por defecto `start=0`).* `help()`: Invoca la ayuda de un objeto (función o comando o variable).* `input([prompt])`: Habilita el ingreso de datos con un `[prompt]`.* `map(función, iterable)`: Aplica `función` sobre cada elemento de `iterable`.* `round(n[, dígitos])`: Redondea `n` con una precisión de `dígitos`* `zip(*iterables)`: Crea un nuevo iterador con la unión de tantos `*iteradores` como se agreguen. El `i`-ésimo elemento de `zip(*iterables)` es una tupla con los `i`-ésimos elementos de cada argumento iterable. --- Filosofía de Python*Tim Peters*- Bello es mejor que feo.- Explícito es mejor que implícito.- Simple es mejor que complejo.- Complejo es mejor que complicado.- Plano es mejor que anidado.- Disperso es mejor que denso.- La legibilidad importa.- Los casos especiales no son tan especiales como para quebrantar las reglas.- Lo práctico gana a lo puro.- Los errores nunca deberían dejarse pasar silenciosamente.- A menos que hayan sido silenciados explícitamente.- Frente a la ambigüedad, rechace la tentación de adivinar.- Debería haber una —y preferiblemente solo una— manera obvia de hacerlo.- Aunque esa manera puede no ser obvia al principio a menos que usted sea holandés.- Ahora es mejor que nunca.- Aunque nunca es a menudo mejor que ya mismo.- Si la implementación es difícil de explicar, es una mala idea.- Si la implementación es fácil de explicar, puede que sea una buena idea.- Los espacios de nombres (*namespaces*) son una gran idea, ¡hagamos más!**Nota**: Un *namespace* es una forma de asegurarse de que no hay nombres de variables o funciones o métodos repetidos entre el *script* y las librerías y módulos utilizados.<jupyter_code>import this<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1456998973255757, 'fraction_numerical': 0.013200997408693101, 'mean_word_length': 2.6596886741814276, 'pattern_counts': {'":': 0, '<': 78, '<?xml version=': 0, '>': 79, 'https://': 7, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 52, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '17', 'dir': 'jupyter-structured-clean-dedup', 'id': '258979', 'license': 'CC0-1.0', 'n_tokens_mistral': 6876, 'n_tokens_neox': 6638, 'n_words': 2424, 'path': '.ipynb_checkpoints/Py0-checkpoint.ipynb', 'repo_name': 'jonixrojas/mpss', 'size': '20453'}