От начального до среднего уровня: Словно мыльные пузыри
Введение
В предыдущей статье, От начального до среднего уровня: Навигация по песочнице, мы рассмотрели, как можно перемещаться внутри одной из песочниц, поддерживаемых MetaTrader 5. Также было показано, что мы можем сделать это двумя совершенно разными способами и с разными целями. Одним из способов навигации было использование диалогового окна, встроенного в саму ОС, что делало его наиболее практичным, простым и удобным способом перемещения внутри песочницы.
Однако был также продемонстрирован второй метод навигации. Этот способ реализовывался внутри самого кода разрабатываемого вами приложения, что делает его несколько более сложным. Дело в том, что целью системы навигации в данном случае является не простое и быстрое взаимодействие с пользователем приложения, а совершенно иная задача: узнать, какие типы файлов там имеются и как организована структура каталогов внутри песочницы.
Этот второй способ подхода или, лучше сказать, навигации внутри песочницы, открывает перед нами определенные возможности, которые мы можем немного исследовать прямо сейчас. Подобное происходит, потому что, хотя при тестировании приложения всё кажется идеальным, на практике во многих случаях всё может пойти наперекосяк. Файлы и каталоги могут быть представлены в совершенно хаотичном порядке, а имена — что ещё вероятнее — могут оказаться неупорядоченными.
Это обычно самый распространенный случай: мы можем захотеть создать фильтр, например, на основе даты создания или изменения файла или каталога. Это делалось бы в целях поиска, чтобы представить файлы и каталоги в определенном порядке и тем самым облегчить поиск нужной информации. В той реализации кода, который мы рассмотрели в предыдущей статье, у нас нет упорядоченного способа вывода результатов и даже какой-либо фильтрации, пусть даже самой простой в реализации.
Для сортировки любых значений, то есть для создания отсортированного способа представления результатов, нам необходимо включить в код некоторые элементы. Такая ситуация часто становится моментом, когда многие новички окончательно запутываются. Это связано с типом задачи, которую необходимо выполнить. Дело не в том, что это сложно, а потому что существует несколько разных способов сделать одно и то же. Однако, такая реализация на том или ином языке программирования может быть несколько запутанной. Часто приходится адаптировать код из другого языка, то, чего не удается многим менее опытным людям.
Однако здесь мы будем предельно откровенны, я не буду предпринимать никаких попыток или вводить новые концепции. Мы лишь увидим, как использовать то, что очень часто встречается в программировании, но о работе чего изнутри многие новички даже не догадываются. Поэтому давайте перейдем к новому разделу, чтобы начать самую интересную часть статьи. Потому что это именно та тема, от которой я получаю массу удовольствия, когда мне нужно использовать её на практике.
Словно мыльные пузыри
Отлично, давайте начнем с разговора о том, что вы, без всякого сомнения, уже точно использовали. Я имею в виду сортировку массива чисел по возрастанию. И зачем я об этом говорю? Причина проста, уважаемый читатель. В библиотеке функций и процедур MQL5 есть функция под названием ArraySort. Эта функция предназначена для сортировки содержимого массива в порядке возрастания.
Для тех, кто еще не видел эту функцию в действии, мы можем посмотреть на ее работу на примере небольшого кода, представленного ниже.
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. //+------------------------------------------------------------------+
Код 01
В коде 01 мы используем генератор псевдослучайных чисел для инициализации небольшого массива. Массив объявлен в строке 06. Инициализация выполняется циклом в строке 10. Но нас здесь интересуют именно следующие строки. В них мы сначала выводим массив до сортировки его значений, а затем после сортировки. Поскольку генерация основана на случайных значениях, наиболее вероятно, что результат будет отличаться от того, который вы можете видеть на следующем изображении.

Изображение 01
Такое происходит, потому что при каждом выполнении будет генерироваться новая последовательность значений. Но цель состоит как раз в том, чтобы заметить, что значения были отсортированы по возрастанию. Это происходит из-за функции в строке 16, которая присутствует в коде 01.
Но какое отношение это имеет к нашей идее о навигации в песочнице? Что ж, уважаемый читатель, вот тут начинается самое интересное. Многие, будучи новичками, могут не понять здесь важный момент. Когда данные представляются по порядку, будь то по возрастанию или по убыванию, их необходимо пропустить через какой-либо механизм сортировки. Понимание того, как работают эти механизмы, поможет вам решать самые разные задачи. Дело в том, что далеко не всегда язык программирования, какой бы он ни был, содержит встроенную реализацию, позволяющую сортировать абсолютно любой тип данных. Если вы понимаете, как работает механизм сортировки, вы сможете создать функцию или процедуру, которая позволит обобщить процесс сортировки. Это позволяет сортировать как числовые данные, так и текст, а также другие типы данных.
Например, если вы попытаетесь заменить код 01 следующим кодом, вы заметите, что его невозможно будет скомпилировать.
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. //+------------------------------------------------------------------+
Код 02
Но почему? Причина заключается как раз в том, что компилятор не может найти подходящую модель для использования строковых данных. Это связано не с самим массивом, а с функцией ArraySort, которая не была реализована или спроектирована для работы со строками с целью их сортировки по возрастанию. "Но подождите секунду. То есть, вы хотите сказать, что если мы удалим строку 16 из кода 02, то сможем получить результат? Это происходит, потому что компилятор может сгенерировать исполняемое приложение". Да, всё верно, уважаемый читатель. Дело в том, что если вы сделаете это, то при выполнении кода 02 сможете увидеть нечто похожее на следующее изображение: Конечно, после удаления строки 16 из кода.

Изображение 02
Теперь обратите внимание на следующее. На изображении 02 показаны строки, но они не отсортированы по возрастанию. Причина в том, что у нас нет механизма, который выполнял бы данную сортировку за нас. Теперь рассмотрим следующую деталь: если вы реализуете способ сортировки этих же строковых данных, даже если они содержат числовые значения, вы можете использовать тот же механизм для сортировки файлов и каталогов, чтобы представить их в определенном порядке - либо по возрастанию, либо по убыванию. Именно здесь и возникает необходимость изучения механизмов сортировки, которые могут быть реализованы внутри языка программирования.
Чтобы упростить задачу и сделать её понятной для каждого, поскольку наша цель здесь — быть максимально дидактичными и представить материал в легко усваиваемой форме даже для новичка, мы будем двигаться шаг за шагом. Таким образом, каждый сможет следить за процессом и понимать, что именно мы делаем. Понимание того, что именно происходит, гораздо важнее, чем простое копирование кода, когда вам понадобится сделать что-то подобное. Итак, начнём со следующего кода:
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. //+------------------------------------------------------------------+
Код 03
Код 03 станет нашей отправной точкой. Обратите внимание, что мы делаем нечто очень похожее на то, что было представлено в коде 01. Однако здесь мы подготавливаем код таким образом, что вы получите те же результаты, которые будут показаны на следующих изображениях. Это происходит, потому что, хотя мы всё ещё используем генератор псевдослучайных чисел для инициализации массива, мы запускаем этот генератор с определённым значением. Вы можете увидеть это в строке 08.
А теперь, уважаемый читатель, будьте внимательны. В отличие от кодов 01 и 02, код 03 содержит в строке 16 вызов процедуры, которую мы реализовали. Цель данной процедуры — отсортировать данные в массиве в порядке возрастания. Теперь перейдем к важной части: в предыдущих статьях было объяснено, как создавать шаблоны функций и процедур. Также объяснялось, как мы можем использовать данный подход на практике. Что ж, вот пример того, как использовать эти знания из предыдущих статей. Поэтому, в отличие от библиотечной функции ArraySort, которая не позволяет нам сортировать любой тип значений, здесь мы имеем реализацию, которая позволит нам это делать.
Чтобы сделать это возможным, мы используем проверку в строке 36 для проверка того, сортируем ли мы строки или любой другой тип данных. Обратите внимание: МЫ ПОКА НЕ СОРТИРУЕМ СТРОКОВЫЕ ДАННЫЕ. Но остальные типы данных мы уже сортируем. Мы можем убедиться в этом, посмотрев на результат, полученный на следующем изображении.

Изображение 03
Хотя реализованный в коде механизм не является лучшим из существующих, так как он очень медлителен для больших массивов, для нашей демонстрации он достаточно быстрый и отлично работает. Это можно увидеть на изображении 03. "И это всё?" Да, мой уважаемый читатель. Теперь перейдем к самой интересной части: модификации кода для возможности сортировки строковых данных. Прежде чем это сделать, давайте предпримем небольшой шаг. Он представлен в следующем коде:
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. //+------------------------------------------------------------------+
Код 04
Обратите внимание, что именно изменилось между кодом 03 и этим кодом 04. Все сделанные изменения направлены на то, чтобы используемые значения были одинаковыми в обоих случаях. Тем не менее, мы всё ещё не сортируем строки. Однако результат выполнения кода 04 представлен ниже:

Изображение 04
А теперь я хочу, чтобы вы обратили пристальное внимание, уважаемый читатель. То, что будет сделано, может показаться многим довольно запутанным. Это связано с тем, что, в отличие от сортировки числовых значений, сортировка строк не является настолько прямолинейным процессом. Для корректной работы сортировки это необходимо делать строго определенным образом. Однако, как можно увидеть в коде 03 и коде 04, между строками 37 и 38 есть место, где можно реализовать сам код сортировки. Чтобы понять, как должна выполняться эта сортировка, мне нужно, чтобы вы сначала поняли, какой тип результата мы получаем при использовании определенных способов ее реализации. Для начала воспользуемся функцией StringCompare, поскольку это, очевидно, первый вариант, который нам придет в голову. Для этого мы используем следующий код.
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. //+------------------------------------------------------------------+
Код 05
Когда мы запустим этот код, мы получим результат, показанный на следующем изображении:

Изображение 05
"Что это за безумие? По всей видимости, здесь нет никакого порядка. Хотя на первый взгляд элементы действительно отсортировались. Если присмотреться, то я замечаю, что в этом расположении есть нечто странное".
Совершенно верно, уважаемый читатель, вы правы. Код способен отсортировать значения, содержащиеся в строковом массиве. Но здесь есть кое-что странное, а именно тот факт, что 37 и 78 меньше, чем 113. Тем не менее, эти два значения помещаются в конец последовательности. Почему? Причина заключается в том, что сортировка учитывает только содержимое строки. Поэтому все значения между 113 и 189 остаются в правильном порядке. Тем не менее, когда мы перестаем учитывать количество символов, содержащихся в строке, сортировка кажется несколько странной, даже несмотря на то, что она сработала.
Интересно то, что если вместо числовых значений, как мы делаем здесь, использовать слова из словаря, вы заметите, что сортировка действительно сработает так, как ожидалось. Однако для того, что мы делаем — или пытаемся сделать, — сортировка не была идеальной на все сто процентов. Что ж, для решения этой проблемы нам также необходимо включить количество символов, содержащихся в строке, в качестве одного из используемых фильтров. Таким образом, пузырьковая система сможет сгенерировать корректную сортировку строкового массива. Необходимые изменения содержатся в следующем коде:
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. //+------------------------------------------------------------------+
Код 06
При выполнении кода 06 мы получим следующий результат.

Изображение 06
"Что ж, мы действительно получили желаемый и ожидаемый результат. В каком-то смысле я не понимаю некоторых вещей. Недавно вы сказали, что код 05 может сортировать, или, точнее, сортировать слова, взятые из словаря, по возрастанию. Но нам не удалось расположить числовые значения в правильном порядке, что отчетливо видно на изображении 05. Однако, используя данную модифицированную версию кода 06, мы смогли правильно отсортировать данные. Но разве это изменение кода 06 не делает код неспособным правильно отсортировать слова, взятые из словаря?" Что ж, возможно, многие из тех, кто считает себя программистами, скажут: конечно, нет. Код был изменен, но он по-прежнему способен правильно отсортировать слова.
Тем не менее, те, кто сомневается в написанном коде, вполне могут подумать, что программа может работать некорректно. Испытывая подобные сомнения и ставя под вопрос жизнеспособность изменений, внесенных в структуру кода, они в итоге приходят к пониманию, если можно так выразиться, что решения, способного охватить абсолютно все случаи, не существует. Они также обнаруживают, что в определенный момент знание и понимание того, как механизмы работают изнутри, может стать решающим фактором, отделяющим адекватный результат от посредственного.
Поэтому давайте проведем простой тест: ещё раз модифицируем код 06, чтобы проверить, можем ли мы использовать эту реализацию в любых случаях или нет. Для этого нам достаточно изменить код 06, как показано в следующем примере:
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. //+------------------------------------------------------------------+
Код 07
Теперь у нас есть кое-что немного другое. Однако единственное реальное различие заключается в значениях строкового массива. В данном случае у нас есть слова, порядок которых вам точно известен, учитывая, что порядок слов по возрастанию. Но к вашему удивлению, при выполнении кода 07 мы получаем результат, показанный на следующем изображении:

Изображение 07
Без всякого сомнения, результат на изображении 07 не совпадает с тем порядком, в котором эти же слова располагались бы в словаре. Тем не менее, здесь сортировка произошла точно так же, как это было во время выполнения кода 05, результат которого был показан на изображении 05. Именно поэтому, мой уважаемый читатель, важно понимать, что возвращаемый приложением ответ не всегда будет абсолютно правильным, но и не обязательно будет ошибочным. Ключевой момент: Какого типа ответ ожидался от приложения? В зависимости от конкретного случая, этот результат может как подходить, так и не подходить для достижения поставленной цели. В данном случае это не так. Мы оказались в тупике.
В данном случае процедура, созданная для выполнения задачи, которая здесь заключается в сортировке данных в массиве, выдает нам ответы, которые могут иметь или не иметь для нас смысл. Мы можем внести небольшое и едва заметное изменение в код, чтобы сделать этот процесс чуть более контролируемым. Данное изменение содержится в следующем коде.
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. //+------------------------------------------------------------------+
Код 08
Код 08 позволяет проверить изменения и их корректность, а также отсортировать строковой массив ожидаемым образом. Давайте посмотрим, что произойдет, если мы запустим код 08 в терминале MetaTrader 5. Таким образом, мы получим следующее изображение:

Изображение 08
Просто идеально. Обратите внимание, что как массив с числовыми строками, так и массив со словами из словаря отсортированы правильным образом. Вернее, ожидаемым образом. Раньше это было невозможно. Главный вопрос здесь заключается как раз в том, что именно потребовалось реализовать, чтобы эта сортировка выдала ожидаемый результат. Всё, что требовалось, — это добавить дополнительный тест в блок, где мы обрабатываем строковые данные.
Хотя вы можете подумать, что представленный здесь тип сортировки является идеальной моделью и вы можете использовать его в любом случае, я хочу, чтобы вы пересмотрели эту гипотезу, мой уважаемый читатель. Этот метод сортировки, без сомнения, является худшим из всех существующих методов. Хотя его код очень прост для понимания, он крайне быстро снижает производительность системы в целом. Это происходит, потому что для каждого элемента в сортируемом массиве нам придется выполнять цикл очень большое количество раз.
Если говорить точнее, количество итераций, которое должен выполнить цикл, в худшем случае равно количеству элементов в квадрате. То есть для сортировки пяти элементов нам нужно выполнить цикл 25 раз. Для десяти элементов, которых всего в два раза больше, чем в первом случае, количество выполнений цикла возрастает до 100. Обратите внимание, что количество выполнений цикла растет очень быстро по мере добавления новых элементов в массив.
По этой причине некоторые программисты обычно называют представленный до сих пор метод «пузырьковым адом». Это происходит, потому что каждый новый добавленный элемент потенциально увеличивает количество итераций, которые необходимо выполнить для успешного завершения сортировки.
Хотя эта рудиментарная и несовершенная методология, представленная здесь, является проблемой, существуют случаи, когда нам не нужно выполнять цикл так много раз, чтобы получить отсортированный массив на выходе. Это связано с тем, что для полной сортировки массива может потребоваться всего лишь несколько итераций обмена. Каким бы невероятным это ни казалось, простое и на первый взгляд малоэффективное изменение в коде может превратить этот «пузырьковый ад» в нечто вполне приемлемое при определенных обстоятельствах.
Чтобы понять, как это изменение влияет на код, и, главное, как оно может ускорить его работу, необходимо чтобы вы, уважаемый читатель, поняли, как работает процесс сортировки в представленной до настоящего момента реализации.
Как только вы это поймете, мы сможем перейти к улучшению кода. Таким образом мы стремимся сделать так, чтобы система сортировки в некоторых ситуациях была немного быстрее или менее требовательной с точки зрения загрузки процессора и времени обработки.
Что ж, для того чтобы мы могли понять, как данная модификация, которую мы собираемся реализовать, влияет на выполнение кода, сначала нам придется реализовать способ увидеть, что происходит внутри алгоритма сортировки. Для этого мы изменим код 08 на следующий код:
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. //+------------------------------------------------------------------+
Код 09
Обратите внимание, что в этом коде 09 мы лишь добавили способ подсчета того, сколько итераций произошло внутри сортировщика. Это было сделано путем добавления строки 42 и последующего подсчета итераций в строке 46. В конце, в строке 58, мы выводим результат, чтобы узнать, сколько итераций произошло. Обратите внимание на один момент: эта реализация не будет выполнять операцию nElements в квадрате. Это связано с тем, что на каждую итерацию, выполняемую циклом в строке 44, будет выполняться на одну итерацию меньше в цикле в строке 46. Очень важно, чтобы вы это поняли, дабы не запутаться в значении, которое будет выведено в качестве количества выполненных итераций.
Отлично, тогда при запуске этого кода мы получим результат, показанный на следующем изображении:

Изображение 09
Что нас действительно интересует здесь и в данный момент — это области, выделенные на изображении 09. Обратите внимание, что происходит определенное количество итераций. Теперь я спрашиваю: это хороший или плохой показатель? А ответ на этот вопрос: всё зависит от обстоятельств. Здесь есть один нюанс, и именно к этому я хочу подвести. Обратите внимание, что значения, которые необходимо отсортировать, на первый взгляд кажутся сильно неупорядоченными. Но что, если бы они были ближе к ожидаемой сортировке? Снизило бы это количество итераций по сравнению с предыдущим результатом? Хорошо, давайте попробуем и посмотрим, что получится. Для этого мы используем следующий код:
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. //+------------------------------------------------------------------+
Код 10
Обратите внимание, что в шестой строке этого кода 10 мы определяем те же значения, что были показаны на изображении 09. Они, если можно так выразиться, немного менее хаотичны. На первый взгляд может показаться, что это ускорит процесс сортировки или потребует меньшего количества итераций для её завершения. Но, к вашему удивлению, когда данный код 10 будет выполнен, результат окажется таким, как на следующем изображении.

Изображение 10
Итак, хотя нам, по всей видимости, и требуется меньше итераций для сортировки массива в строке 06 из этого кода 10, мы всё равно выполняем то же самое количество итераций. Многим это может показаться странным, но в таком случае, что же не так с этим кодом 10, из-за чего он не выполняет меньше итераций? На самом деле, с кодом всё в порядке. Осталось только добавить проверку, целью которой будет узнать, отсортирован массив или нет.
Хорошо, но эта проверка кажется довольно сложной, так как, чтобы узнать, отсортирован массив или нет, нам нужно было бы это проверить. Это займет время и потребует дополнительной итерации. Это нежелательно, так как мы стремимся именно к сокращению количества итераций. Хорошо, но как же нам это решить? На мой взгляд, это самая интересная часть, именно потому, что для решения этой проблемы нам нужно лишь внести небольшие изменения в код. Теперь посмотрите на изменение, которое необходимо будет внести в следующий код:
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. //+------------------------------------------------------------------+
Код 11
При выполнении кода 11 мы получим следующий результат:

Изображение 11
Обратите внимание, что теперь на изображении 11 количество итераций сократилось, но без серьезных изменений в самом коде. Всё, что нам пришлось сделать, это добавить строку 38 и затем изменить некоторые моменты в коде. Как цикл в строке 41 и макрос. Обратите внимание, что сам код проверит, были ли произведены какие-либо перестановки в массиве или нет. Если цикл в строке 44 выполняется, а строка 33 не выполняется ни на одной итерации, мы получим гарантию того, что массив уже должным образом отсортирован. Таким образом, на следующей итерации цикла в строке 41 мы сможем завершить процедуру сортировки. Интересно, не правда ли?
Заключительные идеи
В этой статье был объяснен очень простой и легкий для понимания механизм, целью которого является сортировка любого массива. Также было показано, что представленный результат не всегда совпадает с тем, который мы действительно ожидаем получить. Поэтому необходимо адаптировать реализацию для получения правильных результатов.
Мы тоже показали, что, приложив совсем немного усилий, мы можем превратить изначально медленную реализацию в более гибкую. Это происходит тогда, когда ситуация позволяет обрабатывать определенные массивы с большей скоростью. Может случиться так, что при минимальных корректировках ожидаемый результат уже будет сформирован. Это исключает выполнение новых итераций, так как они уже не изменили бы полученный результат.
Поскольку большая часть из рассмотренного здесь представляет собой изменения в дидактических целях, в приложении у вас будут только коды уже в их финальной версии. Поэтому, если вы хотите понять, почему был выбран тот или иной путь, достаточно использовать коды, полностью представленные в тексте этой статьи. В остальном остается только изучать и практиковать уже рассмотренный материал, чтобы понять, как работает показанный здесь механизм.
| Файл | Описание |
|---|---|
| Code 01 | Демонстрация простой сортировки |
| Code 02 | Демонстрация простой сортировки |
| Code 03 | Демонстрация простой сортировки |
Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/16418
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Встраивание торговой дисциплины в код (Часть 3): Принудительное ограничение торговли на уровне символов с помощью белого списка в MQL5
Создание пользовательских индикаторов в MQL5 (Часть 2): Разработка индикатора RSI в виде шкалы со стрелкой на Canvas
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования