El título suena un poco polémico, pero es sólo para llamar la atención. Si vienes sólo por el problema de código, puedes saltar hasta el final de esta entrada jeje. En esta entrada sólo escribiré una opinión personal sobre la parte técnica del mundo TI, mi experiencia en algunas empresas e interactuando en redes sociales.

Es cierto que el boom tecnológico ha estado fuerte esta última década, esto se debe al avance de muchas disciplinas, como por ejemplo reducción en los costos de manufactura de chips (tenemos chips con mayor capacidad de cómputo y mejores características de rendimiento que hace 20 años), nuevos paradigmas como desarrollo en la nube, desarrollo de arquitecturas y herramientas para facilitar procesamiento distribuído, y un gran flujo de datos (dispositivos, uso de aplicaciones, etc.). Esto ha provocado que también suba la cantidad de discusiones, artículos, consejos, asesorías, consultorías, charlas, ventas, cursos, etc. En particular yo no tengo problemas con los negocios, cada quién ve un nicho e intenta de alguna forma cosechar y sembrar, en este caso una red de contactos e ingresos monetarios. Lo que si estoy en desacuerdo, es que se vendan algunas ideas, entre las más repetidas:

  • No es necesaria la teoría, sólo tienes que aprender la tecnología (framework, lenguaje) e ir subiendo el sueldo
  • Promesas sobre tener un conocimiento para trabajar en un área específica (ingeniería de software, ciencia de datos, inteligencia artificial)
  • Consejos y asesorías sobre sacar una billonada de certificados (que expiran pronto y dependen de los cambios de paradigmas y tecnologías). Dar una falsa sensación de éxito por colección de papeles
  • Tomar cursos online (tipo Udemy, Udacity, Coursera) y salir con la falsa noción de expertiz en un área (IA, Ing de Software, Machine Learning, etc)
  • El punto previo implica cerrarse sólo a aprender tecnologías y frameworks, lenguajes, bibliotecas, sin entender los fundamentos y quedarse sólo en cursos “introductorios”. Hay que estar consciente de las desventajas de los MOOC.

No quiero sonar amargado, ni tampoco creer que tengo la razón en todo, sólo quiero aclarar puntos en mi experiencia tanto académica como de carrera. En primer lugar, no tengo nada contra los cursos online. De hecho yo participaba activamente de MOOCs cuando empezó edX y Coursera, por el 2012 (probablemente estas plataformas empezaron antes), recuerdo los certificados eran gratis, y los cursos tenían plazo. Habían discusiones interesantes en los foros de la plataforma, y en las primeras iteraciones no existían las soluciones online de los cursos. Ahora esto es distinto, mucho material reciclado (de buena calidad, pero reciclado), y las soluciones exactas a los problemas y tareas ya se pueden encontrar googleando. Esto me recuerda, *¿cuántas veces vi en la universidad estudiantes con la excusas del tipo pero si estudié, hice todas las pruebas anteriores todas las guías, la prueba de hoy estuvo más difícil que nunca, hice 1000 ejercicios, estudié y aún así me fue mal? Creo que perdí la cuenta, para los metidos con Machine Learning (aprendizaje automático), es como entrenar un modelo en el conjunto de entrenamiento, obtener una métrica por ejemplo F1 Score = 0.80 y que en la práctica el rendimiento sea 0.2 (reprobado). Esto puede ocurrer por varias razones, entre las más comunes:

  • Inconsistencia entre la distribución de datos del conjunto de entrenamiento (ej. sesgo humano, datos fabricados, fuga de datos) y el dominio en si
  • Modelo mal ajustado, sobre-estimando conocimiento, básicamente memorizando las respuestas.

Esta es la última que me preocupa y esta enlaza directamente con las personas que proponen:

  1. Que se puede ser profesional en tecnología en 12 meses (software, investigación, inteligencia artificial)
  2. Que los fundamentos no importan (muchas veces he leído el típico los libros no sirven el conocimiento lo da la experiencia)
  3. Que taparse en certificados es un hito importante respecto a lo técnico (en mi opinión hay mejores formas de lidiar con la falta de confianza, yo también la tengo)
  4. Romantización de “Ya no se necesita título” o si trabajas duro lo lograrás o “X% de una de las mejores empresas en el mundo M no tiene título”, son falacias, ya que los grandes inventores al menos todos hicieron doctorado, es decir tuvieron un hambre de conocimiento, una consistencia que les permitió mover el límite a través de innovación y descubrimientos.
  5. Mostrar al mundo y a las personas con duda, que el conocimiento y la carrera debiese ser algo lineal, donde si sigues una cierta receta llegarás a lo que se define como éxito en redes profesionales (como LinkedIn por ejemplo)

Este último punto me preocupa bastante, porque muchas personas que cumplen ciertos patrones del Efecto Dunning–Kruger quedan en posiciones donde tienen a cargo personas y gente que confía en su criterio (esto lo he visto más en latinoamérica). Esto puede causar varios problemas en el largo plazo, en especial a terceras personas. Lo que me gustaría aclarar es que el camino al conocimiento no es lineal y no hay receta. No importa que tengas 50 certificaciones de diferentes proveedores cloud eso no te va a hacer un experto en procesamiento distribuído, diseño de sistemas de ML, inteligencia artificial, etc.

Abstracción Conocimiento

Fig 1: Abstracción del camino hacia el conocimiento.

El engaño que menciono al inicio de este post lo muestro en la figura 1. El creer que siguiendo ciertos pasos, y recetas voy a llegar desde un punto A hasta un punto B (ej. From Zero to Hero). En general, mi observación en los artículos, entradas de blog, que se encuentran en internet, se comienza con una premisa falsa, enunciando que la creencia general es el pensamiento que para ir de A hasta B (conocimiento, éxito, etc) es una línea recta. Luego, a partir de perturbaciones, se genera una romantización, probablemente recomendando un camino de vida, receta (ej. consigue X certificaciones, toma estos cursos de Udemy, haz tal bootcamp, los títulos no sirven, etc.), de tal forma que se muestra un camino “difícil” utilizando expresiones/palabras activadoras de emociones (como esfuerzo, trabajo duro, vamos por más). Si se racionaliza esto, en el fondo, no es muy distinto que la premisa que el camino al “éxito” no es una línea recta y fue “refutada”, en esencia con la misma premisa. Yo prefiero ser realista, pero no negativo. Mi experiencia al menos en lo técnico y mis aspiraciones en el conocimiento han sido más parecido a una caminata aleatoria (como camina un ebrio). A veces avanzo, a veces retrocedo, a veces siento que no sé nada, a veces siento que entendí todo mal, por lo que avanzo retrocedo, vuelvo a avanzar y retrocedo el cuádruple quedando peor que cuando empecé. Aunque en algún punto, la experiencia, tanto mía como de otros (ej. libros, artículos) me permiten cambiar el momentum, la exploración y la cosecha, que me permiten finalmente llegar hasta un punto B. Si luego quiero llegar a C, ¿significa que es posible que retroceda hasta A y frustrarme? Probablemente sí.

Volviendo al mundo TI, es lo que me molesta un poco de algunos desarrolladores o ingenieros de software, que creen que por ser ellos deben contratarlos en ciertos roles, y que por estar X años haciendo algo (la falacia del conocimiento lineal) merecer estar en una posición. Estos en general se frustran con desafíos técnicos en vivo, la verdad en mi opinión porque no quieren admitir que no manejan algo en su totalidad, y no quieren hacer el real esfuerzo de estudiar o marcar la diferencia. A mi me hace cuestionar, ¿tenemos asegurado un camino siguiendo una receta? ¿Qué significa realmente la experiencia? ¿Se puede únicamente aprender haciendo (aquí es donde entra el árgumento del cirujano o la variación del piloto de avión)? ¿Por qué entender los fundamentos de una ciencia a través de la información existente es mucho más complejo que terminar un curso online?

He hablado mucho de los cursos online y la verdad, repito que no tengo nada contra ellos. Es más tengo mi colección de certificados (más de 70 en distintas disciplinas jaja), que cuando me engañaba a mi mismo solía “presumir” en alguna que otra red social. El porrazo me lo pegué cuando llegó el momento realmente de hacer algo que moviera un poco el estado del arte (tesis de magíster en ciencias, no profesional). Ello me hizo explorar la maravilla del conocimeinto y las abstracciones y encontrar un estilo de aprendizaje adecuado para mí.

Para cerrar, me quedo con una frase que escuché por ahí: El trabajar duro y esforzarte, no te garantiza que llegarás a la meta que te propones, pero si no haces nada de seguro no lo lograrás.

¿Consejos para el porrazo?

Algunos materiales que más me tocaron en mi inicio del viaje hacia el conocimiento:

El ejercicio del día

Dada la raíz de un árbol binario, retorne el recorrido en orden zig-zag de sus nodos (es decir, de izquierda a derecha, luego de derecha a izquierda y así hasta recorrer todos los nodos alternando entre niveles).

Ejemplo:

image

Entrada: raíz (nodo con valor 3)
Salida: [[3],[20,9],[15,7]]

Como referencia, la clase nodo puede definirse como:

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right
Ver Solución

Caso de Borde

Primero debemos comenzar con el caso trivial, en que la raíz del árbol es null (caso de borde). En este caso retornamos una lista vacía, ya que no hay nodos que procesar.

Caso General

Para el caso general, podemos reconocer que es una variación de recorrer el árbol en post-orden (post order traversal). Consideremos el ejemplo mostrado en la siguiente figura:

Alt text

En este ejemplo, primero visitaríamos la raíz del árbol (nodo cuyo valor es 3). Luego expandiríamos este nodo, y podríamos visitar el nodo 9 o el nodo 20. Sin embargo, como visitamos los nodos en zig-zag, el órden sería [20, 9] en el nivel 2. Debemos tener en cuenta, que cada nodo a visitar se expande y deberán visitarse los hijos izquierdo y derecho de cada nodo en cada paso. Si nos detenemos a pensar, este es un caso de búsqueda en anchura o BST (Breadth First Search).

En BST podemos visitar los nodos del árbol por nivel, partiendo desde la raíz hasta los niveles más profundos, como se muestra en la siguiente animación:

Alt text

Básicamente, debemos expandir los nodos en el órden en que se van descubriendo, y generar la lista de resultados y alternar de acuerdo al nivel. Como abstracción, supongamos que un nodo tiene el nivel al que pertenece, comenzando desde el nivel 1. Para procesar los nodos en el orden en que se descubren, necesitamos una estructura de datos tipo FIFO (first in first out), por lo cual utilizaremos una cola (queue).

Primero debemos chequear si la raíz del árbol es null:

algoritmo ZigZag-Traversal:
  entrada: (root) raíz del árbol
  salida: Lista de nodos visitados por nivel en orden Zig-Zag

  if root == null:
    return []

El primer elemento que agregamos a la cola es la raíz del árbol, además debemos inicializar la salida como una lista vacía y un flag para indicar el orden en que estamos recorriendo el nivel (por convención lo llamamos left):

algoritmo ZigZag-Traversal:
  entrada: (root) raíz del árbol
  salida: Lista de nodos visitados por nivel en orden Zig-Zag

  if root == null:
    return []

  queue = new Queue()
  queue.enqueue(root)
  output = []
  nodes_in_level = []
  left = False
  current_level = root.level

Procesamos la cola, mientras tenga elementos que procesar (es decir, no hayamos recorrido todo el árbol) y vamos agregando nodos a medida que vamos descubriendo (es decir, si el nodo expandido tiene hijos, se agregan a la cola):

algoritmo ZigZag-Traversal:
  entrada: (root) raíz del árbol
  salida: Lista de nodos visitados por nivel en orden Zig-Zag

  if root == null:
    return []

  queue = new Queue()
  queue.enqueue(root)
  output = []
  nodes_in_level = []
  left = False
  current_level = root.level

  while not queue.empty():
    node = queue.dequeue()
    if node.left != null:
      queue.enqueue(node.left)
    if node.right != null:
      queue.enqueue(node.right)

Aquí es donde depende de la implementación anterior. Si encolamos los nodos de izquierda a derecha, entonces el siguiente nodo a procesar siempre será el de más a la derecha. Por lo tanto, cuando el flag left sea False, entonces insertamos los nodos en la lista nodes_in_level en orden inverso. Finalmente el algoritmo queda como:

algoritmo ZigZag-Traversal:
  entrada: (root) raíz del árbol
  salida: Lista de nodos visitados por nivel en orden Zig-Zag

  if root == null:
    return []

  queue = new Queue()
  queue.enqueue(root)
  output = []
  nodes_in_level = []
  left = False
  current_level = root.level

  while not queue.empty():
    node = queue.dequeue()
    if node.left != null:
      queue.enqueue(node.left)
    if node.right != null:
      queue.enqueue(node.right)

    if node.level == curr_level:
      if not left:
        nodes_in_level.insert(0, node.val)
      else:
        nodes_in_level.append(node.val)
    else:
      left = not left
      result.append(nodes_in_level)
      nodes_in_level = [node.val]
      curr_level = level
  if not nodes_in_level.empty():
    result.append(nodes_in_level)
  return result

Implementación tentativa en python

"""Zig-zag traversal implementation."""
from typing import List, Optional

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def zigzag_level_order(root: Optional[TreeNode]) -> List[List[int]]:
    """Traverse tree in zig-zag order.
    Returns a list where each element is a list of nodes in a given
    depth of the tree, in Zig-Zag order, starting from left to right
    and alternating.
    :param root: Root of the binary tree
    :type root: Optional[TreeNode]
    :return: A list of list of nodes in each depth.
    :rtype: List[List[int]]
    """
    if root is None:
        return []

    queue = []
    queue.append((root, 0))
    result = []
    left = True
    nodes_in_level: List[int] = []
    curr_level = 0
    while queue:
        node, level = queue.pop()
        if level == curr_level:
            if not left:
                nodes_in_level.insert(0, node.val)
            else:
                nodes_in_level.append(node.val)
        else:
            left = not left
            result.append(nodes_in_level)
            nodes_in_level = [node.val]
            curr_level = level
        if node.left is not None:
            queue.insert(0, (node.left, level + 1))
        if node.right is not None:
            queue.insert(0, (node.right, level + 1))
    if nodes_in_level:
        result.append(nodes_in_level)
    return result