Búsqueda binaria o dicotómica

Durante el corto tiempo que tuve cuenta en CuriousCat (lo borré porque para recibir insultos anónimos ya me llega con Tweeter y los comentarios de este blog) alguien me pidió que escribiera sobre la diferencia entre una búsqueda binaria y una búsqueda dicotómica. Eso se lo contesté rápidamente allí: búsqueda dicotómica y búsqueda binaria son sinónimos, no hay diferencia.

¿En qué consiste la búsqueda binaria?

Cuando hablamos de búsqueda binaria hablamos de un algoritmo, una serie de instrucciones para que un programa informático realice una tarea. Concretamente de un algoritmo de búsqueda, es decir, la serie de instrucciones necesarias para encontrar un valor dentro de una colección de valores. La búsqueda binaria está pensada para buscar un elemento dentro de una colección ordenada. Vamos a explicar cómo funciona con un ejemplo: imagínate que tuvieras una lista con todos los habitantes de Barcelona ordenada por la primera letra de su apellido, tienes que buscar a alguien que se apellida «Martínez» ¿te pondrías a mirar la lista desde el principio hasta llegar a ese nombre? Son millones de comprobaciones. Ese procedimiento sería el de una búsqueda lineal: comprobar todos los valores hasta encontrar el deseado, funciona pero es muy lento, tiene utilidad cuando no queda más remedio (una colección desordenada) pero es demasiado ineficiente para una lista ordenada.

Aplicando una búsqueda binaria a ese ejemplo lo que haríamos sería encontrar primero el valor del medio de la lista, dividir la lista en dos mitades y comprobar si el valor intermedio es el que buscamos, es menor o es mayor. Si se diera la casualidad de que es el que buscamos la búsqueda ya estaría finalizada, si el valor intermedio es menor descartaríamos la mitad inferior de la lista y si es mayor descartaríamos la mitad superior. Ahora que localizamos en qué mitad tiene que estar nuestro valor repetimos el procedimiento sobre esa mitad, creando otras dos mitades y de nuevo repitiendo el procedimiento hasta dar con nuestro valor. En cada iteración descartamos la mitad de los datos que teníamos, lo que reduce el tiempo de búsqueda respecto a la lineal.

¿Se usa este algoritmo?

La mayoría de lenguajes de programación o frameworks ya tienen funciones de búsquea integradas muy bien optimizadas, así que no es habitual que alguien tenga que escribir una función de búsquea binaria, excepto en ejercicios académicos para aprender algoritmia. Yo personalmente he utilizado este método para buscar entre conjuntos de datos manualmente, cuando no disponía de un índice. Aquí abajo os dejo una implementación del algoritmo en Javascript:

function buscaDicotomica(valor, conjunto) {
    //valor es lo que vamos a buscar, cojunto es el array donde lo buscamos
    var ini = 0;    //inicio del array
    var fin = conjunto.length - 1;   //fin del array
    var pos = -1;
    var finaliza = false;
    var media;
 
    while (finaliza === false && ini<= fin) {
        media= Math.floor((ini+ fin)/2);
        if (conjunto[media] == valor) {
            finaliza = true;
            pos  = media;
        } else if (conjunto[media] > valor) {  //si está en la mitad inferior
            fin= media - 1;
        } else {  //si está en la mitad superior
            ini= media+ 1;
        }
    }
    return pos;
}

Varianza y Desviación Estándar ¿Qué son?

En su día hablamos aquí sobre las medidas de tendencia central en estadística y también sobre cómo hacer una gráfica de dispersión en una hoja de cálculo de LibreOffice, pero nunca habíamos hablado de la dos principales medidas de dispersión: la varianza y la desviación estándar.

Imagen genérica de un libro de matemáticas y una libreta
Photo by Lum3n on Pexels.com

Si la media, mediana y moda (medidas de tendencia central) buscan resumir en un solo valor todo un conjunto, en el caso de las medidas de dispersión se pretende estudiar la dispersión de los datos con respecto a la media. Cuánto más homogéneos sean los datos con los que trabajamos, menor será la dispersión, a mayor dispersión mayor variabilidad.

En teoría de la probabilidad la varianza es definida formalmente como «la esperanza del cuadrado de la desviación de dicha variable respecto a su media«, representa la variabilidad de una serie de datos respecto a su media. La fórmula para calcularla consiste en sumar los residuos al cuadrado, divididos entre el total de observaciones, por eso mismo la unidad de medida de la varianza siempre será positiva y se expresará como la unidad de medida de los datos elevada al cuadrado, por ejemplo si calculamos euros hablaríamos de una varianza de X euros al cuadrado, si calculamos litros diríamos litros al cuadrado. La fórmula os la dejo en la siguiente imagen:

Siendo x la variable sobre la que se calculará la varianza, xi la observación número i de la variable x, n el número de observaciones y la media de x. Los residuos se elevan al cuadrado porque de no hacerlo el resultado siempre sería 0.

Usando la varianza sabremos que cuánto más grande sea más desviados están los datos, pero lo que no sabes es cuánto, para esto necesitaremos calcular la desviación estándar.

La desviación estándar, o desviación típica, es una medida que se utiliza para cuantificar la dispersión de un conjunto de datos. Para calcularla simplemente habría que hacer la raiz cuadrada de la varianza, de esa forma podremos comparar ese valor contra la media para verificar la dispersión de los datos. Un valor bajo en la desviación estándar indicará que la mayoría de los datos de la muestra están agrupados cerca de la media, mientras que un valor alto indicará que los datos se repearten en un rango de valores más amplio.

¿Por qué el factorial de 0 (0!) es igual a 1?

En los comentarios de la popular entrada ¿por qué un número elevado a 0 es igual a 1? me preguntaban también ¿Por qué el factorial de 0 es igual a 1?

En principio la definición de factorial de un número n sería el producto de todos los números enteros positivos desde 1 hasta dicho número n. Es decir, el factorial del número cuatro sería:

4!=4*3*2*1=24

¿Entonces por qué el factorial de 0 es 1? Bueno, si nos vamos a la definición anteriormente vista de factorial «producto desde 1 hasta n» nos encontramos con que no tenemos número alguno que multiplicar, esto es lo que se llama un producto vacío y por convención es 1.

Pero también, como con el factorial, podemos argumentar esto más allá de decir «por convención» con una explicación informal:

Empecemos estableciendo una igualdad: el factorial de un número n es el resultado de dicho número multiplicado por el factorial de n-1.

n! = n*(n-1)!

Por lo tanto también podemos decir que el factorial de n-1 es igual al factorial de n divido entre n:

(n-1)!=n!/n

Entonces si forzamos la definición de factorial para meter el cero tendríamos este resultado para poder mantener la coherencia:

0!=(1-1)!=1!/1=1

Otro ejemplo informal. Supongamos que tenemos a cuatro personas y queremos colocarlas en fila. ¿Cuántas combinaciones posibles tenemos? ¿Cuántas formas tenemos de colocarlas? Pues 4!, 24 posibilidades. Ahora ¿si solo fuesen 2 personas? Entonces 2! que es 2, solo tenemos dos formas de ordenar la fila. ¿Una persona? ¿Cuántas formas tenemos de colocar una fila de una persona? Solo una. Entonces ¿cuántas formas tenemos de colocar una fila de 0 personas? Técnicamente no sería una fila porque no habría nadie formándola, pero en todo caso también tenemos solo una forma.

Operaciones a bit a bit: NOT, AND, OR y XOR

Seguimos con la racha de entradas encadenadas. Si la entrada de los tipos de configuración RAID nos llevó a definir el concepto de información de paridad ahora a raíz de esa entrada vamos a hablar de las operaciones bit a bit.

Hablamos de operaciones a nivel de bit porque con ellas trabajamos directamente sobre los bits, dígitos binarios, a nivel individual. Este tipo de operaciones son muy utilizadas en computación, a la hora de cifrar datos o, como comentábamos en el otro artículo, para realizar los cálculos de paridad. Es posible que te suenen los nombres de estas operaciones, pues son operaciones lógicas que ya vimos en su día en un artículo sobre LibreOffice Calc o también en la entrada sobre el álgebra booleana.

NOT: La operación NOT también podría ser llamada complemento y es un operador unario. Realiza la negación lógica de cada bit, es decir, invierte su valor. Por ejemplo, la operación NOT 1010 daría como resultado 0101.

AND: La operación AND, o conjunción lógica, funciona de forma similar a una multiplicación: sólo nos devolverá 1 si todas las entradas son 1, en el resto de casos devolvería 0. Es decir 0 AND 0 es igual a 0, 1 AND 0 es igual 0 y solo 1 AND 1 es igual a 1. Por ejemplo: 100111001011010100111010 AND 010110100001101111011000 = 000110000001000100011000. Cuando hacemos la operación AND la secuencia que dará como resultado no puede ser más larga que la mayor de las secuencias operadas.

OR: La operación OR, o disyunción lógica, dará como salida 1 siempre que una o más de sus entradas sean 1. Es decir, 1 OR 1 es igual a 1, 1 OR 0 es igual a 1 y solo 0 OR 0 es igual a 0. Por ejemplo: 1000 OR 0011 = 1011. Cuando hacemos la operación OR la secuencia que dará como resultado no puede ser más corta que ninguna de las secuencias operadas.

XOR: La operación XOR, u OR exclusivo, dará como salida 1 siempre que solo una de las dos entradas sea 1. Es decir, que 0 XOR 0 será igual a 0, 1 XOR 1 será igual a 0 pero 1 XOR 0 o 0 XOR 1 serán igual a 1. Por ejemplo 1010 XOR 1001 = 0011 Dado que es complicado revertir su resultado es muy utilizada en algoritmos de cifrado.

¿Qué es la paridad en una configuración de RAID?

Bueno, mi última entrada había sido explicar qué es un RAID y raíz de eso me han preguntado otra cosa ¿Qué es la paridad?

Bueno, la información de paridad es un tipo de redundancia que nos permite recuperar los datos al vuelo en caso de que falle algún disco. Esto también lo permite una copia en espejo (RAID 1), pero con una diferencia: En el artículo anterior veíaimos que en un RAID 1 tenemos todo un disco ocupado con los datos redundantes, en cambio en un RAID 5 decíamos que solo ocupábamos el equivalente a un disco para almacenar la paridad, si tenemos cuatro discos aprovechamos el espacio de 3 ¿Qué brujería es esta? Pues una brujería matemática.

La información de paridad es una informacion calculada operando con el resto de datos de la misma división. De esta forma si se rompe un disco podemos recuperarlos comparando los datos que tenemos con los de paridad. Por poner un ejemplo simplificado, pensemos en álgebra sencilla. Imagina que calculásemos la paridad sumando los valores de los sectores de una misma división: El valor del sector 1 sería 1, del sector 2 sería 2, del sector 3 sería 3 y la paridad sería su suma ( 1+2+3=6 ). Si se rompiese el disco que conteía el sector 2 tendríamos esto en nuestros discos: 1+X+3=6. Podemos resolver la ecuación para saber que X era 2 antes de tener que cambiar el disco. Aunque el cálculo de paridad es realmente más complejo, esencialmente sería algo así.

Los sistemas de paridad simple más sencillos suelen usar la operación XOR para hacer estos cálculos a nivel de bit. Los sistemas más complejos, por ejemplo la doble paridad del RAID 6, utilizan operaciones más complicadas como un código Reed-Solomon u operaciones sobre un campo de Galois concreto. El requerir operaciones más complejas para calcular la paridad provoca que la velocidad de escritura sea menor, a cambio de conseguir una mayor tolerancia a fallos ya que podrían fallar hasta dos discos sin que perdamos datos.

¿Qué significa que un teléfono o tablet es IP68 o IP69K?

Me preguntaba un amigo el otro día que significaba IP68 porque le habían ofrecido un teléfono Ulefone que en su descripción destacaba esa característica. IP será la abreviatura de Ingress Protection, protección de entrada en castellano y se trata de un estándar internacional para medir y certificar la resistencia de un dispositivo electrónico ante cuerpos extraños. Este estándar es definido y regulado por la normativa IEC 60529 (equivalente a la EN 60529 de la Unión Europea).

El primer número define el nivel de resistencia ante intrusiones o cuerpos sólidos:

  • X: Implica que no hay datos disponibles
  • 0: No hay ningún tipo de protección ante el acceso o el contacto con cuerpos sólidos
  • 1: Protección contra cuerpos mayores de 50 milímetros de diámetro.
  • 2: Protección contra cuerpos mayores de 12.5 milímetros de diámetro y 80mm de longitud. Por ejemplo, un dedo podría penetrar dentro del dispositivo
  • 3: Protección contra cuerpos mayores de un diámetro de 2.5 milímetros. El dispositivo podría ser penetrado por algunas herramientas o cuerpos de más de ese tamaño.
  • 4: Protección contra cuerpos sólidos de más de un milímetro de diámetro. El dispositivo solo podría ser penetrado con algún tipo de hilo o con herramientas de precisión.
  • 5: Protección contra polvo grueso, arena, etc. Puede entrar una cantidad de polvo en el dispositivo pero no suficiente para dañarlo.
  • 6: Dispositivo totalmente estanco al polvo.

El segundo número lo que marcaría es su nivel de estanqueidad ante el agua:

  • 0: Sin protección.
  • 1: Protegido contra la condensación
  • 2: Protección contra gotas de agua que caen con un ángulo de 15º sobre la posición normal del dispositivo, testeado con cuatro ejes.
  • 3: Protección contra gotas de agua que caen con un ángulo de 60º sobre la posición normal del dispositivo, testeado con cuatro ejes y con agua vertida en forma de spray.
  • 4: Protegido contra salpicaduras en cualquier dirección
  • 5: Protegido contra chorros en cualquier dirección.
  • 6: Protegido contra chorros y olas.
  • 7: Protegido contra una inmersión total durante un corto espacio de tiempo.
  • 8: Protegido contra una inmersión total durante un largo espacio de tiempo. Generalmente es testeado de 3 a 1.5 metros de profundidad durante 30 minutos.

¿Y la IP69K?

Pues en este caso iría un paso más allá en cuanto a estanqueidad. Fue regulada originalmente por el estándar alemán DIN 40050-9, que posteriormente sería substituído por la normativa ISO 20653. En el estándar EN 60529 sería IPX9.

Básicamente IP69K testearía la resistencia del dispositivo ante líquidos a alta presión y alta temperatura, con agua a 80 grados, a una presión de entre 80 y 100 bares a una distancia de 10 o 15 centímetros desde cuatro ángulos distintos, aplicando 30 segundos por cada ángulo.

Así que si necesitas un equipo electrónico para trabajar en condiciones duras, de polvo extremo, humedad extrema, arena, etc… buscar un dispositivo que cumpla estos estándares sería una buena idea.

Problema de Parada (o problema de Halting)

Vamos con un poco de teoría de la computación ¿Qué es el problema de paro, de parada o de Halting? Consiste en determinar si existe una máquina de Turing capaz de determinar si cualquier Máquina de Turing se va a detener o no. Dada una máquina de Turing M y una palabra w se determinará si M acabará en un número finito de pasos usando w como dato de entrada.

El problema es indecidible, según el propio Turing ninguna máquina de Turing puede resolverlo. Es decir, no puede existir un programa genérico que demuestre que todos los programas del mundo terminan, se puede hacer para un programa concreto pero no existe la solución general.

Existen varias demostraciones de por qué es indecidible, vamos a ver una demostración escrita en javascript.

Imaginemos que alguien escribe una función f que recibe como parámetros una función y sus argumentos, y que tiene un código capaz de comprobar si esta se detendrá o no.

var f = function (funcion, argumentos) { ... }; // Aquí iría un código correcto que supuestamente calcularía si la máquina para o no

Vamos a suponer que esa función está correcta, funciona y nos devuelve true si el programa termina, y false si caería en un bucle infinito. Entonces podríamos usarla como subrutina dentro de otra función más grande llamada g como la que viene a continuación:

var g = function (funcion) {
//Pasamos en el parámetro funcion la función y sus argumentos 
  if (f(funcion,funcion)) {
    while (true); //esto provoca un bucle infinito
  }
  else {
    return false;
  }
}

Esto vendría a decir que g(funcion) termina siempre y cuando funcion(funcion) nunca termina.

Entonces ¿qué ocurre si la pasásemos a la función g como parámetro su propio código? Es decir ¿qué pasa si g evalúa a g? Pues que llegamos a una contradicción: g(g) termina siempre y cuando g(g) nunca termina. Por tanto, al llevarnos a una paradoja dicho algoritmo no puede existir.

Diferencia entre corriente alterna y continua

En el pasado ya hablamos un poco de electrónica básica cuando vimos las magnitudes existentes en un circuito eléctrico. Hoy vamos a hablar de los distintos tipos de corriente.

La corriente eléctrica es el flujo de carga eléctrica que recorre un material, debido al movimiento de electrones. Según el sentido en que se muevan estas cargas de electrones podemos distinguir dos tipos de corriente: alterna y continua.

Corriente Continua:

Es el tipo de corriente que vemos en baterías y generadores. En este caso la electricidad mantiene una intensidad constante y el flujo de electrones se mueve en un solo sentido.

En castellano se abrevia como CC mientras que en inglés se usan las siglas DC.

Corriente Alterna:

Es el tipo de corriente más común en las instalaciones domésticas, puesto que permite utilizar un mayor voltaje para que la corriente viaje a mayor distancia, pudiendo utilizar luego transformadores para rebajar dicho voltaje si es necesario.

En este caso las cargas de electrones cambian de sentido constantemente. Los cambios de sentido en el flujo de electrones se producen muy rápido, varias veces por segundo. La frecuencia de cambio se mide en hercios, que son el número de ciclos por segundo. Esta corriente no tiene una polaridad definida, ya que dependiendo al orden esta puede ser tanto positiva como negativa, por esto puede alternar su polaridad.

En castellano se abrevia como CA mientras que en inglés se usan las siglas AC.

En resumen:

La diferencia principal entre corriente alterna y continua está en el sentido del flujo de sus electrones: la corriente continua se desplaza en línea recta mientras que la corriente alterna, a medida de su paso y velocidad, toca ambas polaridades.

La corriente alterna nos permite conectar un aparato a un enchufe sin que importe donde está el polo positivo o el negativo, mientras que en la corriente continua las conexiones han de realizarse conectando siempre el polo positivo y el negativo de forma adecuada para que funcione.

Magnitudes básicas de un circuito eléctrico

No hace mucho veíamos en un artículo sobre Javascript cómo realizar cálculos basados en la ley de Ohm. Esta vez vamos a alejarnos un poco de los lenguajes de programación y a darle un repaso a la física básica ¿Cuáles son las principales magnitudes que podemos medir en un circuito eléctrico?

Diferencia de Potencial:

También llamada tensión o voltaje, indica la diferencia de energía entre dos puntos de un circuito. La diferencia de potencial que existe entre los polos del generador o entre los puntos cualquiera del circuito es la causa de que los electrones circulen por el circuito, si este se encuentra cerrado. Es una medida de la fuerza que hay que comunicar a los electrones para que se muevan y su unidad de medida es el Voltio (V).

Intensidad:

La intensidad sería la cantidad de carga eléctrica que circula por un circuito en la unidad de tiempo, que mediríamos en segundos. La unidad con la que mediríamos la intesidad es el Amperio (A).

Resistencia:

Se trata de la propiedad que tienen los cuerpos para dificultar el paso de la energía. Cuanto menor sea esta mejor conductor será esa sustancia. La resistencia de un conductor depende tanto del tipo de material del que está compuesto como de su longitud y de su sección. Denominaremos estos factores resistividad (ρ), longitud (l) y sección del conductor (S). La unidad de medida de resistencia es el Ohmnio (Ω).

Para calcularla debemos multiplicar la resistividad por la longitud dividida entre la sección. Lo expresaríamos con la fórmula: R = ρ * l / S.

Calculando potencia y energía

En base a lo visto arriba podríamos calcular cuánta es la energía consumida por un aparato eléctrico en una unidad de tiempo. Esto sería la potencia del aparato.

Potencia:

La mediríamos en Watios (W).

Tenemos dos fórmulas para calcularla: multiplicar el voltaje por la intensidad o multiplicar el cuadrado de la intensidad por la resistencia. La primera fórmula la expresaríamos como P = V * I y la segunda sería P = I2 * R

Energía eléctrica:

Y acabaríamos con la energía consumida por un circuito eléctrico medida en Julios (J).

Podemos calcularla multiplicando la potencia del circuito (que acabamos de ver cómo calcular) por el tiempo que está en uso (en segundos). La fórmula para esto la expresaríamos como E = P * t

¿Y saber esto para qué me vale?

Es la típica pregunta de adolescente ¿para qué me vale saber esto si no voy a ser electricista? Pues por ejemplo entenderlo te ayudará a calcular qué potencia necesitas contratar para tu casa en tu contrato de la luz, o a calcular cuántos paneles solares necesitarías en caso de querer ponerte a producir tu propia energía.

En España el grueso de la factura depende de la cantidad de potencia contratada. La potencia contratada vendría a ser la cantidad de watios que podemos estar consumiendo a la vez en nuestra casa, que lo puedes entender como la suma de la potencia de todas las luces y electrodomésticos que podemos estar utilizando a la vez. Puedes leer más sobre el tema de la contratación de energía en este blog.

Orden de operaciones aritméticas (PEMDAS) y su aplicación en lenguajes de programación.

Todo un clásico en las redes sociales es que alguien comparta la operación 5+4/3-1*2 y que se monte un gallinero tremendo en los comentarios con distintas soluciones. Esto se debe a que mucha gente no tiene claro cómo va la jerarquía de las operaciones y el orden de evaluación de las mismas.

Si hablamos de operaciones básicas, y de la mayoría de lenguajes de programación (Javascript, PHP, Python, Ruby, C,Visual Basic, Java…), nos regiremos por el orden de operaciones conocido por el acrónimo inglés PEMDAS, que en castellano podríamos traducir como PAPOMUDAS (PAréntesis, POtencias, MUltiplicación, División, Adición, Sustracción). En base a esto el orden de operaciones en lenguajes de programación como Python, PHP, Ruby o Javascript sería:

  1. Paréntesis
  2. Potencias y radicales
  3. Multiplicación, división, división entera y módulo.
  4. Suma y resta.

En este enlace puedes comprobar los resultados de distintas operaciones realizados en distintos lenguajes de programación. Puedes copiar los siguientes ejemplos para comprobar que el resultado es el mismo.

Aquí el código en Javascript:

var resultado = 5+4/3-1*2;
console.log(resultado);

Aquí el código en Python:

resultado = 5+4/3-1*2
print(resultado)

Aquí en Java:

public class Test {
  public static void main(String[] args){
    System.out.println(5.0+4.0/3.0-1.0*2.0);
  }
}

Y aquí en C:

void main(void) {
   double resultado;
   resultado = 5.0+4.0/3.0-1.0*2.0;
   printf("%f",resultado);
}

Como puedes comprobar, en todos el resultado es 4.333333 ya que todos usan el mismo orden para las operaciones.