Оптимизация задачи. https://yadi.sk/d/zFoikkKNw2hDQw Можно выбрать от 0 до 8 изменив дефайн OPTIMIZATION. Время выполнения уменьшилось от 5 млн тактов и до 500-700, т.е. в 10 тысяч раз. Теперь задачу можно решить на любом утюге т.е. калькуляторе хоть на Б3-21. --- Сообщение объединено, Sep 8, 2021 --- Базовый вариант, самый тупой, но зато понятный. Code (C): for(l3=0; l3<10; l3++) for(l2=0; l2<10; l2++) for(l1=0; l1<10; l1++) for(r3=0; r3<10; r3++) for(r2=0; r2<10; r2++) for(r1=0; r1<10; r1++) if (l1+l2+l3==r1+r2+r3) count++; Решил что сумму цифр надо вычислять там, где они меняются. Code (C): for(l3=0, sum_l=0; l3<10; l3++, sum_l-=9) for(l2=0; l2<10; l2++, sum_l-=9) for(l1=0; l1<10; l1++, sum_l++) for(r3=0, sum_r=0; r3<10; r3++, sum_r-=9) for(r2=0; r2<10; r2++, sum_r-=9) for(r1=0; r1<10; r1++, sum_r++) if (sum_l==sum_r) count++; Потом подумал что 686+ очень не любят условные переходы ака if'ы. Code (C): for(l3=0, sum_l=0; l3<10; l3++, sum_l-=9) for(l2=0; l2<10; l2++, sum_l-=9) for(l1=0; l1<10; l1++, sum_l++) for(r3=0, sum_r=0; r3<10; r3++, sum_r-=9) for(r2=0; r2<10; r2++, sum_r-=9) for(r1=0; r1<10; r1++, sum_r++) count += sum_l==sum_r; Стало быстрей. Но это всё ерунда. Оптимизируем вариант 1. Можно легко доказать что условия count++ в цикле срабатывает только один раз или не разу, если сумма чисел в определённом диапазоне. Вот следующий вариант. Code (C): for(l3=0, sum_l=0; l3<10; l3++, sum_l-=9) for(l2=0; l2<10; l2++, sum_l-=9) for(l1=0; l1<10; l1++, sum_l++) for(r3=0, sum_r=0; r3<10; r3++, sum_r-=9) for(r2=0; r2<10; r2++, sum_r++) if (sum_l>=sum_r && sum_l<sum_r+10) count++; Скорость заметно возросла. Но идём дальше, там уже по сложней. Далее такой вариант. Смотрим на эти ступеньки, и мысленно обваливаем её в пирамиду. Code (Text): 0000000001111111111 000000001111111111 00000001111111111 0000001111111111 000001111111111 00001111111111 0001111111111 001111111111 01111111111 1111111111 Вертикальные столбики - это количество вариантов в этих диапазонах. А значит легко делаем следующий код. Code (C): for(l3=0, sum_l=0; l3<10; l3++, sum_l-=9) for(l2=0; l2<10; l2++, sum_l-=9) for(l1=0; l1<10; l1++, sum_l++) for(r3=0, sum_r=0; r3<10; r3++, sum_r++){ int sum = sum_l - sum_r + 1; if (sum>0 && sum<20){ count += sum; sum -= 10; if (sum>0) count -= 2*sum; } } Ещё быстрей! И результат правильный. Но идём дальше. Теперь общая сумма вариантов равно не отдельному столбику, а суммы от левого края. Сумма половины пирамиды определяется так: (sum*sum+sum)/2. И по той же формуле отсекает в пирамиде лишние варианты. Получается код. Code (C): for(l3=0, sum_l=0; l3<10; l3++, sum_l-=9) for(l2=0; l2<10; l2++, sum_l-=9) for(l1=0; l1<10; l1++, sum_l++){ int sum = sum_l + 1; count += (sum*sum+sum)/2; if (sum>10){ sum -= 10; count -= ((sum*sum+sum)/2)*3; if (sum>10){ sum -= 10; count += ((sum*sum+sum)/2)*3; } } } Стало ещё быстрей. Что дальше? Можно так. Code (C): for(sum_l=0; sum_l<=9*3; sum_l++){ int sum = sum_l + 1; unsigned int count_l = (sum*sum+sum)/2; if (sum>10){ sum -= 10; count_l -= ((sum*sum+sum)/2)*3; if (sum>10){ sum -= 10; count_l += ((sum*sum+sum)/2)*3; } } CountVar3[sum_l] = count_l; } for(l3=0, sum_l=0; l3<10; l3++, sum_l-=9) for(l2=0; l2<10; l2++, sum_l-=9) for(l1=0; l1<10; l1++, sum_l++) count += CountVar3[sum_l]; Быстрей, но что то не то. Может так. Code (C): for(sum_l=0; sum_l<=9*3; sum_l++){ int sum = sum_l + 1; unsigned int count_l = (sum*sum+sum)/2; if (sum>10){ sum -= 10; count_l -= ((sum*sum+sum)/2)*3; if (sum>10){ sum -= 10; count_l += ((sum*sum+sum)/2)*3; } } CountVar3[sum_l] = count_l; } for(sum_l=0; sum_l<=9*2; sum_l++){ int i; unsigned int count_l = CountVar3[sum_l]; for(i=1; i<10; i++) count_l += CountVar3[sum_l+i]; CountVar2[sum_l] = count_l; } for(sum_l=0; sum_l<=9*1; sum_l++){ int i; unsigned int count_l = CountVar2[sum_l]; for(i=1; i<10; i++) count_l += CountVar2[sum_l+i]; CountVar1[sum_l] = count_l; } for(l1=0; l1<10; l1++) count += CountVar1[l1]; Не! Быстрей, но тоже не то. Ах да. Как же я упустил, задача то симметричная, мы вычислили вариант для одной стороны, а значит и для другой стороны такое количество вариантов. Значит надо просто возвести в квадрат. Вот вариант. Code (C): for(sum_l=0; sum_l<=9*3; sum_l++){ int sum = sum_l + 1; unsigned int count_l = (sum*sum+sum)/2; if (sum>10){ sum -= 10; count_l -= ((sum*sum+sum)/2)*3; if (sum>10){ sum -= 10; count_l += ((sum*sum+sum)/2)*3; } } count += count_l*count_l; } Так, да тут ещё можно в два раза уменьшить вариантов в цикле, а потом умножить на два. Но и так стало очень быстро. Теперь можно и для МК-61 программу составить, и он на своих 100 килогерцах решит достаточно быстро. Вот, как то так. Потом может лучше рисунками статью дополнить, чтобы попонятней было и выложить на хабре.
У меня год назад, сильно зудело - хотелось переписать свой старый FFT алгоритм - пришла пара классных идей. Но увы, нет времени, пришлось включить волю и взять себя в руки. Отложил на потом. Иногда закрыв глаза, мечтаю о том, как переписываю алгоритм. :lol: А вам вот сейчас очень завидую.
Кто-то спрашивал, а что такое пессемизация кода. А вот она, питухонцы, вперёд! Мир на вас надеяться. Code (Python): n = 0 for num in range(1000000): st = str(num) st = '0'*(6-len(st)) + st; if int(st[0]) + int(st[1]) + int(st[2]) == int(st[3]) + int(st[4]) + int(st[5]): n = n+1 #end #end print n Сейчас попробуют скомпилировать этот код в ассемблер, и без существенной оптимизации. Питухон это ЯП для замены С/С++ и прочих сложных ЯП. У вас не хватает быстродействия для переваривания тонн кода на питухоне, купите мощный ПК хотя бы iшак5 13600, лучше i9 14900 с 64 ГБ ОЗУ, иначе наш питухон будет тормозить.
Не хватает в нем возможности создавать свои структуры как в си/паскале, без всяких извращений типа классов, так что, такая себе замена
Короче, результат компиляции питухон --> ассемблер UASM Code (ASM): mov sum, 0 .for (esi=0: sdword ptr esi<1000000: esi++) ASSUME ebx:ptr byte, edi:ptr byte mov ebx, GlobalAlloc(GMEM_FIXED, 256) ;;st_ = new char[256]; mov edi, GlobalAlloc(GMEM_FIXED, 256) ;;st = new char[256]; sprintf(ebx, "%d", esi) ;;st_ = str(num) strlen(ebx) mov ecx, 6 sub ecx, eax ;;(6-len(st_)) .for (edx=0: sdword ptr ecx>0: edx++, ecx--);;st = '0'*(6-len(st)) mov [edi][edx], '0' .endfor .for (ecx=0: [ebx][ecx]: edx++, ecx++) mrm [edi][edx], [ebx][ecx] .endfor mov [edi][edx], 0 ;;st = '0'*(6-len(st)) + st; ;;if int(st[0]) + int(st[1]) + int(st[2]) == int(st[3]) + int(st[4]) + int(st[5]): mrm [ebx][0], [edi][0] mov [ebx][1], 0 mov tempA, atoi(ebx) mrm [ebx][0], [edi][1] mov [ebx][1], 0 add tempA, atoi(ebx) mrm [ebx][0], [edi][2] mov [ebx][1], 0 add tempA, atoi(ebx) mrm [ebx][0], [edi][3] mov [ebx][1], 0 mov tempB, atoi(ebx) mrm [ebx][0], [edi][4] mov [ebx][1], 0 add tempB, atoi(ebx) mrm [ebx][0], [edi][5] mov [ebx][1], 0 add tempB, atoi(ebx) mov eax, tempB .if (tempA==eax) add sum, 1 .endif GlobalFree(edi) ;;delete []st_; GlobalFree(ebx) ;;delete []st; ASSUME ebx:nothing, edi:nothing .endfor Время выполнения 1137810458 тактов, а код оптимизации - 0: 10738795 тактов. Это получается 1137810458 / 10738795 = 106 раз медленней. Так что пессимизаторам есть куда стремиться, сделаем код ещё медленней, не надо задрачивать код, и так сойдёт. А то денег сотни тысяч не получите.
Да ладно, не так давно питоний код одного известного в узких кругах ютубера ускорили на 40,832,277,770% (да да, на 40+ миллиардов процентов) Пруф (англ):
aa_dav, есть разные ЯПы. И есть такие которые провоцируют пограмистов писать хреновый код, т.е. если есть вариант плохого кода, то пограмист обязательно его выберет. Собственно вот. st = str(num) st = '0'*(6-len(st)) + st; Так сделал питухонец, а на С/С++ без std можно: sprintf(st, "%06d", sum); Но тут важно погромист постарался вообще написать самый тупую реализацию насколько это возможно, а всё это из-за особенностей питухона. Код на ассме получается эффективней потому-что там нет всяких автоматических преобразований, в результате такой на вид простой код усложняется, и заставляет идти пограмиста по более оптимальному пути. А вот так. Code (ASM): xor ebp, ebp mov dh, 10 .repeat mov bh, 10 .repeat mov ch, 10 .repeat mov dl, 10 .repeat mov bl, 10 .repeat mov cl, 10 align 16 .repeat mov al, cl add al, bl add al, dl mov ah, ch add ah, bh add ah, dh .if (al==ah) inc ebp .endif dec cl .until (zero?) dec bl .until (zero?) dec dl .until (zero?) dec ch .until (zero?) dec bh .until (zero?) dec dh .until (zero?) хотя при желании можно сделать, и так, как сделал питухонер. Самое интересное, в таком стиле можно программировать и на С/С++, да именно С++ но обязательно БЕЗ std! Тогда код получается максимально эффективный. --- Сообщение объединено, Oct 30, 2023 --- Короче так. Код питухона немного доделал, вместо print n, надо print ("n = ", n) Потом нашёл онлайн транслятор питух в С++, код почему-то на 2010 С++ не пошёл, но после танцев с бубном получилось вот. Code (C++): #include <iostream> #include <string> #include <sstream> #include <time.h> using namespace std; int main() { clock_t tm = clock(); int n = 0; for (int num = 0; num < 1000000; num++){ ostringstream tmp; tmp << num; string st = tmp.str(); st = string(6 - st.length(), '0') + st; if (st[0]-'0' + st[1]-'0' + st[2]-'0' == st[3]-'0' + st[4]-'0' + st[5]-'0')//тут транслятор сам сделал оптимизацию!!! n++; } double time = (double)(clock() - tm)/CLOCKS_PER_SEC; cout << "n = " << n << ", time = " << time << " sec" << endl; return 0; } И можно было подумать что С++ будет быстрей моего варианта компиляции питухон-->ассемблер. Но нет. TEST: Happy tickets. OPTIMIZATION = -1 Count tickets = 55252, tics = 5380653243, time = 287 ms А наш любимый С++, настоящий С++ кстати, только немного старый. n = 55252, time = 0.819 sec Разница почти в три раза! И кстати, сам питухон по сравнению с С++ ненамного медленней. Вот почему говорят: да ладно, питон почти такой же быстрый как С++, и таки не врут собаки. А я думал брешуть, а оно воно как, просто сравнивают говноС++. А С++ может быть быстрей ассемблера(за счёт мощной оптимизации) и даже С(за счёт лучше конструкции), ну чуть чуть совсем, но как я сказал, только при условии низкого уровня, при этом забудьте о std, и прочей шаблонной ереси. --- Сообщение объединено, Oct 30, 2023 --- Вот питухон версии 3.* Code (Python): import time tm = time.time() n = 0 for num in range(1000000): st = str(num) st = '0'*(6-len(st)) + st; if int(st[0]) + int(st[1]) + int(st[2]) == int(st[3]) + int(st[4]) + int(st[5]): n = n+1 #end #end print ("n = ", n, " time = ", time.clock() - tm, " sec") Тест тут запустил, что там за машина не знаю. https://www.onlinegdb.com/online_python_compiler Но выдала: n = 55252 time = 2.5153164863586426 sec Как видно, несильно медленней С++, всего в 3 раза. И это компиляция, иначе намного медленней вышло. --- Сообщение объединено, Oct 30, 2023 --- И ещё важно, надо сравнить вариант оптимизация=0 Code (Python): tm = time.time() cnt = 0 for L1 in range(10): for L2 in range(10): for L3 in range(10): for R1 in range(10): for R2 in range(10): for R3 in range(10): if L1+L2+L3==R1+R2+R3: cnt = cnt+1 #end #end #end #end #end #end #end print ("cnt = ", cnt, " time = ", time.time() - tm, " sec") Результат на той же машине: cnt = 55252 time = 0.13225150108337402 sec Быстрей в 19 раз, ну да код немного больше, но это же копипаста, шлёп, шлёп, готово. Но питухонцы не идут простыми путями, прога должна тормозить по определению.
Intro, Ты, конечно, мощно задвигаешь нужна скорость на питохе - пиши сишный модуль и вызывай его питохой сила скриптовых япов не в скорости, а в гибкой работе с типами данных, то бишь можно делать само-адаптируемые коды. для компилируемых яп-ов это тоже возможно, но там процесс получается более затратным.
Intro, а в чём твой посыл? В чём прикол сравнивать производительность совершенно полярных языков? Питон никогда не целился в нишу High-Performance Computing - это же скриптовый язык. Его используют для автоматизации и прототипирования: быстро проверить теорию, что-то распарсить или сконвертировать - лучше питона ничего нет. Но писать на нём надо без пошлости. Вот смотри, ты говоришь: Никто так делать не будет. А вот как это делается правильно: Code (Python): import time def number_sum(val: int) -> int: hundreds = val // 100 without_hundreds = val - hundreds * 100 decimals = without_hundreds // 10 ones = without_hundreds - (decimals * 10) return hundreds + decimals + ones begin_time = time.time() total_sum = 0 for i in range(1000): left_sum = number_sum(i) for j in range(1000): right_sum = number_sum(j) if left_sum == right_sum: total_sum += 1 end_time = time.time() print("Matches", total_sum) print("Elapsed time:", end_time - begin_time) Однако, этот вариант в 2.5 раза медленнее, чем 6 вложенных циклов для каждой цифры - видимо, из-за потерь на вызов функции и из-за делений. Но кого это волнует, когда я могу сделать так: А теперь нарисуй мне то же самое на ассемблере или плюсах.
HoShiMin, ну - графики питоха рисует всё-таки не скриптянкой, а вызывает для этого модули на тех же плюсах.. а в остальном поддерживаю твою точку зрения
Кстати, если переписать на плюсах с -O3, компилятор посчитает результат в компайлтайме, и получится примерно это: Code (ASM): mov eax, 55252 ret
Нарисовать такой график на канве, не сложно, если речь о c++/компилируемых яп Отдельно нарсовать систему координат, отдельно данные. И еще надо масштаб правильно подсчитать, чтобы за границы велосипеда не заходило )
Code (Text): int sum = sum_l + 1; unsigned int count_l = (sum*sum+sum)/2; if (sum>10){ sum -= 10; count_l -= ((sum*sum+sum)/2)*3; if (sum>10){ sum -= 10; count_l += ((sum*sum+sum)/2)*3; } } Вот эту вот магию я не понял. Смысл понял - считаем количество комбинаций каждой суммы по отдельности и суммируем их квадраты, но как этот код правильно считает количество комбинаций суммы sum_l для трёх разрядов глубоко непонятно.
Да ладно, я этот код взял с хабра, с ходу ссылку не найду, надо в истории браузера копаться. Сам бы до такого, просто не додумался. Вот возьмём ваш любимый MASM, наш любимый ассемблер. Я давненько пытался реверсить, надо было найти максимальную длину строки и имени переменной. Да к я не нашёл, код был очень странный, я так понял это именно С++, с толи boost, толи ещё какие-то фреймворки, шаблоны шаблонов, шаблонами шаблонируют. Ну ладно, потом мне тут подсказали UASM, я его немного изучил, есть доступные исходники, нашёл нужные параметры(строка 2048, и длина переменной 247)... Ну в общем решил его использовать, и первым делом перевести на его свой XRayExtensions, это у меня не сразу получилось, оказалось про 100% совместимости не совсем так, но потом у меня получилось, и.... И оказалось что UASM работает раз 30-40 раз быстрей, мой проект буквально за доли секунд компилировался, кода там достаточно много, и сам MASM компилировал секунд 20-25 что ли, уже не помню, ну на старом моём атлоне !! х4 640 3ГГц. Так что получается, именно так делают очень многие, причём такие крупные фирмы как микрософт. Более того, эти засранцы говорят, я знаю что на С++ можно программировать как на С, но это плохо, ни в коем случае не делайте так. Блин, если бы мне он сказал в лицо, и у меня был АК, я бы в него весь магаз... Тут надо найти свои наброски, там получается типа пирамида, и надо найти формулу которая вычисляет объём пирамиды. Тут я плохо объяснил как точно оптимизировал, т.к. делал это довольно давно, немного подзабыл тонкости хода мысли. PS Вот нашёл где этот питухонокод. https://habr.com/ru/articles/42682/ А вот афтыр. https://habr.com/ru/users/A2K/
Да, для двух цифр я на бумажке прикинул и получилась чистейшая линейная пирамида 1,2...9,10,9...2,1. Но для трёх цифр первые члены ряда уже 1,3,6,10..., т.е. не линейно растут, а инкремент сам увеличивается на 1 каждый шаг, т.е. это уже не пирамида, а что-то изогнутое, и есть подозрение, что к вершине будет сглаженность, по типу графика нормального распределения, тут уже я опух и продолжать не стал.
Так а вон же я выше скинул график распределения совпадений. Они идут по принципу: 000: 1 вариант 001: 1 + 2 = 3 вариантов совпадений 002: 1 + 2 + 3 = 6 вариантов … Потом доходит до какого-то числа и начинается со сдвигом на 1: 1 + 2 1 + 2 + 3 1 + 2 + 3 + 4 … Потом снова доходит до какого-то числа, и следующее уже 1 + 2 + 3 … и так до номера 500. А от 501 до 999 - зеркальная копия (та же последовательность, только в обратную сторону). --- Сообщение объединено, Oct 31, 2023 --- А раскрой мысль: что ты подразумеваешь под "программировать на C++ как на С"? От каких концепций в C++ следует отказаться, чтобы код был быстрым, и почему?
Мы говлрим про другой график. Надо чтобы сумма трех первых цифр совпала с суммой последних трех. А какие суммы бывают? От 3*0 до 3*9. От 0 до 27. Задачу можно свести к тому чтобы понять для каждой такой суммы сколькими комбинациями трех цифр ее можно выложить. А полное решение это сумма квадратов этих величин, т.к. каждой комбинации слева соответствуют такие же комбинации справа. Например 0 можно выложить только одной комбинацией 000, и 27 тоже только одной 999. Они дадут в ответ вклад по единичке в квадрате. А 1 уже выкладывается тремя комбинациями и 26 тоже. Они дадут в ответ вклад по 3 в квадрате каждый. Т.е. по горизонтальной оси числа от 0 до 27. И будет не пила, а симметричная плавно нарастающая и ниспадающая горка. И вот тот код который я процитировал топикастера умудряется каждый такой член считать за три непонятных для меня присяда.
Но ведь твоя претензия не к языку, а к коду конкретного человека. Он просто не сообразил, как из десятичного числа вытащить цифры разрядов. Дай ему хоть си, хоть ассемблер - он напишет примерно то же самое, с такой же низкой производительностью. И наоборот, даже на медленном питоне можно писать код, производительности которого будет достаточно для решения задач, которые ты на нём решаешь. Да, можно даже делать игры и писать веб-сервера. Проще говоря, если человек не умеет писать производительный код и не понимает, почему его код медленный, смена языка ему не поможет. --- Сообщение объединено, Oct 31, 2023 --- Так я про это и говорю. Считаем количество комбинаций, которыми можно получить каждое число - от 000 до 999. Получится такая интересная последовательность, как в том графике. Например, число 000 можно получить только одной комбинацией - таким же числом 000. Сумму 001 можно получить уже тремя способами: 001, 010, 100. Сумму 002 - уже шестью способами. Получается последовательность 1, 3, 6, 10, 15, и т.д. Проще говоря: 1 1 + 2 1 + 2 + 3 1 + 2 + 3 + 4 1 + 2 + 3 + 4 + 5 1 + ... + N 1 + 2 1 + 2 + 3 1 + ... + M 1 + 2 + 3 1 + 2 + 3 + 4 1 + ... И получается такая пила до 499го числа. А с 500 до 999го эта пила идёт в обратную сторону. Если сумеешь описать эту пилу общей формулой - тогда подсчёт всех счастливых билетиков сведётся к линейной сложности: пройти от 000 до 499 и подставить каждое число в эту формулу, получить число совпадений, и просто приплюсовать к общему счётчику. А затем умножить счётчик на 2 (т.к. от 500 до 999 будет столько же).