Diarios de desarrollo

¡Hola! En esta sección podrás encontrar algunas pequeñas píldoras de desarrollos interesantes que pueden ayudarte a mejorar algunos de tus desarrollos o hacer que sean más eficientes o bonitos. ¡Porque los detalles importan!

SQL

La importancia de los datos es indubable hoy en día, dado que es necesario almacenar una gran cantidad de información. Esta información se puede guardar en nuestros dispositivos utilizando un sistema de ficheros o en servidores, mediante el almacenamiento en bases de datos. En este segundo caso, existen varios tipos, uno de ellos son las bases de datos relacionales, que agrupan los datos en forma de tablas, cada una con sus propios campos y "relacionandolas" (de aquí viene el nombre) entre sí. ¿Y cómo se usan?, pues utilizando un lenguaje conocido como SQL, el cual podemos hacer que haga muchas cosas, pero más importante aún, hay que saber cuándo y cómo.

Índices

Los índices son un componente de las bases de datos SQL que se ocupan de crear automáticamente unas estadísticas de aparición y repetición mediante agrupaciones de conjuntos; esto permite a la base de datos que cuando hagamos una consulta, por ejemplo, dame todas las personas que se llaman Maribel, o, dame todas las personas que tengan 25 años; la base de datos consultará estas estadísticas y buscará en los lugares donde exista mayor probabilidad de aparición de esos datos, resultando en búsquedas más eficientes y rápidas.

Hasta ahora todo parece un camino de rosas, pero... ¿Estamos seguros de ello?, una de las máximas de la informática es que si quieres eficiencia en algo es altamente probable que la pierdas en otro lugar, y en este caso no es una excepción, porque... ¿Y estás estadísticas dónde están? En efecto, tenemos que guardarlas en algún sitio, y ese sitio va a ser la base de datos y por tanto nos van a ocupar más espacio.

Entonces, ¿cómo decido? Pues muy fácil, si vas a tener una base de datos pequeña no te interesa, si va a ser muy grande te interesan, pero recuerda, vas a tener que tener muy claro cuáles van a ser los campos por los que más se buscan, porque si buscamos por nombre y no por edad no nos interesa ponerle un índice a la última pero si al nombre.

Referencias: Visión general y estrategias de índices SQL

CREATE TABLE Person
(NAME    VARCHAR(20) NOT NULL PRIMARY KEY, 
 AGE INTEGER,
 INDEX SHORT_DESC_IND(AGE)
);

¿Criptografía por defecto?

Para desgracia de todos nosotros, no vivimos en un mundo en el que la gente posea un código ético estricto con respecto a los demás y por supuesto su información. Por ello el mundo informático se ve constantemente obligado a seecurizar sus sistemas para impedir que personas no deseadas se hagan con la información que no deben tener. Para ello una de las principales técnicas se basa en el uso de criptografía. Es una técnica que se ocupa de convertir los datos en información cifrada y por tanto no entendible, ¿y cómo hacemos esto?, en efecto, lo has adivinado, con la ayuda de nuestras mejor amigas, las matemáticas. Utilizamos formulas matemáticas para "ocultar" la información y mediante claves poder revertirla a lo que nos interesa conocer.

Las bases de datos SQL tienen la posibilidad de encriptarse por defecto, lo cual nos da grandes ventajas, por ejemplo, tendremos la seguridad de que todos nuestros datos estén encriptados incluso en el caso de que alguien pueda mirar en la base de datos, solo verá cosas sin sentido. Pero... (porque en la informática siempre hay peros), la base de datos va a tener incluso más problemas de rendimiento, porque ahora va a tener que que realizar procesamiento adicional para poder revelar los datos, además de que tendremos que ocultar las claves para que no las vea cualquiera y tiremos por tierra todo porque sea capaz de ver el contenido. No solo eso, ¿cómo hacemos la comunicación de los datos?, si ya la hemos desencriptado, tenemos que hacersela llegar a quien la ha pedido, ¿la volvemos a encriptar para enviarla? ¿No os parece redundante? Pero entonces... ¿Cuál es la solución?

¿Y si en vez de hacerlo en la base de datos lo hacemos en la aplicación? En ese caso no vamos a tener que realizar esa redundancia y ganamos la eficiencia de que ahora la base de datos si que va a saber que busca, además de que podemos decirle que queremos que cifre y que no. Pero... Seguro que ya lo esperabais. La aplicación va a ser ahora la encargada, así que si se compromete la aplicación también se podrían comprometer los datos, además de que es más trabajo de desarrollo. Pero... (tranquilos que es la última ;D), estos problemas son mucho más sencillos de manejar, al final es mucho más eficiente porque la base de datos puede hacer su trabajo sin tener que hacer más operaciones adicionales. Por lo que en general te va a interesar mucho más que sea la aplicación la que maneje estos datos, aunque es más trabajo de desarrollo pero merece la pena. Recuerda mantener bien ocultas las claves y no uses 1234 ;D.

Referencias: Cifrado de SQL Server - SQL Server | Microsoft Learn

CREATE TABLE Person
(NAME    VARCHAR(20) NOT NULL PRIMARY KEY, 
 AGE INTEGER,
 INDEX SHORT_DESC_IND(AGE)
);

HTML y CSS

¿Sabes como funciona lo que estás leyendo? Todo comienza con los 1s y 0s, ¡El binario!. ¡No, no te vayas!, mejor lo dejamos para otra ocasión. Estás leyendo HTML y CSS, que es como funcionan la mayoría de páginas web. HTML es un lenguaje de etiquetas que tu escribes y luego un navegador entiende como tiene que distribuir para que puedas leerlo. Mientras que CSS se ocupa de que lo veas bonito... o distinto, según tus gustos y preferencias. ¿Por qué se estará moviendo el fondo?, vamos a averiguarlo.

Cómo recordatorio, toda esta página está hecha con HTML, CSS y JavaScript puros. Por lo que puedes darte una vuelta por las entrañas o copiarla para ver como está hecho todo el contenido, también puedes encontar el proyecto en GitHub - AlejandroVLDev/AlejandroVLDev.github.io

.

nav, body y footer

Uno de los mayores problemas gráficos que se pueden encontrar en páginas es el "footer"", cuando el contenido es poco el footer en vez de estar en la parte inferior de la pantalla, quedará en medio de la misma. Además la solución es inmensamente sencilla, unas pocas líneas de CSS. Primero tendremos que indicar que el body tenga como tamaño mínimo la altura completa de la pantalla, luego utilizaremos un flex con dirección columna para que se propaguen a lo largo del espacio. Finalmente utilizaremos "flex: 1" para indicar que el elemento principal "crezca" y ocupe todo el espacio posible.

<!DOCTYPE html>
<html>
<head>
    <style>
        body {
            display: flex;
            flex-direction: column; 
            min-height: 100vh;

            
            margin: 0;
        }

        main {
            flex: 1;
        }

        nav {
            background-color: rgba(255, 0, 0, 0.3);
        }

        footer {
            background-color: rgba(0, 255, 0, 0.3);
        }
    </style>
</head>
<body>
    <nav>Soy la barra de navegación</nav>
    <main>Soy el cuerpo</main>
    <footer>Soy el pie de página</footer>
</body>
</html>

flex

¿Por qué deberías usar flex? Porque es una de las propiedades más útiles que se han añadido. Nos va a permitir estructurar una página para que tenga un comportamiento automático más agradable para el usuario, permitiendonos agrupar o centrar sin tener que calcular nosotros absolutamente nada. Además de que gracias a estas propiedades podremos definir un comportamiento que funcione para cualquier pantalla con independencia del tamaño (que no se te olviden las "media queries" ;D).

  • display: flex; es la propiedad que tenemos que poner para que un elemento tenga esta característica.
  • flex-direction: Esta propiedad indica la dirección del contenido, ya sea que los elementos se apilen en una columna o que se apilen a lo largo de la fila.
  • justify-content: Esta propiedad nos va a indicar la distribución del contenido dentro del contenedor, algunos ejemplos son: "flex-start" (todos agrupados al principio), "flex-end" (todos agrupados al final), "center" (todos agrupados al centro), "space-between" (todos para dentr..., digo vamos dejar el mismo espacio entre ellos) y "space-around" (todos tengan la misma distancia a izquierda y derecha).
  • gap: Esta propiedad nos va a indicar el espacio mínimo entre los elementos, para que siempre haya separación entre ellos y no se peguen, ¡recordad poneros la mascarilla!
  • align-items: Esta propiedad nos va a indicar la alineación (vertical en línea y horizontal en columna) del contenido dentro del contenedor, algunos ejemplos son: "start" (todos pegados a la arriba o izquierda respectivamente a línea o columna), "end" (todos alineados abajo o derecha, respectivamente como antes), "center" (todos alineados al centro) y "stretch" (todos crezcan hasta tocar arriba y abajo en lína y izquierda y derecha en columna).
  • flex-wrap: Con esta propiedad podemos indicar si los elementos se van a apilar en una sola línea o en varias líneas, de modo que si no caben todos porque ocupan mucho vayan pasando a la siguiente fila.
  • flex-grow: Esta propiedad es muy intersante para los elementos del propio contenido, con ella indicaremos cuanto queremos que ocupe. Por ejemplificarlo, si ponemos un 2 al primero este ocupara el doble que los siguientes, si ponemos un 3, el triple, y así sucesivamente.
<!DOCTYPE html>
<html>
<head>
	<style>
		body {
			display: flex;
			flex-direction: row;
			justify-content: space-around;
			align-items: center;
			flex-wrap: wrap;
			gap: 1rem;
			height: 100vh;
			width: 100vw;
			margin: 0;
		}

		div {
			padding: 1rem;
			text-align: center;
			margin: 1rem;
		}

		.red {
			background-color: rgba(255, 0, 0, 0.3);
			flex-grow: 2;
		}

		.green {
			background-color: rgba(0, 255, 0, 0.3);
			flex-grow: 1;
		}
	</style>
</head>
<body>
	<div class="red fast">x2</div>
	<div class="green slow">/2</div>
</body>
</html>

Transiciones

Las transiciones son un elemento muy importante del diseño web, porque mejoran mucho la experiencia del usuario, frente a un comportamiento funcional pero robótico. Para ello se utilizan las propiedades de la transición:

  • transition-property: Esta propiedad indica sobre que propiedades del objeto vamos a realizar la transición, en el ejemplo, sobre la propiedad "transform", pero se puede sobre casi cualquier propiedad, o incluso todas con un "all".
  • transition-duration: El valor de esta propiedad indica el tiempo que va a durar la transición, en el ejemplo, 1s, que significa que durará un segundo.
  • transition-timing-function: Dependiendo del valor que pongamos, la transición se va a realizar más rápido o más lenta, en el ejemplo, la función "ease-in", que significa que la transición se va a realizar más rápido en el principio y más lenta en el final.
  • transition-delay: Esta propiedad indica el tiempo que tarda en comenzar la transición, en el ejemplo, 0s, que significa que comenzará inmediatamente.

Recuerda que también se pueden agrupar como en el comentario, no hace falta indicarlas de forma individual y puedes saltarte algunas, que les pondrá un valor por defecto (no te saltes ni "transition-property" ni "transition-duration", ¡son muy importantes!). Con todo esto podemos ver que el bloque izquierdo tiene un comportamiento más natural que el derecho ;D

<!DOCTYPE html>
<html>
<head>
    <style>
        body {
            display: flex;
            justify-content: space-around;
            align-items: center;
            height: 100vh;
            width: 100vw;
            margin: 0;
        }

        .block1 {
            background-color: rgba(255, 0, 0, 0.3);
            transition-property: transform;
            transition-duration: 1s;
            transition-timing-function: ease-in;
            transition-delay: 0s;
            /* transition: transform 1s ease-in 0s; */
            padding: 1rem;
        }

        .block2 {
            background-color: rgba(0, 255, 0, 0.3);
            padding: 1rem;
        }

        .block1:hover {
            transform: rotate(-45deg);
        }

        .block2:hover {
            transform: rotate(-45deg);
        }

    </style>
</head>
<body>
    <div class="block1">Mouse over</div>
    <div class="block2">Mouse over</div>
</body>
</html>

Animaciones

Las animaciones son similares a las transiciones pero te permiten hacer muchas cosas distintas. En primer lugar te permiten hacer un comportamiento controlado y detallado a lo largo de la animación definiendo los porcentajes y como quieres que se encuentre en cada punto (¡No te preocupes que no hace falta rellenarlos todos!, los que no rellenes se ocupará de hacer una animación el propio navegador ;D). Primero crearemos la estructura con keyframes y luego indicaremos que tiene que pasar en cada punto, ya sea por ejemplo cambiar de color o moverse. Después tendremos que rellenar las propiedades allá dónde queramos:

  • animation-name: Tendremos que rellenarla con el nombre de la animación que hayamos puesto en keyframes. En el ejemplo, move.
  • animation-duration: Es el tiempo que tarda en ejecutarse la animación completa. En el ejemplo, 0s, que significa que nunca hará la animación. Aunque date cuenta de que hay dos clases que indican otra cosa, esto lo veremos más adelante.
  • animation-timing-function: Al igual que en las transiciones, es la función matemática que se utiliza para calcular la distribución de la animación a lo largo del tiempo. En el ejemplo, la función ease-in, que significa que la animación se va a ejecutar más rápido en el principio y más lenta en el final.
  • animation-delay: Es el tiempo que tarda en comenzar la animación. En el ejemplo 0 segundos, que significa que comenzará inmediatamente.
  • animation-iteration-count: Indicaremos la cantidad de veces que se va a repetir la animación. En el ejemplo, infinitas veces.
  • animation-direction: Esta propiedad indica por donde va a empezar la animación. En el ejemplo, desde el principio. Pero si indicamos "reverse", la animación se va a ejecutar en sentido contrario.
  • animation-fill-mode: Cuando la animación acabe (si no la hemos puesto infinita ;D), esta propiedad indica que va a suceder con el objeto. Por poner un ejemplo, si la animación lo pintase a azul, siendo rojo en un principio, al terminar volvería a ser rojo. En cambio, si ponemos "forwards" se mantendría en color azul al acabar la animación.

Ahora que sabemos animar hay que fijarse en las dos clases, "fast" y "slow", estas clases definen comportamientos distintos en base a la duración de la animación. Por tanto si añadimos a una etiqueta html que tenga animación podrían modificar las propiedades del objeto (en este caso porque la clase en css tiene mayor prioridad que la etiqueta) y conferir así de propiedades únicas reutilizables en nuestra página.

<!DOCTYPE html>
<html>
<head>
	<style>
		body {
			display: flex;
			justify-content: space-around;
			align-items: center;
			height: 100vh;
			width: 100vw;
			margin: 0;
		}

		@keyframes move {
			0% {
				transform: translateY(0);
			}
			50% {
				transform: translateY(100px);
			}
			100% {
				transform: translateY(0);
			}
		}

		div {
			padding: 1rem;
			animation-name: move;
			animation-duration: 0s;
			animation-timing-function: ease-in-out;
			animation-delay: 0s;
			animation-iteration-count: infinite;
			animation-direction: normal;
			animation-fill-mode: none;
		}

		.red {
			background-color: rgba(255, 0, 0, 0.3);
		}

		.green {
			background-color: rgba(0, 255, 0, 0.3);
		}

		.slow {
			animation-duration: 4s;
		}

		.fast {
			animation-duration: 2s;
		}
	</style>
</head>
<body>
	<div class="red fast">I'm fast</div>
	<div class="green slow">I'm slow</div>
</body>
</html>

Python

¿Sabes qué es Python? Sí, una serpiente también, pero yo estoy hablando del lenguaje de programación. Está basado en uno llamado c y rust, pero es un lenguaje bastante sencillo de aprender y muy útil de adquirir maestría. Es un lenguaje muy eficiente y que tiene una gigantesca comunidad desarrollando paquetes para usar de forma pública. Te va a permitir hacer casi cualquier cosa que quieras con un computador en las manos, y no, no hay que ser malo. Te brillan los ojos, ¿Qué idea se te ha ocurrido?

Compresión de listas

Cuando desarrollamos hablamos números y letras, hacemos operaciones, tanto matemáticas (sumar, multiplicar, derivar, etc.), como lógicas (si es verdad haz esto y si no lo otro), las cuales podemos agrupar para hacer cosas fascinantes, como poder sacar un listado no ordenado de nuestro puesto en la oposición y saber que número eres, con tan solo unas pocas líneas. Hablando de listas, es muy habitual que trabajemos con listas, ya sea en Excel en el trabajo o para hacer la compra o enumerar las tareas que tienes que hacer hoy. Por tanto, una lista en general va a recibir operaciones para cada uno de sus elementos (comprar, limpiar, comer, dormir, etc.). Existen formas de hacerlo igualmente válidas, pero nosotros queremos ser muy elegantes, así que vamos a comenzar.

Lo que vamos a hacer es seguir una estructura: [ operación for elemento/s in lista ], con el ejemplo [i+1 for i in range(5)]

  • operación: Es la operación que queremos realizar sobre cada elemento de la lista. Como podemos ver en el ejemplo, es la suma de uno a un elemento "i" (No os acelereis que lo vemos en el siguiente).
  • elemento/s: Es el elemento o los elementos descompuestos de la lista sobre los que queremos realizar la operación, en este caso la lista es de enteros y por tanto solo existe un posible elemento en cada iteración, por lo que le damos el nombre de "i", aunque podriamos poner el que queramos.
  • lista: Es la lista sobre la que queremos iterar, como se ha dicho antes, es una lista de enteros. La función "range" nos da una lista de enteros desde el 0 hasta el número que le damos sin incluirlo, por tanto "range(5)" nos da la lista [0, 1, 2, 3, 4].

¿Sabes cuál es la respuesta? En efecto, es [1, 2, 3, 4, 5]

¿Por qué lo hemos hecho así en vez de otra manera? En python existen distintas maneras de escribir algo, pero en este caso, cuando realizas operaciones sencillas sobre una lista o solo la usas para llamar a una función, es mucho más "idiomático" y fácil de entender por la comunidad. Así que ya sabes cómo ser mejor programador ;D.

resultado = [i+1 for i in range(5)]
//instead of
for i in range(5):
    resultado.append(i+1)

Type hints

¿Sabeis que son los tipos? Los tipos son una forma de especificar que una variable puede contener una forma de dato en concreto, por ejemplo, una variable tipo int sería una variable que puedes contener números enteros, mientras que una variable con tipo str contendría una cadena de caracteres. ¿Qué implicaciones tiene esto? Como podeis deducir implica que en una variable de tipo entero no podemos poner una cadena de caracteres y al reves. Aunque python es un lenguaje de tipado dinámico, ¿que qué es eso preguntas? Significa que no tenemos que decirle de que tipo son las variables, que el solito en tiempo de ejecución las inferira.

Entonces por qué me cuentas esta milonga de los tipos si no podemos decir que tipo tiene una variable, ¿se te ocurre algo? Pues porque es tan sencillo como que sobre determinados tipos no se puede realizar cualquier tipo de operación, por ejemplo, sobre una cadena de caracteres no podemos hacer un exponencial, eso solo nos vale en números.

¿Cómo lo hacemos entonces? Pues dado que en python no podemos forzar a que quien use nuestras funciones use determinados tipos podemos poner salvaguardas (comprobar por nosotros mismos que lo que nos están pasando es lo que esperamos) y para ayudar a que la gente no se equivoque, podemos usar type hints, que son anotaciones para que la persona que lo use vea que tipos espera la función y que le vamos a devolver. En el ejemplo podemos ver que esperamos un iterador con un tipo de dato "T" (que puede ser cualquier cosa) y un valor de ese mismo tipo "T", además de que el retorno va a ser el mismo tipo de iterador que nos han pasado con el mismo tipo de datos "T".

from typing import Iterable, Iterator, TypeVar
import itertools

T = TypeVar('T')

def deleteIfStartsWith(myList: Iterable[T], myValue: T) -> Iterator[T]:
    return itertools.islice(myList, 1, None) if myValue == next(iter(myList), None) else iter(myList)

Regex fácil

En general utilizamos el texto para absolutamente todo lo que hacemos, pero resulta que los equipos muchas veces no nos permiten utilizar el texto o lenguaje que queramos o lo muestra de forma extraña. ¿Sabes por qué sucede esto? Resulta que en su día, cuando se invento la tecnología de los computadores se utilizo un subconjunto muy limitado de caracteres, lo que se conoce como el alfabeto ASCII y mucha tecnología solo utiliza ese formato, aunque hoy en día hemos añadido soporte para muchos caracteres más, lo que se conoce como Unicode o UTF-8. Aunque no toda la tecnología actual utiliza o permite utilizar este formato.

Vale pero, ¿qué significa "regex"? Es una expresión regular, que es una forma de expresar patrones de texto (los números también van a entrar porque se encuentran dentro de UTF-8 ;D), por ejemplo, un número de teléfono es un patrón de texto que puede ser "123456789", aunque también lo podemos escribrir en un formato distinto como "123-456-789" o "123.456.789". ¿Va viendo por dónde vamos a continuar? Pues es muy sencillo, porque vamos a utilizarlo para eliminar/sustituir los caracteres que no queramos del texto, como por ejemplo las tildes o la ñ para poder convertir el texto a algo funcional para la tecnología que vamos a utilizar.

En el ejemplo vemos dos funciones, la primera es una función vamos a aprovechar de la libreía estandar de Python unicodedata, mediante compresión de listas (que ahora ya conocemos ;D) para sustituir todos los caracteres a su equivalente en ASCII, como por ejemplo la "ñ" a "n" o la "é" a "e". La segunda función utiliza la primera y le da un formato utilizable en ur, quitando primero los carácteres distintos de ASCII, luego a minúsculas y finalmente los espacios por barras bajas, porque si no tendrían que ser "%20" que es bastante menos legible ;D, para terminar con quedarnos con el patrón que contenga de la "a" a la "z" y de "0" a "9" y las "_".

import re
import unicodedata
                                    
def remove_accentuation(text: str):
    return ''.join(c for c in unicodedata.normalize('NFD', text) if unicodedata.category(c) != 'Mn')

def text_safe(text: str):
    text = remove_accentuation(text)
    text = text.lower()
    text = text.replace(' ', '_')
    text = re.sub(r'[^a-z0-9_]', '', text)
    return text

Funciones como argumentos

¿Ya sabemos lo que son los argumentos? Son una forma de pasar la información a una función o procedimiento para que realice la lógica o cálculos que sean aplique, también denominados parámetros. Como hemos visto antes existen los tipos de datos, y, como ahora vamos a ver podemos utilizar un tipo "función" (en realidad por debajo es una dirección de memoria para poder llamarla, pero eso ahora no nos interesa), eso significa que también podemos enviar funciones para que podamos crear una función genérica que haga un procedimiento complejo y que añada lo que sea necesaria sin necesidad de programar 15 funciones distintas que hagan más o menos lo mismo pero con ligeros cambios.

Como podemos ver en el ejemplo, (utilizando la compresión de listas otra vez ;D) tenemos una función que recibe un iterador y va aplicando a todos sus valores la función que el usuario desee para generarnos una lista modificada con los valores nuevos. ¿Sabías que acabas de implementar una función muy similar a una función estándar conocida como "map"?

from typing import Callable, Iterable, Iterator, TypeVar

T = TypeVar('T')

def doForAll(myList: Iterable[T], func: Callable[[T], T]) -> Iterator[T]:
    return (func(i) for i in myList)

C++

Vectores, arrays, listas y mapas hash

Que de cosas hay en el título, a ver cómo hacemoes esto, entre tanto, ¿conoces estás estructuras? Como podeis imaginar son estructuras muy diferentes que se pueden utilizar para guardar datos según distintos criterios, generalmente de eficiencia. Eso es maravilloso, pero, ¿cómo se cúal de todos utilizar?

Primero de todo ¿Sabes qué es la complejidad O(algo)?¿Y qué es la complejidad temporal y espacial (no hablo de Palkia y Dialga, no ;D)? En el mundo de la programación es muy habitual trabajar con estructuras en las que tenemos que buscar información, insertar nueva, modificar datos y borrar la no necesaria. Pero... (siempre hay un pero, ¿verdad? ;D), estas operaciones no siempre son instantaneas, porque a veces no es tan fácil indicar donde está ese dato o dónde tenemos que ponerlo. La complejidad por tanto se refiere a cuánto me va a costar en espacio o tiempo

  • array: El array es una lista de elementos que siempre va a tener un tamaño inamovible en cuanto lo creemos, como vemos en el ejemplo, hemos creado una lista de enteros de tamaño 3, por lo que hay tres espacios, mientras que hemos añadido un elemento en la posición 0 (siempre empezamos en 0 en la informática, cosas del binario ;D). Las ventajas que tiene es que sabemos antes de compilar el programa lo que va a ocupar y tenerlo preparado, sabemos cual va a ser el tamaño (n) y el acceso a los elementos si tenemos que buscarlo es por tanto complejidad lineal o también dicho O(n). Entonces es útil solo si sabemos el tamaño de antemano, si no no merece la pena. Hay que tener mucho cuidado con no pasarse al acceder o quedarse corto, porque si accedemos en el ejemplo al -1 o al 3 (recordad que empezamos en 0 así que llegamos hasta 2) estaremos intentando acceder ilegalmente a la memoria de otra cosa que no sabemos qué es y eso está mal.
  • vector: Es muy similar a todo lo explicado en el array pero el tamaño es dinámico, cambiará en tiempo de ejecución según vayamos añadiendo o quitando elementos, es O(n) la capacidad y O(n) la búsqueda, aunque la inserción sigue siendo O(1) (mientras no tratemos de ordenarlo al menos :D). La ventaja adicional es que puede crecer o disminuir según las necesidades, aunque esto hace que sea un poco más lento por tener que realojar la memoria dado que debe ser continua. Lo usaremos cuando no conozcamos el tamaño.
  • unordered_set: Es una lista aunque un poco más especial, está implementada con lo que se conoce como un árbol rojo negro (aunque en realidad se puede hacer de otras maneras también, en este caso hablo de C++), estos árboles constan de nodos ordenados que se extienden como una árbol boca abajo (no os aburro con los detalles, aunque en realidad es muy sencillo, solo tienes que entender que durante el borrado y la inserción se hacen como mucho dos rotaciones para reordenar el árbol y propagar de modo que la profundidad del árbol o la "algura" siempre sea log n). Lo interesante es que con ello sabemos que las búsquedas siempre van a ser O(log n), o logarítimica, que es mucho mejor, mientras que la complejidad es O(n), pero... me diréis, yo veo tres cuadrados en cada nodo, eso sería mínimo 3n, lo cual es cierto, porque por cada nodo necesitamos 3 espacios, uno para guardar la información y otro para indicar cual es el siguiente nodo menor y cual es el siguiente mayor, pero nosotros simplificamos diciendo que es n, aunque ciertamente va a ocupar un poco más. Pero claro, si necesitamos mantener una lista ordenada esto es muchísimo mejor que tener que reordenar una lista (simplemente enlazada, doblemente enlazada, simple, circular, etc.) porque tardamos menos en encontrar e insertar, siempre que vayamos a hacer muchos accesos, algo bastante habitual.
  • unordered_map: Este es un tipo de lista especial compuesta de pares de elementos (una "clave" y un "valor"), esto es porque la búsqueda se hace mediante una función hash, un tipo de función al que nosotros le damos la "clave" y nos dice en que posición exacta estará ese elemento. Por lo que la búsqueda e inserción serían O(1), básicamente instantáneas, pero existe un concepto llamado colisión, que básicamente implica que ¿qué sucede si dos claves distintas al pasar por la función me devuelven lo mismo?, lo cual hay que gestionar mediante segundas claves o en la siguiente posición inmediata u otras formas y si se borra es posible que haya que recalcularla entera, aunque hay formas de mitigar esto. Lo importante es que son muy rápidas en el caso general pero... (lo has visto venir seguro ;D), ocupan mucho más espacio, porque lo necesitamos para que exista el menor número posible de colisiones, habitualmente hablamos de mínimo el doble de espacios que datos. Por lo tanto estas las usaremos cuando necesitemos un acceso muy rápido a los datos y no nos importe tanto el espacio.

En definitiva, depende de lo que vayas a hacer te interesa más una u otra. Además, se que te has dado cuenta, cuanta más velocidad quieras para acceder (que suele ser el caso más común), más "espacio" o memoria vas a necesitar utilizar.

#include <iostream>
#include <vector>
#include <unordered_set>
#include <unordered_map>

int main() {
    int arr[3];
    arr[0] = 10;
    std::cout << arr[0] << std::endl;

    std::vector<int> vec;
    vec.push_back(7);
    vec.push_back(2);
    std::cout << vec[0] << std::endl;

    std::unordered_set<int> uset;
    uset.insert(3);
    uset.insert(1);
    uset.insert(2);
    for (int val : uset) {
        std::cout << val << std::endl;
    }

    std::unordered_map<int, std::string> umap;
    umap[0] = "patata";
    umap[6] = "tortilla";
    std::cout << umap[1] << std::endl;
}