Del básico al intermedio: Como burbujas de jabón
Introducción
En el artículo anterior, Del básico al intermedio: Navegando por la SandBox, vimos cómo podíamos navegar dentro de una de las SandBox mantenidas por MetaTrader 5. Se mostró que podíamos hacerlo de dos maneras totalmente distintas y con diferentes objetivos. Una de las formas de realizar esta navegación era mediante un cuadro de diálogo que usaba el propio sistema operativo, lo que la convertía en la manera más práctica, fácil y simple de navegar dentro de la SandBox.
Sin embargo, también se mostró una segunda forma de navegación. Esta forma se implementaba dentro del propio código de la aplicación que tú estuvieras desarrollando, lo que la convierte en una manera algo más complicada, precisamente porque el sistema de navegación, en este caso, no tendría como objetivo una interacción fácil y rápida con el usuario de la aplicación, sino un objetivo muy distinto: conocer qué tipos de archivos existen y cómo está organizada la estructura de directorios dentro de la SandBox.
Esta segunda forma de abordar, o mejor dicho, navegar dentro de la SandBox, nos abre algunas puertas que podemos explorar un poco ahora. Esto ocurre porque, aunque todo parezca perfecto cuando probamos una aplicación, en la práctica, en muchos casos, todo puede volverse bastante caótico. Los archivos y directorios pueden presentarse de forma totalmente mezclada, así como, y en este caso es mucho más probable, nombres que no estén ordenados.
Esta suele ser la situación más común: podemos querer crear un filtro, por ejemplo, a partir de la fecha de creación o modificación del archivo o directorio. Esto se haría con fines de búsqueda, para presentar los archivos y directorios en cierto orden y facilitar así la localización del tipo de información buscada. De la manera en que se implementó el código que vimos en el artículo anterior, no tenemos una forma ordenada de presentar los resultados, ni siquiera un filtrado, por simple que fuera de implementar.
Para ordenar cualquier tipo de valor, es decir, crear una forma ordenada de presentar los resultados, tenemos que incorporar algunos elementos al código. Este tipo de situación, muchas veces, es la parte en la que muchos principiantes acaban completamente desorientados. Esto se debe al tipo de tarea que debe realizarse. No porque sea difícil, sino porque existen varias formas distintas de hacer lo mismo. Sin embargo, puede ser algo confuso hacerlo en uno u otro lenguaje de programación. Muchas veces, el código debe adaptarse a partir de otro lenguaje. Algo que muchos con menos experiencia no logran hacer.
Aquí, en cambio, seremos bastante directos. No intentaré nada ni introduciré un concepto nuevo. Solo veremos cómo usar algo muy común en programación, pero que muchos principiantes no tienen idea de lo que ocurre internamente. Por lo tanto, pasemos a un nuevo apartado para comenzar la parte divertida del artículo. Porque este sí es un tema con el que me divierto bastante cuando necesito utilizarlo en la práctica.
Como burbujas de jabón
Muy bien, empecemos hablando de algo que, con toda seguridad, ya debes haber utilizado. Me refiero a poner un array de números cualquiera en orden creciente. ¿Y por qué estoy mencionando esto? El motivo es simple, mi estimado lector. En la biblioteca de funciones y procedimientos de MQL5, tenemos una función llamada ArraySort. Esta función tiene como objetivo ordenar el contenido del array de forma creciente.
Para quien todavía no haya visto esta función en funcionamiento, podemos ver su funcionamiento con un pequeño código, que se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. uchar info[10]; 07. 08. MathSrand(GetTickCount()); 09. 10. for (uint c = 0; c < info.Size(); c++) 11. info[c] = (uchar) MathRand(); 12. 13. Print("Array content before ordering..."); 14. ArrayPrint(info); 15. 16. ArraySort(info); 17. 18. Print("Array content after sorting..."); 19. ArrayPrint(info); 20. } 21. //+------------------------------------------------------------------+
Código 01
En el código 01, usamos el generador de números pseudoaleatorios para inicializar un pequeño array. El array se declara en la línea seis. La inicialización la realiza el bucle de la línea diez. Pero lo que nos interesa aquí son justamente las líneas que vienen a continuación. En ellas, primero imprimimos el array antes de ordenar sus valores y después de ordenarlos. Como la generación se basa en valores aleatorios, lo más probable es que el resultado no sea como el que puedes observar en la siguiente imagen.

Imagen 01
Esto ocurre porque, en cada ejecución, se generará una nueva secuencia de valores. Pero el objetivo es precisamente observar que los valores fueron ordenados de forma creciente. Esto ocurre por la función de la línea 16, que aparece en el código 01.
Pero ¿qué tiene esto que ver con nuestra idea sobre la navegación en la SandBox? Pues bien, estimado lector, esta es la parte interesante. Muchos, por ser principiantes, puede que no entiendan un punto importante aquí. Cuando los datos se presentan en orden, ya sea creciente o decreciente, es necesario pasarlos por algún mecanismo de ordenación. Entender cómo funcionan estos mecanismos te ayudará a resolver diversos problemas. Esto ocurre porque no siempre el lenguaje, sea cual sea, contiene una implementación que te permita ordenar cualquier tipo de dato. Si entiendes cómo funciona el mecanismo de ordenación, puedes llegar a crear una función o procedimiento que permita generalizar la ordenación. Esto permite ordenar tanto datos numéricos como texto, entre otros tipos de datos.
Por ejemplo, si intentas cambiar el código 01 por el siguiente código, notarás que no será posible compilarlo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. string info[10]; 07. 08. MathSrand(GetTickCount()); 09. 10. for (uint c = 0; c < info.Size(); c++) 11. info[c] = (string) MathRand(); 12. 13. Print("Array content before ordering..."); 14. ArrayPrint(info); 15. 16. ArraySort(info); 17. 18. Print("Array content after sorting..."); 19. ArrayPrint(info); 20. } 21. //+------------------------------------------------------------------+
Código 02
Pero ¿por qué? El motivo es precisamente que el compilador no puede encontrar un modelo que permita utilizar datos de tipo string en el array. Esto no se debe al array en sí, sino a la función ArraySort, que no fue implementada ni proyectada para recibir strings a fin de ordenarlas de forma creciente. Pero espera un momento. Entonces, ¿me estás diciendo que, si eliminamos la línea 16 del código 02, podremos obtener un resultado? Esto ocurre porque el compilador puede generar una aplicación ejecutable. Sí, así es, mi estimado lector. Tanto es así que, si se hace esto, podrás ver algo parecido a la siguiente imagen al ejecutar el código 02. Claro está, después de eliminar la línea 16 del código.

Imagen 02
Ahora, observa lo siguiente. En la imagen 02, se muestran strings, pero no aparecen ordenadas de forma creciente. Y el motivo es que no tenemos un mecanismo que realice esa ordenación por nosotros. Ahora, piensa en el siguiente detalle: si, mi estimado lector, por casualidad llegas a implementar una forma de ordenar estos mismos datos de tipo string, aunque contengan valores numéricos, podrás utilizar el mismo mecanismo para ordenar los archivos y directorios, a fin de presentarlos en cierto orden, ya sea creciente o decreciente. Aquí es donde surge la necesidad de conocer los mecanismos de ordenación que pueden implementarse dentro de un lenguaje de programación.
Para simplificar y que todos puedan entender lo que vamos a implementar, ya que el objetivo aquí es ser lo más didáctico posible y presentar el material de forma fácil de comprender, incluso para un principiante, avanzaremos poco a poco. Así, todos podrán seguir el proceso y entender lo que estamos haciendo. Entender lo que ocurre es más importante que simplemente copiar el código cuando necesites hacer algo parecido. Así que vamos a comenzar con el siguiente código.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. uchar info[10]; 07. 08. MathSrand(512); 09. 10. for (uint c = 0; c < info.Size(); c++) 11. info[c] = (uchar) MathRand(); 12. 13. Print("Array content before ordering..."); 14. ArrayPrint(info); 15. 16. BubbleSort(info); 17. 18. Print("Array content after sorting..."); 19. ArrayPrint(info); 20. } 21. //+------------------------------------------------------------------+ 22. template <typename T> 23. void BubbleSort(T &arr[]) 24. { 25. #define macroSWAP(A, B) { \ 26. temp = arr[j - 1]; \ 27. arr[j - 1] = arr[j]; \ 28. arr[j] = temp; \ 29. } 30. 31. uint nElements = arr.Size(); 32. T temp; 33. 34. for (uint i = 1; i < nElements; i++) 35. for (uint j = nElements - 1; j >= i; j--) 36. if (typename(T) == "string") 37. { 38. }else 39. if (arr[j - 1] > arr[j]) 40. macroSWAP(arr[j - 1], arr[j]) 41. 42. #undef macroSWAP 43. } 44. //+------------------------------------------------------------------+
Código 03
Este código 03 será nuestro punto de partida. Observa que estamos haciendo algo muy parecido a lo presentado en el código 01. Sin embargo, aquí estamos preparando el código de forma que obtendrás los mismos resultados que aparecerán en las siguientes imágenes. Esto ocurre porque, aunque todavía usamos el generador pseudoaleatorio para inicializar el array, iniciamos el generador en un valor determinado. Como puedes observar en la línea ocho.
Ahora presta atención, mi estimado lector. A diferencia de los códigos 01 y 02, este código 03 tiene, en la línea 16, una llamada a un procedimiento que implementamos. Este procedimiento tiene como objetivo ordenar de manera creciente los datos del array. Ahora viene la parte importante: en artículos anteriores, se explicó cómo crear plantillas de funciones y procedimientos. También se explicó cómo podemos aprovechar este tipo de modelado en la práctica. Pues bien, aquí hay un ejemplo de cómo usar ese conocimiento explicado en los artículos anteriores. Por lo tanto, a diferencia de la función de biblioteca ArraySort, que no nos permite ordenar cualquier tipo de valor, aquí tenemos una implementación que nos permitirá hacer esto.
Para hacerlo posible, usamos la prueba de la línea 36 para verificar si ordenamos strings u otro tipo de dato cualquiera. Observa lo siguiente: todavía NO ESTAMOS ORDENANDO DATOS DE TIPO STRING. Pero ya estamos ordenando los demás tipos. Podemos comprobarlo observando el resultado obtenido en la siguiente imagen.

Imagen 03
Aunque el mecanismo implementado en el código no es el mejor que existe, ya que es muy lento para arrays grandes, para nuestra demostración es bastante rápido y perfectamente funcional. Como puede comprobarse en la imagen 03. ¿Eso es todo? Sí, mi estimado lector. Ahora viene la parte divertida: modificar el código para poder ordenar datos de tipo string. Antes de hacer esto, vamos a dar un pequeño paso. Este paso aparece en el siguiente código.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. string info[10]; 07. 08. MathSrand(512); 09. 10. for (uint c = 0; c < info.Size(); c++) 11. info[c] = (string) (uchar) MathRand(); 12. 13. Print("Array content before ordering..."); 14. ArrayPrint(info); 15. 16. BubbleSort(info); 17. 18. Print("Array content after sorting..."); 19. ArrayPrint(info); 20. } 21. //+------------------------------------------------------------------+ 22. template <typename T> 23. void BubbleSort(T &arr[]) 24. { 25. #define macroSWAP(A, B) { \ 26. temp = arr[j - 1]; \ 27. arr[j - 1] = arr[j]; \ 28. arr[j] = temp; \ 29. } 30. 31. uint nElements = arr.Size(); 32. T temp; 33. 34. for (uint i = 1; i < nElements; i++) 35. for (uint j = nElements - 1; j >= i; j--) 36. if (typename(T) == "string") 37. { 38. }else 39. if (arr[j - 1] > arr[j]) 40. macroSWAP(arr[j - 1], arr[j]) 41. 42. #undef macroSWAP 43. } 44. //+------------------------------------------------------------------+
Código 04
Observa qué cambió entre el código 03 y este código 04. Todos los cambios realizados tienen como objetivo permitir que los valores utilizados sean los mismos en ambos casos. Aun así, todavía no estamos ordenando las strings. Sin embargo, el resultado de la ejecución de este código 04 se presenta a continuación.

Imagen 04
Ahora quiero que prestes mucha atención, mi estimado lector. Lo que se hará puede resultar algo confuso para muchos. Esto se debe a que, a diferencia de ordenar valores de tipo numérico, la ordenación de strings no es un proceso tan directo. Debe hacerse de una manera muy específica para que la ordenación funcione correctamente. Sin embargo, puedes observar que, tanto en el código 03 como en el código 04, hay, entre las líneas 37 y 38, un espacio donde podemos implementar el código de ordenación propiamente dicho. Para entender cómo debe hacerse esta ordenación, necesito que primero entiendas el tipo de resultado que obtenemos al usar ciertas formas de implementarla. Primero, vamos a usar la función StringCompare, ya que, obviamente, sería la primera opción en la que pensaríamos. Para hacerlo, usamos el siguiente código.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. string info[10]; 07. 08. MathSrand(512); 09. 10. for (uint c = 0; c < info.Size(); c++) 11. info[c] = (string) (uchar) MathRand(); 12. 13. Print("Array content before ordering..."); 14. ArrayPrint(info); 15. 16. BubbleSort(info); 17. 18. Print("Array content after sorting..."); 19. ArrayPrint(info); 20. } 21. //+------------------------------------------------------------------+ 22. template <typename T> 23. void BubbleSort(T &arr[]) 24. { 25. #define macroSWAP(A, B) { \ 26. temp = arr[j - 1]; \ 27. arr[j - 1] = arr[j]; \ 28. arr[j] = temp; \ 29. } 30. 31. uint nElements = arr.Size(); 32. T temp; 33. 34. for (uint i = 1; i < nElements; i++) 35. for (uint j = nElements - 1; j >= i; j--) 36. if (typename(T) == "string") 37. { 38. if (StringCompare(arr[j - 1], arr[j]) > 0) 39. macroSWAP(arr[j - 1], arr[j]) 40. }else 41. if (arr[j - 1] > arr[j]) 42. macroSWAP(arr[j - 1], arr[j]) 43. 44. #undef macroSWAP 45. } 46. //+------------------------------------------------------------------+
Código 05
Cuando ejecutamos este código, obtendremos el resultado de la siguiente imagen.

Imagen 05
¿Qué locura es esta? Aparentemente, aquí no hay una ordenación. Aunque, a primera vista, sí logramos ordenar los elementos. Si observo con más atención, noto que hay algo extraño en esta ordenación.
Así es, mi estimado lector, tienes razón. El código logra ordenar los valores que contiene el array de strings. Pero hay algo extraño, precisamente el hecho de que 37 y 78 son menores que 113. Sin embargo, estos dos valores se colocan al final de la secuencia. ¿Por qué? El motivo es que la ordenación solo tiene en cuenta el contenido de la string. Por eso, todos los valores entre 113 y 189 quedan en el orden correcto. Sin embargo, cuando dejamos de tener en cuenta la cantidad de caracteres que contiene la string, la ordenación resulta algo extraña, aunque haya funcionado.
La parte interesante es que, si utilizas palabras tomadas de un diccionario, en lugar de valores numéricos, como estamos haciendo aquí, notarás que la ordenación sí funcionará como se esperaba. Sin embargo, para lo que hacemos, o intentamos hacer, la ordenación no fue perfecta al cien por ciento. Bien, para resolver esto, también tenemos que incluir la cantidad de caracteres que contiene la string como uno de los filtros que se utilizará. Así, el sistema de burbujas podrá generar una ordenación correcta del array de strings. El cambio que debe hacerse aparece en el siguiente código.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. string info[10]; 07. 08. MathSrand(512); 09. 10. for (uint c = 0; c < info.Size(); c++) 11. info[c] = (string) (uchar) MathRand(); 12. 13. Print("Array content before ordering..."); 14. ArrayPrint(info); 15. 16. BubbleSort(info); 17. 18. Print("Array content after sorting..."); 19. ArrayPrint(info); 20. } 21. //+------------------------------------------------------------------+ 22. template <typename T> 23. void BubbleSort(T &arr[]) 24. { 25. #define macroSWAP(A, B) { \ 26. temp = arr[j - 1]; \ 27. arr[j - 1] = arr[j]; \ 28. arr[j] = temp; \ 29. } 30. 31. uint nElements = arr.Size(); 32. T temp; 33. 34. for (uint i = 1; i < nElements; i++) 35. for (uint j = nElements - 1; j >= i; j--) 36. if (typename(T) == "string") 37. { 38. if ((StringLen(arr[j - 1]) > StringLen(arr[j]))) 39. macroSWAP(arr[j - 1], arr[j]) 40. if ((StringCompare(arr[j - 1], arr[j]) > 0) && (StringLen(arr[j - 1]) == StringLen(arr[j]))) 41. macroSWAP(arr[j - 1], arr[j]) 42. }else 43. if (arr[j - 1] > arr[j]) 44. macroSWAP(arr[j - 1], arr[j]) 45. 46. #undef macroSWAP 47. } 48. //+------------------------------------------------------------------+
Código 06
Muy bien, entonces, cuando ejecutemos este código 06, obtendremos el siguiente resultado.

Imagen 06
Bien, realmente obtuvimos el resultado deseado y esperado. Pero, en cierto modo, no entiendo algo. Dijiste hace poco que el código 05 puede ordenar, o mejor dicho, clasificar palabras extraídas de un diccionario en orden creciente. Sin embargo, no conseguíamos poner los valores numéricos en el orden correcto, como se observa claramente en el resultado de la imagen 05. Sin embargo, al utilizar esta versión modificada, del código 06, pudimos ordenar los datos correctamente. Pero ¿acaso, al hacer esta modificación del código 06, no volvemos incapaz al código de ordenar, de forma adecuada, palabras tomadas de un diccionario? Bien, tal vez muchos de los que se creen programadores podrían decir: Claro que no. El código se modificó, pero sigue siendo capaz de ordenar las palabras de la manera correcta.
Sin embargo, quienes tienen dudas sobre lo programado pueden pensar perfectamente que el código podría no funcionar correctamente. Al tener esta duda y cuestionar la viabilidad de los cambios hechos en la estructura del código, acaban descubriendo, por así decirlo, que no existe una solución que pueda cubrir todos los casos. También descubren que, en algún momento, saber y comprender cómo funcionan los mecanismos internamente puede ser la diferencia entre un resultado adecuado y un resultado mediocre.
Por lo tanto, hagamos una prueba simple: modifiquemos nuevamente el código 06 para comprobar si podemos usar o no esta implementación en cualquier caso. Para ello, basta con que cambiemos el código 06 como muestra el siguiente código.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. string info[] = {"value", "data", "time", "assembly", "checking" }; 07. 08. Print("Array content before ordering..."); 09. ArrayPrint(info); 10. 11. BubbleSort(info); 12. 13. Print("Array content after sorting..."); 14. ArrayPrint(info); 15. } 16. //+------------------------------------------------------------------+ 17. template <typename T> 18. void BubbleSort(T &arr[]) 19. { 20. #define macroSWAP(A, B) { \ 21. temp = arr[j - 1]; \ 22. arr[j - 1] = arr[j]; \ 23. arr[j] = temp; \ 24. } 25. 26. uint nElements = arr.Size(); 27. T temp; 28. 29. for (uint i = 1; i < nElements; i++) 30. for (uint j = nElements - 1; j >= i; j--) 31. if (typename(T) == "string") 32. { 33. if ((StringLen(arr[j - 1]) > StringLen(arr[j]))) 34. macroSWAP(arr[j - 1], arr[j]) 35. if ((StringCompare(arr[j - 1], arr[j]) > 0) && (StringLen(arr[j - 1]) == StringLen(arr[j]))) 36. macroSWAP(arr[j - 1], arr[j]) 37. }else 38. if (arr[j - 1] > arr[j]) 39. macroSWAP(arr[j - 1], arr[j]) 40. 41. #undef macroSWAP 42. } 43. //+------------------------------------------------------------------+
Código 07
Ahora tenemos algo un poco diferente. Sin embargo, la única diferencia real está en los valores del array de strings. En este caso, tenemos palabras que con toda seguridad sabes en qué orden deberían aparecer, considerando que tenemos una ordenación creciente. Pero, para tu sorpresa, al ejecutar el código 07, obtenemos el resultado de la siguiente imagen.

Imagen 07
Sin duda alguna, el resultado de la imagen 07 no coincide con el orden en que estas mismas palabras aparecerían en un diccionario. Aun así, aquí hubo una ordenación, del mismo modo en que había ocurrido durante la ejecución del código 05, cuyo resultado se mostró en la imagen 05. Por esta razón, es importante que, mi estimado lector, entiendas que no siempre la respuesta devuelta por una aplicación será correcta, ni necesariamente equivocada. El gran detalle es: ¿Qué tipo de respuesta se esperaba de la aplicación? Dependiendo del caso, puede que la respuesta sea adecuada o no para el objetivo que se quiere alcanzar. Este no es el caso aquí. Así que tenemos un impasse.
En este caso, un procedimiento creado para realizar una tarea, que, en este caso, consiste en ordenar datos en un array, nos da respuestas que pueden tener sentido o no para nosotros. Podemos hacer una leve y sutil modificación en el código para hacer el proceso un poco más controlable. Esta modificación aparece en el siguiente código.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. string info_01[10]; 07. string info_02[] = {"value", "data", "time", "assembly", "checking" }; 08. //+----------------+ 09. MathSrand(512); 10. 11. for (uint c = 0; c < info_01.Size(); c++) 12. info_01[c] = (string) (uchar) MathRand(); 13. //+----------------+ 14. Print("Number array before ordering..."); 15. ArrayPrint(info_01); 16. 17. BubbleSort(info_01, true); 18. 19. Print("Number array after ordering..."); 20. ArrayPrint(info_01); 21. //+----------------+ 22. Print("Dictionary before ordering..."); 23. ArrayPrint(info_02); 24. 25. BubbleSort(info_02); 26. 27. Print("Dictionary after sorting..."); 28. ArrayPrint(info_02); 29. } 30. //+------------------------------------------------------------------+ 31. template <typename T> 32. void BubbleSort(T &arr[], const bool bUsingLength = false) 33. { 34. #define macroSWAP(A, B) { \ 35. temp = arr[j - 1]; \ 36. arr[j - 1] = arr[j]; \ 37. arr[j] = temp; \ 38. } 39. 40. uint nElements = arr.Size(); 41. T temp; 42. 43. for (uint i = 1; i < nElements; i++) 44. for (uint j = nElements - 1; j >= i; j--) 45. if (typename(T) == "string") 46. { 47. if ((bUsingLength) && ((StringLen(arr[j - 1]) > StringLen(arr[j])))) 48. macroSWAP(arr[j - 1], arr[j]) 49. if ((StringCompare(arr[j - 1], arr[j]) > 0) && ((StringLen(arr[j - 1]) == StringLen(arr[j])) || !bUsingLength)) 50. macroSWAP(arr[j - 1], arr[j]) 51. }else 52. if (arr[j - 1] > arr[j]) 53. macroSWAP(arr[j - 1], arr[j]) 54. 55. #undef macroSWAP 56. } 57. //+------------------------------------------------------------------+
Código 08
Este código 08 es el que permite probar los cambios y su validez, para ordenar un array de strings de la manera esperada. Entonces, veamos qué ocurre cuando ejecutamos este código 08 en el terminal de MetaTrader 5. Al hacerlo, obtendremos el resultado de la siguiente imagen.

Imagen 08
Simplemente perfecto. Observa que tanto el array con strings numéricas como el array con palabras de un diccionario se ordenan de la manera correcta. O mejor dicho, de la manera esperada. Esto antes no era posible. La cuestión principal aquí es precisamente qué fue necesario implementar para que esta ordenación produjera el resultado esperado. Todo lo que hubo que hacer fue añadir una prueba adicional en el bloque donde tratamos datos de tipo string.
Aunque puedas pensar que este tipo de ordenación presentado aquí es un modelo perfecto y que puedes utilizarlo en cualquier caso, quiero que reconsideres esta hipótesis, mi estimado lector. Este método de ordenación es, con diferencia, el peor método que existe. Aunque su código sea muy fácil de entender, degrada muy rápidamente el rendimiento del sistema en su conjunto. Esto ocurre porque, por cada elemento que haya en el array que se debe ordenar, tendremos que ejecutar el bucle una cantidad muy grande de veces.
Para ser exacto, el número de veces que el bucle debe ejecutarse es el número de elementos elevado al cuadrado, en el peor de los casos. Es decir, para ordenar cinco elementos, tenemos que ejecutar el bucle 25 veces. Para diez elementos, es decir, apenas el doble que en el primer caso, las ejecuciones del bucle saltan a 100. Observa que la cantidad de veces que tenemos que ejecutar el bucle crece muy rápidamente a medida que se añaden nuevos elementos al array.
Por este motivo, algunos programadores suelen llamar a este método presentado hasta ahora el infierno de las burbujas. Esto ocurre porque cada nuevo elemento añadido aumenta potencialmente el número de ejecuciones que deben realizarse para que la ordenación realmente tenga éxito.
Aunque esta metodología rudimentaria y poco refinada presentada aquí sea un problema, hay casos en los que no necesitamos ejecutar el bucle tantas veces para obtener un array ordenado en la salida. Esto se debe a que quizá solo sea necesaria alguna iteración de intercambio para que el array quede totalmente ordenado. Por increíble que pueda parecer, un cambio simple y aparentemente poco eficaz en el código puede volver este infierno de las burbujas algo bastante aceptable en ciertas circunstancias.
Para entender cómo este cambio afecta al código, pero principalmente cómo puede hacerlo más rápido, es necesario que, mi estimado lector, hayas comprendido cómo funciona el proceso de ordenación en esta implementación presentada hasta ahora.
Una vez que hayas entendido esto, podemos pasar a la mejora del código. Con esto buscamos permitir que el sistema de ordenación sea, en algunas situaciones, un poco más rápido, o menos exigente en términos de uso de CPU y tiempo de procesamiento.
Bien, para que podamos entender cómo esta modificación que vamos a implementar afecta a la ejecución del código, primero tendremos que implementar una forma de ver qué ocurrió dentro del algoritmo de ordenación. Para ello, cambiaremos el código 08 por el siguiente código.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. string info_01[10]; 07. string info_02[] = {"value", "data", "time", "assembly", "checking" }; 08. //+----------------+ 09. MathSrand(512); 10. 11. for (uint c = 0; c < info_01.Size(); c++) 12. info_01[c] = (string) (uchar) MathRand(); 13. //+----------------+ 14. Print("+----------------+\nNumber array before ordering..."); 15. ArrayPrint(info_01); 16. 17. BubbleSort(info_01, true); 18. 19. Print("Number array after ordering..."); 20. ArrayPrint(info_01); 21. //+----------------+ 22. Print("+----------------+\nDictionary before ordering..."); 23. ArrayPrint(info_02); 24. 25. BubbleSort(info_02); 26. 27. Print("Dictionary after sorting..."); 28. ArrayPrint(info_02); 29. } 30. //+------------------------------------------------------------------+ 31. template <typename T> 32. void BubbleSort(T &arr[], const bool bUsingLength = false) 33. { 34. #define macroSWAP(A, B) { \ 35. temp = arr[j - 1]; \ 36. arr[j - 1] = arr[j]; \ 37. arr[j] = temp; \ 38. } 39. 40. uint nElements = arr.Size(); 41. T temp; 42. uint count = 0; 43. 44. for (uint i = 1; i < nElements; i++) 45. { 46. for (uint j = nElements - 1; j >= i; j--, count++) 47. if (typename(T) == "string") 48. { 49. if ((bUsingLength) && ((StringLen(arr[j - 1]) > StringLen(arr[j])))) 50. macroSWAP(arr[j - 1], arr[j]) 51. if ((StringCompare(arr[j - 1], arr[j]) > 0) && ((StringLen(arr[j - 1]) == StringLen(arr[j])) || !bUsingLength)) 52. macroSWAP(arr[j - 1], arr[j]) 53. }else 54. if (arr[j - 1] > arr[j]) 55. macroSWAP(arr[j - 1], arr[j]) 56. } 57. 58. Print("Number of interactions: ", count); 59. 60. #undef macroSWAP 61. } 62. //+------------------------------------------------------------------+
Código 09
Observa que, en este código 09, solo añadimos una forma de contar cuántas iteraciones ocurrieron dentro del ordenador. Esto se hizo añadiendo la línea 42 y, luego, en la línea 46, contabilizando las iteraciones. Al final, en la línea 58, imprimimos el resultado para saber cuántas iteraciones ocurrieron. Ahora, presta atención a una cosa aquí, mi estimado lector: esta implementación no ejecutará nElements al cuadrado. Esto se debe a que, por cada iteración que ejecute el bucle de la línea 44, se ejecutará una iteración menos en el bucle de la línea 46. Es muy importante que entiendas esto para no confundirte con el valor que se mostrará como número de iteraciones ejecutadas.
Muy bien, entonces, cuando ejecutemos este código, obtendremos como resultado el resultado de la siguiente imagen.

Imagen 09
Lo que realmente nos interesa aquí y en este momento, son las regiones destacadas en la imagen 09. Observa que se produce un determinado número de iteraciones. Ahora pregunto: ¿Es una buena cifra o una mala cifra? Bien, la respuesta a esto es: depende. Aquí hay una cuestión, y a eso quiero llegar. Observa que los valores que deben ordenarse aparentemente están muy desordenados. Pero ¿y si estuvieran más cerca del orden esperado? ¿Eso reduciría el número de iteraciones respecto del resultado anterior? Bien, vamos a probarlo para ver qué ocurre. Para ello, usamos el siguiente código.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. string info_01[] = {"37", "78", "0", "145", "113", "139", "148", "247", "174", "189"}; 07. string info_02[] = {"value", "data", "time", "assembly", "checking" }; 08. //+----------------+ 09. Print("+----------------+\nNumber array before ordering..."); 10. ArrayPrint(info_01); 11. 12. BubbleSort(info_01, true); 13. 14. Print("Number array after ordering..."); 15. ArrayPrint(info_01); 16. //+----------------+ 17. Print("+----------------+\nDictionary before ordering..."); 18. ArrayPrint(info_02); 19. 20. BubbleSort(info_02); 21. 22. Print("Dictionary after sorting..."); 23. ArrayPrint(info_02); 24. } 25. //+------------------------------------------------------------------+ 26. template <typename T> 27. void BubbleSort(T &arr[], const bool bUsingLength = false) 28. { 29. #define macroSWAP(A, B) { \ 30. temp = arr[j - 1]; \ 31. arr[j - 1] = arr[j]; \ 32. arr[j] = temp; \ 33. } 34. 35. uint nElements = arr.Size(); 36. T temp; 37. uint count = 0; 38. 39. for (uint i = 1; i < nElements; i++) 40. { 41. // interact = false; 42. for (uint j = nElements - 1; j >= i; j--, count++) 43. if (typename(T) == "string") 44. { 45. if ((bUsingLength) && ((StringLen(arr[j - 1]) > StringLen(arr[j])))) 46. macroSWAP(arr[j - 1], arr[j]) 47. if ((StringCompare(arr[j - 1], arr[j]) > 0) && ((StringLen(arr[j - 1]) == StringLen(arr[j])) || !bUsingLength)) 48. macroSWAP(arr[j - 1], arr[j]) 49. }else 50. if (arr[j - 1] > arr[j]) 51. macroSWAP(arr[j - 1], arr[j]) 52. } 53. 54. Print("Number of interactions: ", count); 55. 56. #undef macroSWAP 57. } 58. //+------------------------------------------------------------------+
Código 10
Observa que, en la línea seis de este código 10, estamos definiendo los mismos valores mostrados en la imagen 09. Están un poco menos desordenados, por así decirlo. A primera vista, podrías pensar que esto aceleraría el proceso de ordenación, o que necesitara menos iteraciones para completarse. Pero, para tu sorpresa, cuando se ejecute este código 10, el resultado será el de la siguiente imagen.

Imagen 10
Es decir, aunque aparentemente necesitemos menos iteraciones para ordenar el array de la línea seis de este código 10, seguimos ejecutando el mismo número de iteraciones. Esto, para muchos, puede parecer extraño. Pero entonces, ¿qué está mal en este código 10 para que no use menos iteraciones? Bien, en realidad, mi estimado lector, no hay nada malo en el código. Solo falta añadir una prueba cuyo objetivo sea saber si el array está ordenado o no.
De acuerdo, pero esta comprobación parece algo complicada, ya que, para saber si el array está ordenado o no, tendríamos que verificarlo. Esto acabaría llevando tiempo y exigiría una iteración adicional. Esto no es deseable, ya que justamente buscamos reducir el número de iteraciones. Bien, pero entonces, ¿cómo podríamos resolver esto? A mi modo de ver, esta es la parte interesante. Precisamente porque, para resolver este problema, solo necesitamos realizar un pequeño cambio en el código. Ahora observa, en el siguiente código, el cambio que sería necesario hacer.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. string info_01[] = {"37", "78", "0", "145", "113", "139", "148", "247", "174", "189"}; 07. string info_02[] = {"value", "data", "time", "assembly", "checking" }; 08. //+----------------+ 09. Print("+----------------+\nNumber array before ordering..."); 10. ArrayPrint(info_01); 11. 12. BubbleSort(info_01, true); 13. 14. Print("Number array after ordering..."); 15. ArrayPrint(info_01); 16. //+----------------+ 17. Print("+----------------+\nDictionary before ordering..."); 18. ArrayPrint(info_02); 19. 20. BubbleSort(info_02); 21. 22. Print("Dictionary after sorting..."); 23. ArrayPrint(info_02); 24. } 25. //+------------------------------------------------------------------+ 26. template <typename T> 27. void BubbleSort(T &arr[], const bool bUsingLength = false) 28. { 29. #define macroSWAP(A, B) { \ 30. temp = arr[j - 1]; \ 31. arr[j - 1] = arr[j]; \ 32. arr[j] = temp; \ 33. interact = true; \ 34. } 35. 36. uint nElements = arr.Size(); 37. T temp; 38. bool interact = true; 39. uint count = 0; 40. 41. for (uint i = 1; (i < nElements) && interact; i++) 42. { 43. interact = false; 44. for (uint j = nElements - 1; j >= i; j--, count++) 45. if (typename(T) == "string") 46. { 47. if ((bUsingLength) && ((StringLen(arr[j - 1]) > StringLen(arr[j])))) 48. macroSWAP(arr[j - 1], arr[j]) 49. if ((StringCompare(arr[j - 1], arr[j]) > 0) && ((StringLen(arr[j - 1]) == StringLen(arr[j])) || !bUsingLength)) 50. macroSWAP(arr[j - 1], arr[j]) 51. }else 52. if (arr[j - 1] > arr[j]) 53. macroSWAP(arr[j - 1], arr[j]) 54. } 55. 56. Print("Number of interactions: ", count); 57. 58. #undef macroSWAP 59. } 60. //+------------------------------------------------------------------+
Código 11
Cuando ejecutamos este código 11, obtendremos como resultado el resultado siguiente.

Imagen 11
Observa que, ahora, en la imagen 11, hubo una reducción del número de iteraciones, pero sin grandes cambios en el propio código. Todo lo que tuvimos que hacer fue añadir la línea 38 y, luego, modificar algunos puntos del código. Como el bucle de la línea 41 y la macro. Ahora, observa que el propio código verificará si realizamos algún intercambio en el array o no. Si el bucle de la línea 44 se ejecuta y la línea 33 no se ejecuta en ninguna iteración, tendremos la garantía de que el array ya está debidamente ordenado. Así, en la siguiente iteración del bucle de la línea 41, podremos finalizar el procedimiento de ordenación. Interesante, ¿verdad?
Consideraciones finales
En este artículo, se explicó un mecanismo muy simple y fácil de entender, cuyo propósito es ordenar un array cualquiera. Se mostró que no siempre el resultado presentado es el que realmente esperamos obtener. Por eso es necesario adaptar la implementación para obtener los resultados adecuados.
También se mostró que, con muy poco trabajo, podemos convertir una implementación que, en principio, sería lenta en una un poco más ágil. Esto se da cuando la situación permite procesar ciertos arrays con mayor agilidad. Puede ocurrir que, con pocos ajustes, el resultado esperado ya quede formado. Esto elimina la ejecución de nuevas iteraciones, ya que estas ya no cambiarían el resultado obtenido.
Como buena parte de lo que se vio aquí corresponde a cambios con fines didácticos, en el anexo tendrás solo los códigos, ya en su versión final. Así que, si deseas entender por qué se siguió uno u otro camino, basta con usar los códigos presentados íntegramente en el texto de este artículo. Por lo demás, solo queda estudiar y practicar lo que se vio para entender cómo funciona el mecanismo mostrado aquí.
| Archivo | Descripción |
|---|---|
| Code 01 | Demostración de ordenación simple |
| Code 02 | Demostración de ordenación simple |
| Code 03 | Demostración de ordenación simple |
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/16418
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Optimización Extrema — Extremal Optimization (EO)
Simulación de mercado: Position View (VII)
Simulación de mercado: Position View (VIII)
De novato a experto: Noticias animadas utilizando MQL5 (VI) Estrategia de órdenes pendientes para el trading basado en noticias
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso