summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEkaitz Zarraga <ekaitz@elenq.tech>2019-11-19 16:07:14 +0100
committerEkaitz Zarraga <ekaitz@elenq.tech>2019-11-19 16:08:56 +0100
commitef1779490b877405eff362b4e163f5bf64ebe1b1 (patch)
treedd9c163d773ac44e6e60a748e0f0c4a3b3f8c58b
parentd12f3c17647f9a473d45efceac759dccba50e8d4 (diff)
add chapter 2: data
-rw-r--r--src/02_datos.md587
1 files changed, 587 insertions, 0 deletions
diff --git a/src/02_datos.md b/src/02_datos.md
new file mode 100644
index 0000000..3a35bea
--- /dev/null
+++ b/src/02_datos.md
@@ -0,0 +1,587 @@
+# Trabajando con datos
+
+Programar es, principalmente, tratar con datos y los datos no son más que
+piezas de información que se estructura de una forma concreta.
+
+## Nombrando datos
+
+Aunque no lo hemos mencionado, los datos se almacenan en la memoria principal
+de la computadora. Puedes imaginarte la memoria como un conjunto de cajas
+identificadas con direcciones, como si fuesen los buzones de un portal con
+muchos pisos. Para utilizar datos, éstos se guardan en los diferentes cajones y
+se van extrayendo y actualizando. La importancia de nombrar las cosas es
+evidente, si no guardamos un listado de dónde hemos guardado qué no podemos
+recuperar los datos que hemos creado.
+
+Al igual que en las matemáticas se utilizan los símbolos para representar
+posibles valores y simplificar los procesos, en los lenguajes de programación
+se utilizan símbolos para definir referencias a los datos y así poder referirse
+a los mismos datos por su nombre sin tener que introducir su contenido
+constantemente. Los lenguajes de programación aportan al programador la
+facilidad de poder crear sus propios nombres para no tener que usar las
+direcciones propias de la memoria, que normalmente son número sin más
+significado que su posición, la mayor parte de las veces permitiendo al
+programador abstraerse de estos detalles internos.
+
+En python existe un operador para declarar símbolos que hacen referencia a un
+valor: el operador `=`. Este operador enlaza el valor de la derecha con el
+nombre de la izquierda. Con un ejemplo es más fácil de comprender. Probando en
+la REPL:
+
+```
+>>> a = 10
+>>> a
+10
+>>> a + 1
+11
+```
+
+Como ves, en el primer paso se asocia el valor `10` al identificador `a` y más
+adelante se puede utilizar `a` para hacer referencia al valor enlazado.
+
+Las referencias han de ser declaradas antes de usarse, si no, el intérprete no
+las conoce y lanza una excepción:
+
+```
+>>> b
+Traceback (most recent call last):
+ File "<pyshell#6>", line 1, in <module>
+ b
+NameError: name 'b' is not defined
+```
+
+### Todo es una referencia
+
+El comportamiento de estas referencias puede no ser intuitivo si vienes de
+otros lenguajes. El operador `=` enlaza, como se dice anteriormente, un nombre
+a un valor concreto, pero eso no significa que copie su contenido. En python
+los valores y los nombres son conceptos independientes. Los valores ocupan su
+lugar en la memoria y los nombres hacen referencia a dónde se encuentran estos
+valores. Es muy común tener muchas referencias a la misma estructura de datos y
+transformar su contenido desde todas las referencias.
+
+A nivel técnico, lo que en python se conoce como variable y en este documento
+hemos hecho el esfuerzo de llamar referencia es lo que en otros lenguajes se
+conoce como un puntero y la labor del operador `=` es la de asignar el puntero
+a la dirección donde se encuentran los datos a los que debe apuntar.
+
+Volviendo a la metáfora de los buzones, en lenguajes de más bajo nivel como C
+estás obligado a seleccionar qué buzón vas a utilizar para introducir cada
+dato. Por lo que si cambia el tipo de dato a gestionar, puede que el buzón se
+quede pequeño o que los datos se interpreten de forma incorrecta. Sin embargo,
+python guarda las referencias de forma independiente a los valores y adecua el
+número de buzones en uso al tamaño de los datos de los que dispones
+reordenando, si es necesario, la estructura completa de valores y referencias.
+A este concepto se le conoce como gestión automática de memoria (*automatic
+memory management*).
+
+En resumen, las referencias en python son únicamente un recordatorio que sirve
+para poder acceder a un valor. Esto cobra importancia más adelante, y no vamos
+a rehuir la responsabilidad de enfangarnos en ello.
+
+## Tipos
+
+Tal y como aclaraba el texto de la introducción, python tiene un sistema de
+tipos dinámico (*dynamic type system*). Lo que significa que gestiona los tipos
+de forma automática, permitiendo a los nombres hacer referencia a diferentes
+tipos de valor durante la ejecución del programa a diferencia de otros
+lenguajes como, por ejemplo, C, donde el tipo de las variables debe ser
+declarado de antemano y no puede cambiarse.
+
+Esto es posible debido al fenómeno explicado en el apartado anterior por un
+lado, y, por el otro, a que los datos de python son un poco más complejos que
+en otros lenguajes y guardan una pequeña nota que indica cómo deben ser
+interpretados.
+
+Si en algún momento se le pide a python que asigne un valor de un tipo distinto
+al que una referencia tenía no habrá problemas porque es el propio dato quien
+guarda la información suficiente para saber cómo entenderlo. Las referencias
+sólo almacenan dónde se guarda este dato.
+
+> NOTA: Seguramente te habrás dado cuenta de que el funcionamiento de python es
+> más ineficiente que el de C o lenguajes similares pero mucho más flexible. Y
+> así es. A la hora de elegir el lenguaje debemos valorar cuál nos interesa
+> más para la labor que vamos a realizar.
+
+### Tipos simples
+
+Hemos denominado tipos simples a los que no empaquetan más de un valor
+internamente. En otros lenguajes o contextos se les conoce como escalares.
+
+
+#### La nada
+
+La nada en python se representa con el valor `None` y es útil en innumerables
+ocasiones.
+
+#### Boolean
+
+Los valores booleanos expresan *verdad* (`True`) o *mentira* (`False`) y sirven
+para gestionar lógica desde esos términos. Mas adelante los veremos en acción.
+
+#### Integer
+
+Los Integer, números enteros en inglés, ya han aparecido anteriormente. Para
+usar un número entero puedes introducirlo tal cual. Recuerda que hay enteros
+positivos y negativos. El símbolo para marcar números negativos en python es el
+`-`, que coincide con el operador de la resta.
+
+```
+>>> 14
+14
+>>> -900
+-900
+>>>
+```
+
+Los números enteros también pueden expresarse en otras bases, como en
+hexadecimal. Dependiendo de la aplicación en la que te encuentres igual
+necesitas mirarlo más en detalle:
+
+```
+>>> 0x10
+16
+```
+
+#### Float
+
+Los números Float, o de coma flotante, son números no-enteros. Ten cuidado
+con ellos porque la coma flotante es peculiar.
+
+El nombre coma flotante viene de que la coma no siempre se mantiene con la
+misma precisión. En realidad estos números se guardan como los números en
+notación científica ($2.997e8 m/s$ para la velocidad de la luz, por ejemplo).
+La notación científica siempre implica tener una coma, pero cuya posición se
+varía con el exponente posterior.
+
+El modo de almacenamiento de los números de coma flotante es muy similar a la
+notación científica: se almacenan dos valores de tamaño fijo, la *mantisa* y el
+*exponente* para, de este modo, poder ajustar la precisión en función del
+tamaño del número almacenado. No es lo mismo expresar la velocidad de la luz,
+$2.997e8 m/s$, que la longitud de onda del color rojo, $6,250e-7 m$, ambos
+valores tienen un tamaño muy distinto, pero usando dos *mantisas* de tamaño
+similar, cuatro dígitos (2997 y 6250), y dos exponentes de tamaño similar, un
+dígito (8 y -7), hemos expresado valores muy diferentes, ambos con la misma
+precisión relativa.
+
+El problema viene cuando nos apetece mezclarlos, por ejemplo, sumándolos.
+Imagina que tienes dos valores de esas dimensiones, uno de millones y otro de
+millonésimas partes de la unidad, y los quieres sumar entre ellos. Si sólo
+tienes una mantisa limitada para representarlos, la suma resultará en el
+redondeo de ambos a la precisión que tienes disponible. Es decir: el resultado
+será el valor grande y el pequeño se perderá en el redondeo.
+
+Aunque en este caso la suma es precisa, si tratas con un billón de valores
+pequeños y uno grande y quieres obtener la suma de todos y los sumas en parejas
+siempre con el grande, en cada suma se descartará el valor pequeño en el
+redondeo y el resultado total será el valor grande que tenías. Sin embargo, si
+sumas los valores de tamaño similar entre ellos primero, obtendrás un valor
+suficientemente grande como para alterar el resultado de la suma contra el
+valor grande al final, dando así un resultado distinto. Te recomiendo entonces,
+que si te encuentras en una situación como ésta tengas cuidado y, por ejemplo,
+ordenes los números de menor a mayor antes de sumarlos, para obtener una suma
+de buena precisión.
+
+En realidad, el exponente en el caso de python (y en casi todos los demás
+lenguajes) no está elevando un 10 a la enésima potencia, si no que lo hace con
+un 2. Por lo que lo expresado anteriormente es un poco distinto. Esto provoca
+que algunos números de coma flotante no sean tan redondos como deberían (por
+ejemplo, `2.999999999`, cuando debería ser `3.0`) y compararlos entre ellos
+puede ser desastroso. Para evitarlo, te recomiendo que *siempre* redondees los
+valores a una precisión que puedas controlar.
+
+Aunque realmente es algo más complejo[^1], lo que sabes ahora te evitará
+problemas en el futuro, sobre todo cuando analices datos, uno de los sectores
+donde python se usa de forma extensiva.
+
+[^1]: https://en.wikipedia.org/wiki/Floating-point_arithmetic
+
+Declarar números de coma flotante es natural porque usa una sintaxis a la que
+estamos acostumbrados:
+
+```
+>>> 1E10
+10000000000.0
+>>> 1.0
+1.0
+>>> 0.2E10
+2000000000.0
+>>>
+```
+
+
+#### Complex
+
+Python soporta números complejos y los expresa utilizando la letra `j`. Como
+suponen un caso bastante concreto no los analizaremos en detalle. Pero tienes
+disponible la documentación de python para lo que quieras.
+
+```
+>>> 1-23j
+(1-23j)
+```
+
+#### String
+
+Un String es una cadena de caracteres. Los Strings en python son, a diferencia
+de en otros lenguajes, un escalar, al contrario de lo que la primera definición
+que hemos expresado puede hacernos pensar. En python los Strings no son un
+conjunto de caracteres alfanuméricos sueltos que se comportan como un valor, es
+al revés. El concepto de carácter no existe y ha de expresarse con un String de
+longitud 1.
+
+Los strings se expresan rodeando texto con comillas dobles, `"`, o simples `'`
+(el símbolo del apóstrofe).
+
+```
+>>> "Hola"
+'Hola'
+>>> 'Hola'
+'Hola'
+```
+
+El hecho de que haya dos opciones para delimitar los strings facilita el
+etiquetado como string de valores que contienen las propias comillas en su
+contenido. También puede utilizarse la contrabarra `\` para cancelar la acción
+de las comillas.
+
+
+```
+>>> "Tiene un apóstrofe: Luke's"
+"Tiene un apóstrofe: Luke's"
+>>> 'Tiene un apóstrofe: Luke's'
+SyntaxError: invalid syntax
+>>> 'Tiene un apóstrofe: Luke\'s'
+"Tiene un apóstrofe: Luke's"
+```
+
+> NOTA: la contrabarra sirve para introducir caracteres especiales o caracteres
+> de escape[^2]: `\n` salto de línea, `\t` tabulador, etc. Que son una herencia
+> de los tiempos de las máquinas de escribir, pero son aún útiles y muy usados.
+> Para expresar la propia contrabarra ha de escaparse a sí misma con otra
+> contrabarra para que no evalúe el siguiente caracter como un caracter de
+> escape.: `\\`.
+
+[^2]: https://en.wikipedia.org/wiki/Escape_character
+
+
+### Tipos compuestos
+
+Hemos denominado tipos compuestos a los que pueden incluir diferentes
+combinaciones de tipos simples. Estos tipos dejan de ser escalares y se
+comportan como vectores o conjuntos de datos. Estos tipos de dato pueden
+contenerse a sí mismos, por lo que pueden crear estructuras anidadas complejas.
+
+#### Tuple
+
+Las tuplas a *tuple* en inglés son el tipo compuesto más sencillo en python.
+Las tuplas definen un conjunto de valores de cualquier tipo.
+
+Se declaran utilizando paréntesis añadiendo sus elementos separados por comas.
+Y se accede a sus contenidos utilizando los corchetes e introduciendo el índice
+del elemento que se quiere extraer.
+
+```
+>>> (2, 3, "HOLA")
+(2, 3, 'HOLA')
+>>> (2, 3, "HOLA")[0]
+2
+```
+En python los índices comienzan en `0`.
+
+#### List
+
+Las listas o *list* son muy similares a las tuplas, pero son algo más complejas
+porque pueden alterarse así mismas. A diferencia de todos los tipos que hemos
+visto hasta ahora, tanto las listas como los diccionarios que veremos a
+continuación son inmutables. Esto significa que no puede transformarse su
+valor, más adelante trataremos esto en detalle.
+
+De momento, recuerda que las listas se construyen de forma similar a las
+tuplas, pero utilizando corchetes en lugar de paréntesis. La forma de acceder a
+los índices es idéntica.
+
+```
+>>> [2, 3, "HOLA"]
+[2, 3, 'HOLA']
+>>> [2, 3, "HOLA"][0]
+2
+```
+
+#### Dictionary
+
+Los diccionarios o *dictionary* son un tipo de dato similar a los dos
+anteriores, pero que en lugar de utilizar índices basados en la posición de sus
+elementos usan claves arbitrarias definidas por el usuario.
+
+Además, los diccionarios no están ordenados así que no se puede suponer que las
+claves siempre van a estar en el orden en el que se introducen.
+
+Para declarar diccionarios es necesario indicar qué claves se quieren usar. Las
+claves son de tipo string. En el caso de los diccionarios, además, se utilizan
+llaves para definirlos. El acceso a sus valores se realiza con los corchetes,
+del mismo modo que en las listas, pero es necesario seleccionar la clave para
+acceder.
+
+Los diccionarios, al igual que las listas, son mutables. Como veremos en
+seguida.
+
+```
+>>> {"nombre": "Guido", "apellido": "Van Rossum", "popularidad": 8.0}
+{'nombre': 'Guido', 'apellido': 'Van Rossum', 'popularidad': 8.0}
+>>> {"nombre": "Guido", "apellido": "Van Rossum", "popularidad": 8.0}["popularidad"]
+8.0
+```
+
+## Conversión
+
+Ahora que conoces los valores sé que quieres saber cómo cambiar de uno a otro.
+Cómo leer un Integer desde un String etc. Python tiene funciones para construir
+sus diferentes tipos de datos a partir de los diferentes inputs posibles.
+
+Aunque aún no sabes ejecutar funciones te adelanto como se hace con algunos
+ejemplos:
+
+```
+>>> bool(1)
+True
+>>> bool(0)
+False
+>>> int("hola")
+Traceback (most recent call last):
+ File "<pyshell#27>", line 1, in <module>
+ int("hola")
+ValueError: invalid literal for int() with base 10: 'hola'
+>>> int("10")
+10
+>>> float(10)
+10.0
+>>> complex(19)
+(19+0j)
+>>> str(10)
+'10'
+>>> tuple([1,2,3])
+(1, 2, 3)
+>>> list((1,2,3))
+[1, 2, 3]
+>>> dict((("a", 1),("b", 2)))
+{'a': 1, 'b': 2}
+```
+
+Los propios nombres de las funciones son bastante representativos de a qué tipo
+convierten. Si quieres saber más puedes ejecutar `help(nombre)` y ver qué te
+cuenta la ayuda.
+
+## Operadores
+
+Ahora que sabes el contexto en el que vas a jugar, necesitas poder alterar los
+datos.
+
+Existen operadores básicos que te permiten transformar los datos, algunos ya
+los has visto antes, el operador `=`, que sirve para nombrar cosas, la suma
+(`+`) o la resta (`-`), pero hay otros.
+
+```
+>>> 1 + 1
+2
+>>> 10 - 9
+1
+>>> 10 ** 2
+100
+>>>
+```
+
+Los siguientes apartados muestran algunos operadores que es interesante
+memorizar.
+
+
+### Pruebas de verdad
+
+Las pruebas de verdad generan un valor booleano desde una pareja de valores. A
+continuación una lista de las pruebas de verdad con unos ejemplos:
+
+| operator | meaning |
+|---|---|
+| < | strictly less than |
+| <= | less than or equal |
+| > | strictly greater than |
+| >= | greater than or equal |
+| == | equal |
+| != | not equal |
+| is | object identity |
+| is not | negated object identity |
+| in | containment test |
+| not in | negated containment test |
+
+```
+>>> 1 > 1
+False
+>>> 1 >= 1
+True
+>>> 1 not in (0, 2, 3)
+True
+```
+
+### Operadores lógicos
+
+Los operadores lógicos mezclan booleanos y son muy importantes, sobre todo para
+combinar las pruebas de verdad.
+
+| operator | meaning |
+|---|---|
+| and | logical and, returns True if both True, False otherwise |
+| or | logical or, returns True if any True, False otherwise |
+| not | logical not, returns True if False, True otherwise |
+
+La mayor parte de los operadores son binarios (como la suma), necesitan dos
+valores y devuelven otro, pero existe al menos una excepción que funciona con
+un único valor. El operador `not` sirve para darle la vuelta a un Booleano.
+
+```
+>>> not True
+False
+>>> True and True
+True
+>>> False and True
+False
+>>> False or True
+True
+>>> 1 > 0 and 2 > 1
+True
+```
+
+### Matemáticos
+
+Los operadores matemáticos transforman números entre ellos. Casi todos son
+conocidos por su operación matemática.
+
+| operator | meaning |
+|---|---|
+| + | addition |
+| - | subtraction or negative |
+| `*` | multiplication |
+| / | division |
+| `**` | power |
+| % | remainder |
+
+### Operación en función del tipo
+
+Python simplifica muchas tareas transformando el comportamiento de los
+operadores en función del tipo de dato sobre el que trabajan. Los operadores
+matemáticos están preparados para trabajar sobre números (de cualquier tipo)
+pero la verdad es que algunos pueden ejecutarse sobre otros formatos. Por
+ejemplo:
+
+```
+>>> "a" + "a"
+'aa'
+```
+
+Esto se debe a que la funcionalidad del operador `+` ha sido diseñada para
+operar de forma especial en Strings, haciendo una concatenación.
+
+En el futuro, cuando aprendas a diseñar tus propios tipos podrás hacer que los
+operadores les afecten de forma especial, tal y como pasa aquí.
+
+### Precedencia
+
+La precedencia en python es muy similar a la matemática y usa las mismas reglas
+para marcarla de forma explícita. Recuerda, en matemáticas se utilizan los
+paréntesis para esto.
+
+Los operadores siempre trabajan con sus correspondientes valores y python los
+resuelve de forma ordenada. Si generas una operación muy compleja, python la
+irá desgranando paso a paso y resolviendo las parejas una a una, cuanto más te
+acostumbres a hacerlo en tu mente menos errores cometerás.
+
+```
+>>> 8 + 7 * 10 == (8 + 7) * 10
+False
+>>> 8 + 7 * 10
+78
+>>> (8 + 7) * 10
+150
+>>> 78 == 150
+False
+```
+
+## Mutabilidad
+
+Ya adelantamos que el operador `=` sirve para nombrar cosas. Ahora vamos a
+combinar esa propiedad con la mutabilidad, o la propiedad de las cosas de
+alterarse a sí mismas. Empecemos con un ejemplo:
+
+```
+>>> a = 10
+>>> b = a + 10
+>>> b
+20
+>>> a = b
+>>> a
+20
+```
+
+En este ejemplo hacemos que `a` haga referencia al valor 10, y después creamos
+`b` a partir de `a` y otro 10. Gracias a la precedencia, primero se resuelve el
+lado derecho completo obteniendo un 20 y después se asigna la referencia `b` a
+ese valor.
+
+Si queremos, después podemos reasignar el símbolo `a` a otro valor nuevo, en
+este caso al que hacer referencia `b` que es 20.
+
+En este primer ejemplo, ningún valor está siendo alterado, si te fijas, sólo
+estamos creando nuevos valores y cambiando las referencias a éstos.
+
+Con los datos mutables podemos alterar los valores. Lo vemos con otro ejemplo:
+
+```
+>>> a = {}
+>>> b = a
+>>> a["cambio"] = "hola!"
+>>> b
+{'cambio': 'hola!'}
+```
+
+Primero creamos un diccionario vacío y le asignamos la referencia `a`. Después
+le asignamos la referencia `b` a quien referenciaba `a`, es decir, al
+diccionario vacío. Ambas referencias apuntan al mismo dato. Si después usamos
+alguna alteración en el diccionario `a` como asignarle una nueva clave, `b`,
+que hace referencia al mismo diccionario, también ve los cambios. Esto mismo
+podría hacerse si se tratara de listas, ya que tienen la capacidad de alterarse
+a sí mismas, pero nunca podría hacerse en tuplas, porque son inmutables.
+
+> NOTA: Los diccionarios y las listas soportan un montón de funciones y
+> alteraciones, no se mencionan en este apartado porque nunca terminaría. Se
+> dejan para el futuro y para los ejemplos que se verán durante el documento.
+
+Si intentásemos un ejemplo similar en tupla, no nos dejaría:
+
+```
+>>> a = ()
+>>> b = a
+>>> a[0] = 1
+Traceback (most recent call last):
+ File "<pyshell#67>", line 1, in <module>
+ a[0] = 1
+TypeError: 'tuple' object does not support item assignment
+```
+
+Ten cuidado cuando trates con elementos mutables, sobre todo si tienen muchas
+referencias, porque puede que estés alterando los valores en lugares que no te
+interesa. Para evitar este tipo de problemas, puedes generar copias de los
+objetos, pero el proceso es poco eficiente y tedioso.
+
+En este segundo caso, creamos una copia de `a` para que `b` sea independiente
+de los cambios que ocurran en ésta. Aquí ya no estamos haciendo referencia
+desde `b` a los datos que había en `a`, sino a una copia de éstos, almacenada
+en otro lugar.
+
+```
+>>> a = {}
+>>> b = dict(a)
+>>> a["cambio"] = "hola!"
+>>> b
+{}
+>>> a
+{'cambio': 'hola!'}
+```