logolinq
0

Linq vs Foreach: una experiencia personal

Hace algún tiempo, en una entrevista de trabajo, se me planteó un problema que se podía resolver utilizando LINQ o utilizando dos bucles anidados de toda la vida. De primeras, la resolución del problema no la vi clara utilizando LINQ . Pensé varias posibles soluciones, pero no daba con la tecla y se me empezó a aparecer un demonio en el hombro izquierdo que me susurraba: “usa dos bucles, sabes cómo hacerlo”. Cierto, la solución de los dos bucles estaba clara y se veía a la primera, además, el código quedaría muy limpio y sencillo de entender.

Había leído en muchos sitios (como este, o este) acerca del mejor rendimiento de los bucles for sobre LINQ, pero tengo que reconocerlo, soy un fan incondicional, así que espanté los demonios y me propuse hacerlo en LINQ. Obviamente, no me cogieron, así que me planteé realizar una prueba para este blog, que recogiera las diferencias de rendimiento entre ambas versiones.

El problema

Partiendo de un conjunto de usuarios existentes, debemos encontrar los que están duplicados. Considerando como duplicados, aquellos que tengan el mismo e-mail y el mismo nombre de usuario, pero distinta tarjeta de crédito. La entidad usuario tendría esta pinta:

De primeras, AddressEntity nos importa poco para este ejercicio, así que vamos a obviarlo. User_ID es el típico identificador autoincremental.

Solución con dos bucles foreach anidados

Esta solución apareció por sí sola tras pensarlo durante unos segundos, así que no necesita demasiada explicación. Un bucle que recorre todos los usuarios y otro que, para cada usuario, comprueba si hay otro con el mismo e-mail y nombre de usuario, pero distinta tarjeta de crédito. Lo he separado en dos métodos, para que se lea mejor:

Solución LINQ

La solución que se me ocurrió, después de varias horas pensando y muchos intentos fallidos, consiste en utilizar GroupBy para detectar los elementos que son iguales entre sí y luego volverlos a agrupar, para detectar los que no son del todo iguales. Básicamente, lo que hice fue:

  • Agrupar por UserName, Email y CreditCard. Tendríamos n conjuntos de m usuarios “cuasi” repetidos.
  • Pero hay que recordar que sólo consideramos repetidos aquellos que tienen distinta tarjeta de crédito.
  • Así que volvemos a agrupar, cada uno de los n grupos, únicamente por UserName y Email.
  • Después de esto, todos los grupos que contengan más de un elemento contienen usuarios duplicados.

Parece un poco lioso, pero seguro que con el código se entiende a la primera:

 

El final sorprendente

Con todo funcionando correctamente, era hora de medir tiempos. Para agilizar el asunto, creé una prueba unitaria de cada método y generé un fichero con 10000 usuarios aleatorios, que sería mi fuente de datos. Realicé la lectura del fichero en el constructor, para que no afectase al resultado de las pruebas, así que el input era directamente una lista de usuarios. Luego coloqué manualmente algunos usuarios repetidos en el fichero generado y, tras añadir unas líneas para medir los tiempos, las pruebas tenían este aspecto:

Sorpresa: LINQ ganó por goleada. Las pruebas con LINQ tardaban una media de 0.036 segundos, mientras que todas las pruebas con los bucles tardaban más de 5 segundos. Como siempre, tenéis la solución subida a mi GitHub, para el que sienta más curiosidad y quiera comprobarlo.

 

Moraleja

Sé que la estrategia con los bucles anidados puede mejorarse para aumentar su rendimiento y que utilizar GroupBy es hacer un poco de trampa, pero tras este ejercicio me quedé con una idea importante que suelo aplicar siempre en mi trabajo: comprueba las cosas.

No basta con leer cien posts sobre un asunto o buscar opiniones en StackOverflow, hay que probarlo para cada caso. Lo más probable es que los bucles sean más rápidos en la mayoría de ocasiones, pero en este caso y teniendo en cuenta mis requerimientos, era mejor utilizar LINQ y nunca lo hubiera sabido si no lo hubiera comprobado. Por eso es tan importante tener tiempo para desarrollar con tranquilidad y con la mente abierta, sin prisas ni plazos agobiantes, barajando todas las opciones y comprobando cómo cada decisión afecta a la calidad de nuestro producto final.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *