¿Hace falta ir a la universidad para ser un buen desarrollador?

Son muchos los artículos que hay en Internet, principalmente en inglés, defendiendo la idea de que no es necesario ir a la universidad para serlo, llegando algunos al extremo de decir que los mejores desarrolladores son los que no fueron. Las argumentaciones son de lo más variopintas. Algunos razonan que en un mundo teórico ideal el software sería determinista, desarrollado en lenguajes funcionales y por lo tanto libre de efectos secundarios, pero que en el mundo real los lenguajes, por ejemplo Java y todos los orientados a objetos, fueron creados para solventar las necesidades de los negocios, y por lo tanto aceptando que errores del software por casuísticas imprevistas son sobradamente compensados por el incremento en productividad, ductilidad del software, etc. Es decir, que no importa la calidad de la base teórica del software desarrollado, sólo importa si ayuda a cumplir los objetivos de la empresa, que a grandes rasgos sólo pueden ser dos: reducir los costes o aumentar lo beneficios.

Uno de los artículos más famosos que defienden la idea de que no es necesario ir a la universidad, aunque no es este el tema principal del artículo, lo escribió ya hace años Peter Norvig, empleado de Google y de la NASA, reputado científico computacional muy destacado en inteligencia artificial.

Grandes desarrolladores nunca fueron a la universidad o abandonaron a los pocos trimestres: Bill Gates, John Carmack o Jamie Zawinski son buenos ejemplos, Janus Friis no terminó ni el Bachillerato, mientras que buenos ejemplos de lo contrario son Dennis Ritchie, Ken Thompson, Linus Torvalds y Yukihiro Matsumoto.

La informática, en sus albores, fue creada por matemáticos, ingenieros y físicos. Desde la perspectiva de las matemáticas, en la década de los 30 del siglo pasado, se demostró que si bien la lógica de primer orden es completa en sí misma, al añadirle los axiomas para formalizar la aritmética (algo imprescindible para las matemáticas), ya no se puede probar desde dentro del sistema que este sea consistente ni completo. La demostración de que las matemáticas eran consistentes y completas se esperaba como agua de mayo desde principios del siglo XX y esta demostración cayó como un jarro de agua fría. No obstante, quedaba un tercer interrogante abierto: saber si eran decidibles, es decir, si puede existir un algoritmo general capaz de determinar si toda fórmula de lógica de primer orden es un teorema, es decir, si puede demostrarse a partir de los axiomas. Esto era conocido como el Entscheidungsproblem o problema de la decisión.

Cuando el matemático británico Alan Turing creó el modelo matemático que ahora conocemos como Máquina de Turing1, no tenía como objetivo crear la computación sino hallar la respuesta a la última gran duda que quedaba de la lógica matemática. Turing redujo la cuestión de si existe un algoritmo o método general capaz de resolver el problema de la decisión a saber si existe un método general para determinar si una Máquina de Turing parará o no, es decir, a saber si el número de pasos que seguirá es finito. La respuesta es negativa, no existe tal método, la consecuencia es que las matemáticas no son decidibles. Por lo tanto, vemos que el nacimiento de la informática está íntimamente ligado a las matemáticas.

Otro de los padres de la informática, el húngaro John von Neumann, era matemático, ingeniero, físico e inventor. Además de sus aportes para que esta ciencia naciera, aportó a la física, las matemáticas (también a la estadística), la economía y la ingeniería.

Algunos de los desarrollos posteriores que tuvo la ciencia de la computación también fueron debidos a matemáticos y físicos. Unos, para avanzar más en sus respectivas disciplinas, debieron primero avanzar ésta. Otros, directamente dejaron de lado su ciencia para especializarse en la informática.

Creo que está fuera de discusión que la informática es una ciencia cuyas bases están en las matemáticas. Por lo tanto, opino que un conocimiento de la teoría que sustenta la informática, ya sea autodidacta o adquirido en la universidad, da una ventaja a quien lo poseé en un mundo donde la tecnología cambia cada 5 años, donde por ejemplo esas librerías que conocía al dedillo el programador de Visual Basic se convirtieron en conocimiento inútil cuando tuvo que migrar a .NET para no verse tirado en la cuneta por la tecnología. Esta ventaja consiste en la capacidad de aprender más rápido una nueva tecnología. Lamentablemente, a veces un «reclutador» puede preferir a un desarrollador Java recién salido de la universidad frente a otro licenciado años atrás y con experiencia en otro lenguaje pero que quiere migrar a Java, simplemente porque el recién licenciado se sabe más librerías de memoria, lo que frecuentemente sucede en las «cárnicas».

Los conocimientos teóricos no te ponen delante de alguien que lleva años trabajando en el sector, tampoco evitan que te pase la mano por la cara alguien que no los tiene pero sí tiene una elevada inteligencia. Lo único que pueden hacer es hacerte mejor desarrollador y en un mundo global, donde se puede trabajar a distancia para otros países, a veces en otros continentes, tal vez sea más sano no compararse con otros e intentar «sólo» ser mejor. Hace tan sólo 10 años, ser un reconocido desarrollador en Barcelona podía significar unos elevados ingresos, pero hoy una empresa puede contratar los servicios remotos de especialistas a nivel mundial, a sus tarifas, claro.

Otra ventaja, no ya de los conocimientos, sino del título, es que en una empresa muy establecida y grande puede ser una condición sine quan non. Incluso estando el reclutador, por ejemplo el jefe del departamento que necesita el empleado, dispuesto a ver el CV de candidatos sin el título pero con experiencia laboral que considere adecuada, puede verse incapaz de retirar este requisito de la oferta laboral por ser la cultura de la empresa.

Me ha sorprendido haberme encontrado en mi trayectoria profesional compañeros que hacía poco que habían acabado la carrera y afirmaban no entender para qué les habían puesto tantas matemáticas en la carrera: álgebra, análisis, estadística, matemática discreta… O bien pretendían confundirme o bien no entendieron que un ingeniero se las ingenia mejor con una base. En todo caso, tanto esfuerzo de la sociedad para escolarizarlos para que finalmente acabaran llegando a esta conclusión…


1 Este es el modelo que mejor representa un ordenador actual. Como una de sus características es que tiene dos registros capaces de albergar, cada uno, un número entero infinitamente grande, semejante máquina no puede construirse; se trata «sólo» de un modelo teórico.

Anti join

Las bases de datos relacionales (Oracle, SQL Server, Access, MySQL, etc) están basadas en el álgebra relacional. Dicha álgebra la desarrolló el ingeniero británico Edgar F. Codd en 1970 mientras trabajaba para IBM, pero el gigante azul tardó en desarrollar su primera base de datos relacional por preferir seguir explotando los ingresos de su base de datos IMS/DB. Mientras IBM se dedicaba a rentabilizar al máximo su inversión, otras empresas se llevaron el gato al agua al desarrollar sus propios sistemas relacionales a partir de los papeles de Codd. Habían cambiado para siempre las bases de datos.

Codd proporcionó las bases teóricas para las bases de datos relacionales y para los lenguajes que las manipulan. El rey de estos lenguajes es SQL, Structured Query Language. Ahora bien, lo llamo rey por lo extendido que está desde hace décadas, pues curiosamente tiene una carencia importante muy llamativa: no implementa el antijoin que define los papeles de Codd, sin que aparentemente tenga ninguna dificultad su implementación.

Si definimos el semijjoin (el left o right join de siempre) entre dos tablas A y B como:

Es decir, el left semijoin de las tablas A y B es la unión de todos los elementos a que pertenezcan a A junto con al menos uno de b  que pertenezca/n a B y que satisfagan una función sobre a U b. Esta función hace referencia al campo o campos de ambas tablas que hacemos servir para el join, usando sintaxis de MySQL sería

FROM A LEFT JOIN B ON (A.id = B.id)

El antijoin se definiría así:

Es decir, el antijoin de las tablas A y B es la unión de todos los elementos que satisfacen la función sobre a U b a que pertenezcan a A y no pertenezcan a B.

Desgraciadamente SQL no dispone de algo como:

FROM A ANTI JOIN B ON (A.id = B.id)

Y toca ir haciendo apaños como:

FROM A
WHERE A.id NOT IN(
SELECT id
FROM B)

O la supuesta optimización:

FROM A
LEFT JOIN B ON A.id = B.id
WHERE B.id IS NULL

Que producirá resultados inesperados si el campo pivote es nulo en algún registro de B.

Personalmente no veo que sea técnicamente más complicado implementar en los sistemas gestores de bases de datos un antijoin que otros tipos de join, pero el hecho es que de momento ninguno de los sistemas más extendidos lo incorpora en su dialecto SQL.


Editado el 22/01/2020:

En la última versión de MySQL, la 8.0.17, se va a optimizar el «apaño» antes explicado traduciendo la cláusula IN internamente como ANTIJOIN, según se comenta en la documentación. Podemos encontrar más información aquí.

Programa para resolver ecuaciones diofánticas

En el artículo anterior expliqué cómo se resuelven las ecuaciones diofánticas y su relación con las ecuaciones de congruencia. En la presente entrada vamos a ver un programa en Python que las resuelve.

#! /usr/bin/env python
# -*- coding: utf-8 -*-
# Resuelve ecuaciones diofánticas tipo ax + by = c

import sys
from sys import argv

def extendedEuclideanAlgorithm(old_r, r):
    negative = False
    s, old_t = 0, 0
    old_s, t = 1, 1

    if (r < 0):
        r = abs(r)
        negative = True
        
    while r > 0:
        q = old_r / r
        #MCD:
        r, old_r = old_r - q * r, r
        #Coeficiente s:
        s, old_s = old_s - q * s, s
        #Coeficiente t:
        t, old_t = old_t - q * t, t
        
    if negative:
        old_t = old_t * -1
        
    return old_r, old_s, old_t

a = long(argv[1])
b = long(argv[2])
c = long(argv[3])

mcd, s, t = extendedEuclideanAlgorithm(a, b)
if c % mcd == 0:
    a1, b1, c1 = -a / mcd, b / mcd, c / mcd
    x1, y1 = s * c1, t * c1
    print "x = {0}{1:+d}k" . format(x1, b1)
    print "y = {0}{1:+d}k" . format(y1, a1)
else:
    print "No tiene solución"

Para calcuar 23x -4y = 11 hacemos:

vic@LESBIAN:~/mates$ ./diofanticas.py 23 -4 11
x = -11-4k
y = -66-23k

Aplicando el concepto de clase de equivalencia, tal y como se explica en el anterior artículo, podemos computar la forma paramétrica más simplificada como muestra el siguiente programa:

#! /usr/bin/env python
# -*- coding: utf-8 -*-
# Resuelve ecuaciones diofánticas tipo ax + by = c
# El primero par x e y es la forma parmétrica más simplificada
 
import sys
from sys import argv

def extendedEuclideanAlgorithm(old_r, r):
    negative = False
    s, old_t = 0, 0
    old_s, t = 1, 1

if (r < 0):
    r = abs(r)
    negative = True

while r > 0:
    q = old_r / r
    #MCD:
    r, old_r = old_r - q * r, r
    #Coeficiente s:
    s, old_s = old_s - q * s, s
    #Coeficiente t:
    t, old_t = old_t - q * t, t

if negative:
    old_t = old_t * -1

return old_r, old_s, old_t

a = long(argv[1])
b = long(argv[2])
c = long(argv[3])

mcd, s, t = extendedEuclideanAlgorithm(a, b)
if c % mcd == 0:
    a1, b1, c1 = a / mcd, b / mcd, c / mcd
    x1, y1 = s * c1, t * c1
    # Uso abs() pues Python no hace la división Euclídea con cociente negativo
    equivClass = x1 % abs(b1)
    print "x = {0}{1:+d}k" . format(equivClass, b1)
    print "y = {0}{1:+d}k" . format((c1 - (a1 * equivClass)) / b1, -a1)
    print "x = {0}{1:+d}k" . format(x1, b1)
    print "y = {0}{1:+d}k" . format(y1, -a1)
else:
    print "No tiene solución"

Este es el resultado de diferentes ejecuciones:

vic@LESBIAN:~/mates$ ./diofanticas.py 4 7 29
x = 2+7k
y = 3-4k
x = 58+7k
y = -29-4k
vic@LESBIAN:~/mates$ ./diofanticas.py 23 -4 11
x = 1-4k
y = 3-23k
x = -11-4k
y = -66-23k

El uso de la función abs(), que nos devuelve el valor absoluto, es debido a que Python no hace la división Euclídea cuando el cociente es negativo. Los dos pasos:

  • q = old_r / r
  • old_r = old_r – q * r

Se podrían unificar con la función divmod(), pero me parece menos claro para quien no conoce el lenguaje, siendo los dos pasos más parecidos al pseudocódigo.

Resolución de ecuaciones diofánticas

Las ecuaciones diofánticas contienen 2 incógnitas en una sola ecuación y están generalmente expresadas en la forma ax + by = c

Cuando hay dos incógnitas, tal vez nos resulte más familiar un sistema de dos ecuaciones como nos enseñaron en primaria, por ejemplo:

  • 2x + 2 = 6y
  • 4y + 2 = x + 6

Ahora bien, una ecuación diofántica también nos resulta familiar expresada en la forma ax + by – c = 0 , pues se trata de la ecuación de una recta. Al tratarse de una recta sus soluciones serán infinitas, si tiene solución.

Aunque el conjunto de puntos de una recta está en R, al igual que hemos hecho con las congruencias contemplaremos sólo las soluciones en Z. En este conjunto, hay un teorema debido al matemático indio Brahmagupta, que nos dice que la condición necesaria y suficiente para que la ecuación diofántica ax + by = c tenga solución es que d = mcd(a, b) | c En este caso, si (x1, y1) es una solución, todas las demás soluciones se obtienen mediante las expresiones:

  • x = x1 + (b / d) * k
  • y = y1 – (a / d) * k

Este teorema nos resulta familiar con las ecuaciones de congruencia; vimos que ax ≡ b (mod m) tendrá solución si d | b En los dos artículos anteriores, 1 y 2, vimos también que solucionar ecuaciones de congruencia lineal se reduce a resolver congruencias donde el coeficiente de la x, a, y el módulo m son primos entre si. El procedimiento es el mismo para las ecuaciones diofánticas. Por el teorema de Bezout sabemos que existen coeficientes s y t tales que:

a*s + b * t = d

Si e = c / d y multiplicamos ambos lados por e tendremos:

  • x1 = s * e
  • y1 = t * e

Vamos a resolver 23x – 4y = 11 Mediante el algoritmo de Euclides tenemos:

  • 23 = -4 * (-5) + 3 ⇒ 2 = 23 – 4 * 5
  • -4 = 3 * (-2) + 2 ⇒ 2 = -4 + 3 * 2
  • 3 = 2 * 1 + 1 ⇒ 1 = 3 -2 * 1

Obtenemos mcd(23, -4) = 1 Usemos el algoritmo extendido de Euclides para hallar un s y t:

1 = 3 – 2 * 1 = 3 – (-4 + 3*2) * 1 = 3 + 4 – 3 * 2 = 4 – 3 * 1 = 4 – (23 – 4 * 5) * 1 = 4 -23 * 1 + 4 * 5 = 23 * (-1) + 4 * 6

Sabiendo s y t tenemos tendremos x1 e y1:

23 * (-1) – 4 * (-6) = 1

23 * (-11) – 4 * (-66) = 11

  • x1 = -11
  • y1 = -66

Reemplacemos para encontrar las expresiones que dan todas las soluciones:

  • x = -11 + (-4 / 1) * k = -11 – 4k
  • y = -66 – (-23 / 1) * k = -66 – 23k

Siguiendo con las familiaridades, podemos ver que estas expresiones para x e y coinciden con la forma paramétrica de la ecuación de la recta.

Si usamos fracciones continuas para resolver la ecuación diofántica llegaremos a este resultado:

  • x1 ⇒ x = 1 – 4k
  • y1 ⇒ y = 3 – 23k

Cómo resolver ecuaciones diofánticas mediante fracciones continuas puede encontrarse en Google, lo que quiero destacar es que aparentemente hemos llegado a un resultado diferente pero no es así. Veamos que ambas soluciones representan la misma recta:

23x - 4y - 11 = 0La segunda solución nos conduce a la misma ecuación 23x – 4y – 11 = 0

23x -4y - 11 = 0El algoritmo extendido de Euclides nos da un par s y t y cualquier otro par nos dará soluciones que son la misma recta. El resto de pares nos los da la expresión:

1 = 23*( -1 – (-4) * h) + (-4)*(-6 + 23 * h) ∀ h ∈ Z

Para h = 1 y multiplicando luego por 11:

  • x1 = 33 ⇒ x = 33 – 4k
  • y1 = 66 ⇒ y = 187 – 23k

Podemos ver que se trata de la misma recta:

23x - 4y - 11 = 0En el artículo anterior resolvimos la congruencia 12x ≡ 15 (mod 21) y podemos ver que resolver esta ecuación equivale a encontrar todos los enteros x que satisfagan la ecuación diofántica 12x + 21y = 15. Vamos a resolverla:

12x + 21y = 15 equivale a 4x + 7y = 5

Bezout: 4s + 7t = 1

Podemos ver que s = 2 y t = -1 satisfacen la ecuación. Por lo tanto:

4(2 * 5) + 7(-1 * 5) = 5

  • x1 = 10 ⇒ x = 10 + 7k
  • y1 = -5 ⇒ y = -5 – 4k

La solución para x son todos los múltiplos de 7 + 10 En «términos» de congruencias diríamos que estamos en Z7 y que 10 pertenece a la clase de equivalencia [3] Sabemos que el mcd es 3, por lo tanto los 3 primeros residuos serán las soluciones: 3, 10 y 17

También vemos que el conjunto de soluciones k = {-1, 0, 1} dan las soluciones de la ecuación de congruencia 12x ≡ 15 (mod 21) x = {3, 10, 17}  Gracias a lo que sabemos de congruencias podemos suponer que otra forma paramétrica correcta para x es:

x = 3 + 7k Por lo tanto x1 = 3. Para encontrar y1 reemplazamos:

4*(3) + 7y = 5 ⇒ y1 = -1 ⇒ y = -1 – 4k Así que otra forma paramétrica equivalente es:

  • x = 3 + 7k
  • y = -1 – 4k

Si desarrollamos como hemos visto anteriormente veremos que ambas formas paramétricas conducen a la misma ecuación de la recta: -4x -7y + 5 = 0