Text
                    1


Валерий Рубанцев Питономикс Python: Лучшее лекарство Все права защищены. Никакая часть этой книги не может быть воспроизведена в любой форме без письменного разрешения правообладателей. Автор книги не несёт ответственности за возможный вред от использования информации, составляющей содержание книги и приложений. Copyright 2026 Валерий Рубанцев Лилия Рубанцева 2
От автора 3
Питон – как и Карлсон – всегда возвращается. Но в отличие от Карлсончика он возвращается не за вареньем, а с подарками для своих друзей. Вот и в этой книге много занятных и забавных подарков от нашего Питона: - Микстуры для ума и головоломные витамины для раззагогуливания загогулин в мозгу. Ребята, эти лекарства поднимут и поставят на ноги любого, у кого есть голова в нужном месте тела! - Красивые шейдеры – нужны и полезны для развития и укрепления чувства прекрасного и тяги к программированию. - Очень много занимательных задач, решённых в функциональном стиле, которые должны и продвинут навыки программирования на Питоне самыми заковыристыми и замысловатыми способами. - Никто так не смешон, как программист, не понимающий шуток о программистах. Мы с этим злым недоразумением боремся и будем исправлять в последнем разделе книги- Время шуток. Почему в последнем? – Потому что хорошо смеётся тот, кто смеётся последним. А кто с этим поспешит, тот сам людей и насмешит. - По всей рецептуре, но аккуратно разбросаны разные головоломки, чтобы чтецы не только учитали книгу, но и шевелили мозговыми извилинами, не допуская их полного распрямления до полной ненужности, отторжения и отчуждения. Отправляясь в дальний путь, питономикс взять не забудь! Книга адресуется, прежде всего: • Школьникам с 10-летнего возраста. • Родителям для совместных занятий по программированию с детьми. • Учителям информатики. • Всем начинающим программистам любого возраста, не имеющим предварительного опыта в программировании. Автор 4
Условные обозначения, принятые в книге: Дополнение Указание Замечание Задание для самостоятельного решения Исходный код: while True: f = float(input('Введите температуру F > ')) # температура в градусах Цельсия: c = round((f - 32) / 1.8, 2) print('Температура С =', c) print() Заголовок проекта: Проект … Исходные коды всех проектов находятся в папке _Projects 5
Оглавление 6
Питономикс ........................................................... 2 Python: Лучшее лекарство ............................................. 2 От автора ............................................................... 3 Оглавление ............................................................. 6 Микстуры для ума .................................................... 10 Кролики – это не болеки-лёлеки! ........................................................................ 11 Микстура Решето Сундарама ................................................................................. 12 Микстура Решето Сундарама 2 ............................................................................. 17 Микстура Раз монетка, два монетка .................................................................... 20 Фигуры из пиастров ................................................................................................. 26 Микстура Самые простые палиндромы ........................................................... 31 Микстура Счастливые билеты ............................................................................. 37 Микстура Константа Капрекара ............................................................................ 53 Микстура LeetCode 1249. Minimum Remove to Make Valid Parentheses .... 60 Микстура Головоломные скобки ........................................................................ 65 Микстура Головоломные палиндромы ............................................................ 73 Красивые шейдеры ................................................... 80 Проект Polygons & Patterns ................................................................................... 82 Проект Rosette ......................................................................................................... 87 Проект Spiral Rosette ............................................................................................. 93 Проект Crystal ........................................................................................................... 98 Проект Glossy Gradients ....................................................................................... 103 Проект Purple-blue liquid gradient ..................................................................... 107 Проект Contoured Layers ......................................................................................116 Проект Satori .......................................................................................................... 134 Проект Simple circles and gradients ..................................................................141 Проект Voronoi Wave............................................................................................. 145 Проект Light Circles................................................................................................151 Проект Iterations - inversion 2 .......................................................................... 157 Проект Iterations - inversion .............................................................................. 164 Проект Warping - procedural 2 ........................................................................... 170 7
Проект Warping - procedural 5 ........................................................................... 177 Как это всё функционирует? ....................................... 187 Трёхзначное число ................................................................................................ 189 Пятизначное число................................................................................................ 190 Шестизначный перенос .........................................................................................191 Всезнающая статистика ........................................................................................ 195 Восстановите потерянную цифру ..................................................................... 193 Снимите маску с одной цифры ......................................................................... 197 И «хвост», и «грива» ............................................................................................ 203 Ж-Ж-Ж! ...................................................................................................................... 206 Девять в квадрате .................................................................................................. 208 Число 117 649 .......................................................................................................... 199 Пара чисел: 3149 и 3151 ...................................................................................... 210 Трёхзначное число filter ..................................................................................... 213 Таких чисел только два ....................................................................................... 214 Ещё два числа ......................................................................................................... 216 Любопытное свойство чисел ............................................................................. 218 Как определил ошибку Чохбилмиш? .............................................................. 219 Гаусс .......................................................................................................................... 222 «Избранные» числа .............................................................................................. 223 Проект Эйлер. Задача 1 ....................................................................................... 227 Назойливый остаток ............................................................................................ 228 И такие есть числа ................................................................................................ 235 Шестизначное число ............................................................................................ 233 Три велосипедиста .............................................................................................. 237 Пятизначный куб ................................................................................................... 239 Репетитор ................................................................................................................. 240 Американское наследство .................................................................................. 242 Четыре числа .......................................................................................................... 243 Разность кубов ........................................................................................................ 245 Двузначное число ................................................................................................. 247 Квадратный куб ...................................................................................................... 248 Четырёхзначное число ........................................................................................ 254 Зачёркнутая цифра ................................................................................................ 250 8
Зачёркнутая цифра 2 ............................................................................................. 251 Индийские обезьяны 2 ........................................................................................ 252 Жизнь Демохара .................................................................................................... 256 Индийское число .................................................................................................. 258 Пифагорейское число ........................................................................................ 262 Диофантово число ............................................................................................... 260 Индийские квадраты ............................................................................................ 264 Половина и треть .................................................................................................. 266 Наименьшее число ............................................................................................... 268 Индийские пчёлы ................................................................................................. 270 Индийские обезьяны ............................................................................................ 277 Дедушка и внучка .................................................................................................. 283 На одно делится, на другое нет ........................................................................ 287 Кубическое число ................................................................................................. 292 Кузнечики................................................................................................................. 295 Дробный ряд ........................................................................................................... 272 Ещё один дробный ряд ........................................................................................ 273 Трёхзначное число 2 ............................................................................................ 274 Наименьшее число 2 ........................................................................................... 279 Метод SelectMany .................................................................................................. 281 Трёхзначное число 3 (2 в паскале) ................................................................. 285 Плюс-минус ........................................................................................................... 289 Минус-плюс ............................................................................................................ 290 Сколько мне лет? .................................................................................................. 298 Великолепная семёрка ......................................................................................... 301 Вьетнамские буйволы .......................................................................................... 304 Сотая цифра ............................................................................................................ 307 Как перевернуть число? ...................................................................................... 309 Время шуток ......................................................... 312 Ответы ............................................................... 325 Литература ........................................................ 340 9
Микстуры для ума 10
Кролики – это не болеки-лёлеки! Для начала и разбега найдите и пересчитайте всех кроликов, которые спрятались в волшебном лесу. 11
Микстура Решето Сундарама 12
Мы научились отлично просеивать числа с помощью и при участии решета Эратосфена. Но оказалось, что это числовое решето не единственное! В 1934 году индийский студент Сундарам придумал новое решето, которое просеивает составные числа, а простые остаются в решете. При этом учитываются только составные нечётные числа, поскольку существует только одно чётное простое число – двойка. Любое нечётное составное число можно представить в виде (2i+1)(2j+1), где i, j ≥ 1. После нехитрых преобразований получаем: 𝟐𝒎 + 𝟏, где 𝐦 = 𝟐𝐢𝐣 + 𝐢 + 𝐣. Алгоритм действует так: 1. Берём все натуральные числа 𝐦 от 𝟏до некоторого предела. 2. Вычёркиваем все числа вида 𝐦 = 𝟐𝐢𝐣 + 𝐢 + 𝐣 (для 𝑖, 𝑗 ≥ 1). 3. Для каждого оставшегося 𝐦 вычисляем 𝐩 = 𝟐𝐦 + 𝟏 - это все нечётные простые числа в заданном диапазоне. Число 𝟐 добавляем отдельно, потому что решето работает только с нечётными числами. Рассмотрим поучительный пример просеивания чисел через решето Сундарама, чтобы найти простые числа до 𝟑𝟎. Максимальное нечётное число в заданном диапазоне - 𝟐𝟗, и тогда 𝟐𝒎 + 𝟏 ≤ 𝟐𝟗, то есть 𝒎 ≤ 𝟏𝟒. Берём числа от 𝟏до 𝟏𝟒: 1,2,3,4,5,6,7,8,9,10,11,12,13,14 Теперь вычёркиваем все 𝐦, которые можно записать как 𝟐𝒊𝒋 + 𝒊 + 𝒋: • • • При 𝑖 = 1: 𝑗 = 1 ⇒ 2 ⋅ 1 ⋅ 1 + 1 + 1 = 4; 𝑗 = 2 ⇒ 2 ⋅ 1 ⋅ 2 + 1 + 2 = 7; 𝑗 = 3 ⇒ 10; 𝑗 = 4 ⇒ 13 При 𝑖 = 2: 𝑗 = 2 ⇒ 2 ⋅ 2 ⋅ 2 + 2 + 2 = 12 Дальше значения выходят больше 𝟏𝟒. Вычеркнутые 𝐦: 4,7,10,12,13. Оставшиеся 𝐦: 1,2,3,5,6,8,9,11,14. 13
Вычисляем 𝟐𝒎 + 𝟏: • • • • • • • • • 2⋅1+1= 3 2⋅2+1= 5 2⋅3+1= 7 2 ⋅ 5 + 1 = 11 2 ⋅ 6 + 1 = 13 2 ⋅ 8 + 1 = 17 2 ⋅ 9 + 1 = 19 2 ⋅ 11 + 1 = 23 2 ⋅ 14 + 1 = 29 Мы получили нечётные простые до 𝟐𝟗. Добавляем 𝟐 - и все простые числа до 𝟑𝟎 найдены. Ручное просеивание утомительно, поэтому пишем программу на Питоне, которая охотно выполнит все просевные работы за нас! Функция sieve_of_sundaram получает от нас верхнюю границу для поиска простых чисел, вычисляет m и создаёт массив чисел, то есть решето: # ВОЗВРАЩАЕМ РЕЗУЛЬТАТ ЦЕЛОЧИСЛЕННОГО ДЕЛЕНИЯ def div(a: int, b: int) -> int: """ Целочисленное деление с усечением к нулю, в отличие от //, который округляет к минус бесконечности. """ q = abs(a) // abs(b) if (a < 0) != (b < 0): q = -q return q # РЕШЕТО СУНДАРАМА def sieve_of_sundaram(limit: int) -> list[int]: # если limit < 2, то простых чисел нет: if limit < 2: return [] # вычисляем максимум для алгоритма (m = (limit-1) / 2): m = div(limit - 1, 2) 14
# создаём массив, чтобы отмечать составные числа. # (индексы 0..m, сначала все False) is_composite = [False] * (m + 1) Отмечаем составные числа: // ищем составных чисел: for var i := 1 to m do for var j := i to (m - i) div (2 * i + 1) do isComposite[i + j + 2 * i * j] := True; Создаём массив для простых чисел: # создаём массив для простых чисел и заполняем его: # первое простое число — это 2 # (limit >= 2 гарантировано проверкой выше): result = [2] for i in range(1, m + 1): if not is_composite[i]: result.append(2 * i + 1) return result В главной программе задаём верхнюю границу поиска простых чисел: # ГЛАВНАЯ ПРОГРАММА def main(): # вводим максимальное число для поиска простых чисел: limit = int(input('Введите предел для поиска простых чисел: ')) print() Получаем простые числа и распечатываем их на экране: 15
# получаем простые числа методом решета Сундарама: primes = sieve_of_sundaram(limit) # печатаем результат: print(f'Простые числа до {limit}:') for p in primes: print(p, end=' ') print() if __name__ == '__main__': main() Надеюсь мы не погрешили против алгоритма и решета Сундарама, поэтому без обиняков запускаем программу, чтобы отрешетить изрядно чисел: Введите предел для поиска простых чисел: 2027 Простые числа до 2027: 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359 367 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463 467 479 487 491 499 503 509 521 523 541 547 557 563 569 571 577 587 593 599 601 607 613 617 619 631 641 643 647 653 659 661 673 677 683 691 701 709 719 727 733 739 743 751 757 761 769 773 787 797 809 811 821 823 827 829 839 853 857 859 863 877 881 883 887 907 911 919 929 937 941 947 953 967 971 977 983 991 997 1009 1013 1019 1021 1031 1033 1039 1049 1051 1061 1063 1069 1087 1091 1093 1097 1103 1109 1117 1123 1129 1151 1153 1163 1171 1181 1187 1193 1201 1213 1217 1223 1229 1231 1237 1249 1259 1277 1279 1283 1289 1291 1297 1301 1303 1307 1319 1321 1327 1361 1367 1373 1381 1399 1409 1423 1427 1429 1433 1439 1447 1451 1453 1459 1471 1481 1483 1487 1489 1493 1499 1511 1523 1531 1543 1549 1553 1559 1567 1571 1579 1583 1597 1601 1607 1609 1613 1619 1621 1627 1637 1657 1663 1667 1669 1693 1697 1699 1709 1721 1723 1733 1741 1747 1753 1759 1777 1783 1787 1789 1801 1811 1823 1831 1847 1861 1867 1871 1873 1877 1879 1889 1901 1907 1913 1931 1933 1949 1951 1973 1979 1987 1993 1997 1999 2003 2011 2017 2027 И что мы видим? – Что не зря! Следующий – 2027-й – год будет простым. Наука не врёт! 16
Микстура Решето Сундарама 2 17
Не всегда нужны простые числа с самого начала, то есть с двойки. В этой программной микстуре мы усовершенствуем нашу предыдущую программу, чтобы она просеивала простые числа в заданном диапазоне: # РЕШЕТО СУНДАРАМА def sieve_of_sundaram(n: int): """ Функциональная версия решета Сундарама. Возвращает список простых чисел до n включительно. """ if n < 2: return [] limit = (n - 1) // 2 marked = [False] * (limit + 1) # функционально генерируем числа вида i + j + 2ij: for i in range(1, limit + 1): count = (limit - i) // (2 * i + 1) + 1 for j in range(i, i + count): x = i + j + 2 * i * j if x <= limit: marked[x] = True # формируем простые числа: 2k+1 для немаркированных k, плюс 2: primes = [2] + [2 * k + 1 for k in range(1, limit + 1) if not marked[k]] return [p for p in primes if p <= n] # РЕШАЕМ ЗАДАЧУ def primes_in_range(a: int, b: int): return [p for p in sieve_of_sundaram(b) if p >= a] # ГЛАВНАЯОСНОВНАЯ ПРОГРАММА def main(): a = int(input('Введите начало диапазона: ')) b = int(input('Введите конец диапазона: ')) if a > b: a, b = b, a # получаем простые числа: 18
primes = primes_in_range(a, b) # распечатываем их: print(f'Простые числа в диапазоне [{a}, {b}]:') for p in primes: print(p, end=' ') print() print(f'Всего найдено: {len(primes)}') if __name__ == '__main__': main() Получилось изрядно хорошо! Введите начало диапазона: 2000 Введите конец диапазона: 3000 Простые числа в диапазоне [2000, 3000]: 2003 2011 2017 2027 2029 2039 2053 2063 2069 2081 2099 2111 2113 2129 2131 2137 2141 2143 2153 2161 2213 2221 2237 2239 2243 2251 2267 2269 2273 2281 2309 2311 2333 2339 2341 2347 2351 2357 2371 2377 2393 2399 2411 2417 2423 2437 2441 2447 2459 2467 2521 2531 2539 2543 2549 2551 2557 2579 2591 2593 2633 2647 2657 2659 2663 2671 2677 2683 2687 2689 2711 2713 2719 2729 2731 2741 2749 2753 2767 2777 2801 2803 2819 2833 2837 2843 2851 2857 2861 2879 2909 2917 2927 2939 2953 2957 2963 2969 2971 2999 Всего найдено: 127 2083 2179 2287 2381 2473 2609 2693 2789 2887 2087 2203 2293 2383 2477 2617 2699 2791 2897 2089 2207 2297 2389 2503 2621 2707 2797 2903 19
Микстура Раз монетка, два монетка 20
А в этой программной микстуре мы решим пиратскую задачу на построение пирамидок из золотых пиастров: # РЕКУРСИВНО НАХОДИМ ВСЕ РАЗБИЕНИЯ def find_partitions(n: int, start: int, current_partition: list, level: int): if n == 0: # печатаем найденное разбиение: print(' + '.join(str(current_partition[i]) for i in range(level))) return for i in range(start, n + 1): current_partition[level] = i find_partitions(n - i, i + 1, current_partition, level + 1) # ГЛАВНАЯ ПРОГРАММА def main(): number = int(input('Введите число для разбиения: ')) # инициализируем массив для хранения текущего разбиения: current_partition = [0] * number # начинаем поиск разбиений с 1: print(f'Разбиения числа {number} на слагаемые, ' f'где каждое следующее больше предыдущего:') find_partitions(number, 1, current_partition, 0) if __name__ == '__main__': main() А давайте-ка разменяем червонец: Введите число для разбиения: 10 Разбиения числа 10 на слагаемые, где каждое следующее больше предыдущего: 1 + 2 + 3 + 4 1 + 2 + 7 1 + 3 + 6 1 + 4 + 5 1 + 9 21
2 + 2 + 3 + 4 + 10 3 + 5 8 7 6 Из гуманитарных соображений будем считать, что пирамида – это фигура, которая уменьшается/утончается от основания к вершине. Все разбиения числа 10, с нашей точки зрения, верны, поскольку на основании не лежат монетные слои той же монетной численности или даже больше. Следуя своим неизменным математическим принципам, мы построим единственную одноэтажную пирамиду: Двухэтажных пирамид – бери выше! – в четырежды раз больше: 22
Эволюционный переход к трёхэтажным пирамидам куда красивее! 23
24
А самая красивая, четырёхэтажная пирамида всего одна: 25
Фигуры из пиастров И тут на ум капитану Серпиенте пришла математическая мысль – вывести формулу для подсчёта пиастров в пирамиде любой высоты. Монет у капитана Серпиенте было вдоволь и в полном достатке. И были они отчеканены в двух вариантах, хотя второй вариант получился курьёзным из-за излишне выпитого рома чеканщиком монетного двора. И вот уже совершенно правдивая история об этом математическом чуде. Уже смеркалось, когда капитан Серпиенте многозначительно подумал: нехитрое это дело, выложить из пиастров треугольник, а ведь есть и другие, не менее примечательные геометрические фигуры. Напрягши свою заскорузлую пиратскую память, капитан Серпиенте вспомнил ещё квадраты, пятиугольники и шестиугольники. На этом его планиметрические познания закончились, и он хотел было позвонить другу, тоже пирату, капитану Флинту, но вспомнил, что до изобретения телефона осталось ещё несколько веков, и решил ограничиться подсказкой зала. Он покрутил барабан, но так неловко, что все патроны выпали из его револьвера и раскатились по всей капитанской каюте. Просить помощи капитан Серпиенте не захотел, а угрожать публике было уже нечем, поэтому он принялся выкладывать фигуры из подручных средств, коими вновь оказались пиастры. 26
Философский вопрос, - неожиданно для себя смекнул капитан Серпиенте, один пиастр – это уже треугольник или ещё нет? Вроде бы на треугольник он не похож: Если же приставить к нему ещё 2 пиастра, то получится настоящий треугольник, у которого каждая сторона равняется двум пиастрам: Но если длина сторон треугольника равна 1 пиастру, то и весь треугольник будет состоять из одного пиастра. 27
В те далёкие и даже дремучие времена не было ни Интернета, ни Википедии, так что каждый полагался только на свою голову. Так же поступил и капитан Серпиенте. А чтобы и у других не возникло сомнений на этот счёт, он издал декрет, что всякий одинокий пиастр – это треугольник. Самое сложное в этой научно-конструкторской головоломке было позади, и дальше дела у капитана Серпиенте пошли бойчее. Умелой рукой он быстро пристроил к пиастровому треугольнику следующий, третий уровень: А затем четвёртый: 28
И пятый: Ну, тут всё понятно, - воскликнул капитан Серпиенте, когда наличные пиастры у него закончились. – Для шестого уровня понадобится 6 пиастров, а для седьмого – 7… А всего, - он начал было пересчитывать пиастры в треугольнике, тыкая в них пальцем, - нужно… И тут он сообразил, что должна быть простая формула для этого дела, которая вмиг избавит его от докучной работы. Он ненароком вынул из-за обшлага бесполезную уже карту 29
сокровищ и вывел на ней знаменитую формулу, которой он гордился даже больше, чем всеми своими пиастрами. Конечно, хорошо было бы нам найти ту карту сокровищ и подсмотреть формулу капитана Серпиенте, да где ж её найдёшь? А сможете ли вы сами придумать формулу для подсчёта пиастров в треугольнике? И ещё одно пиратское задание: постройте из пиастров квадраты, пятиугольники и шестиугольники! 30
Микстура Самые простые палиндромы 31
Читаем в первом номере журнала Квантик за 2025 год о простых числах Мерсенна, которые многим должны быть известны. На всякий математический случай освежим память ума числами Мерсенна, которые обычно представляют себе в таком замысловатом виде: 𝑴𝒏 = 𝟐𝒏 − 𝟏, где 𝒏 — натуральное число. Например: 𝑴𝟐 = 𝟑, 𝑴𝟑 = 𝟕, 𝑴𝟓 = 𝟑𝟏, 𝑴𝟕 = 𝟏𝟐𝟕. Не числа такого рода и вида простые! 𝑴𝟒 = 𝟏𝟓 - составное. Но если число 𝐧 составное, то и 𝐌𝐧 тоже составное. Простые числа Мерсенна ищут днём и для чисел 𝐧, которые сами простые. Поэтому формулу для поиска чисел Мерсенна лучше и правильнее записать так: 𝑴𝒑 = 𝟐𝒑 − 𝟏, где p – это изрядно простое число. Эти простые числа своим именем обязаны французскому монаху Марену Мерсенну (1588–1648), который в 1644 году издал книгу Cogitata PhysicoMathematica. В ней можно найти список показателей 𝐧, для которых, как он полагал, выражение 𝟐𝒏 − 𝟏 даёт простые числа. Но Мерсенн допустил ошибки: например, он счёл 𝑴𝟔𝟕 простым, но позже Фрэнк Нельсон Коул доказал, что это не так, разложив это число на множители 139 707 721 и 761 838 257 287. Говорят, чтобы найти эти делители вручную, у него ушло «три года воскресений». И тут, конечно, всем захотелось найти новые простые числа мерсенновского вида! На сейчас самое большое число 32
Мерсенна - 𝟐𝟏𝟑𝟔𝟐𝟕𝟗𝟖𝟒𝟏 − 𝟏. Десятичная из 41 024 320 цифр. запись этого числа состоит Нас должна порадовать и удивить связь чисел Мерсенна с совершенными числами. Ещё Евклид заметил: если число 𝟐𝒑 − 𝟏 простое, то (𝟐𝒑 − 𝟏) × 𝟐𝒑−𝟏 - совершенное число. А Леонард Эйлер доказал, что и любое чётное совершенное число имеет такой вид. При внимательном чтении журнала мы найдём в нём и программу на Питоне для проверки чисел Мерсенна на простоту. Оказывается, не все числа поддались Марену Мерсенну. Мы воспользуемся программой на Питоне (Prime factors), чтобы найти некоторые из неподатливых чисел, а заодно поймём, почему в журнале список простых чисел в цикле for обрывается на числе 29: def prime_factors(num): factor = 2 while (num > 1): if (num % factor == 0): yield factor num = num / factor else: factor += 1 for k in [2,3,5,7,11,13,17,19,23,29]: m = 2**k-1 print("2^",k,"-1",end="=",sep="") print(*prime_factors(m),sep="x") Запускаем программу, и она подсказывает нам, что двойка, остепенённая простыми числами (за вычетом единицы) 11, 23 и 29, не даёт простые числа: 2^2-1=3 2^3-1=7 2^5-1=31 33
2^7-1=127 2^11-1=23x89 2^13-1=8191 2^17-1=131071 2^19-1=524287 2^23-1=47x178481 2^29-1=233x1103x2089 Проверка следующего простого числа 31, занимает очень много времени, поэтому было проигнорировано. Следующее непокорное число – 43 – поддаётся разложению на множители: 2^43-1=431x9719x2099863 Как нам обычно свойственно, давайте усложним себе задачу на поиск простых чисел. Мы найдём простые числа-палиндромы, которых много, поэтому потребуем от них, чтобы сумма цифр также была простым числом, а также и сумма цмфр суммы. Мы быстро найдём 28 чисел с такими свойствами: Простые-палиндромы с полностью 2: цепочка сумм -> [2] 3: цепочка сумм -> [3] 5: цепочка сумм -> [5] 7: цепочка сумм -> [7] 11: цепочка сумм -> [11, 2] 101: цепочка сумм -> [101, 2] 131: цепочка сумм -> [131, 5] 151: цепочка сумм -> [151, 7] 191: цепочка сумм -> [191, 11, 313: цепочка сумм -> [313, 7] 353: цепочка сумм -> [353, 11, 797: цепочка сумм -> [797, 23, 10301: цепочка сумм -> [10301, 10501: цепочка сумм -> [10501, 11311: цепочка сумм -> [11311, 13331: цепочка сумм -> [13331, простой цепочкой сумм цифр: 2] 2] 5] 5] 7] 7] 11, 2] 34
19391: 19991: 30103: 35753: 36563: 38183: 38783: 74747: 75557: 76367: 79997: 94349: цепочка цепочка цепочка цепочка цепочка цепочка цепочка цепочка цепочка цепочка цепочка цепочка сумм сумм сумм сумм сумм сумм сумм сумм сумм сумм сумм сумм -> -> -> -> -> -> -> -> -> -> -> -> [19391, [19991, [30103, [35753, [36563, [38183, [38783, [74747, [75557, [76367, [79997, [94349, 23, 29, 7] 23, 23, 23, 29, 29, 29, 29, 41, 29, 5] 11, 2] 5] 5] 5] 11, 11, 11, 11, 5] 11, 2] 2] 2] 2] 2] Всего найдено: 28 чисел Дальнейшее продвижение вперёд ограничивается возможностями компьютера и нашего терпения. Вот программа для ваших дальнейших экспериментов: def is_prime(n: int) -> bool: if n < 2: return False if n in (2, 3): return True if n % 2 == 0: return False i = 3 while i * i <= n: if n % i == 0: return False i += 2 return True def is_palindrome(n: int) -> bool: s = str(n) return s == s[::-1] def digit_sum(n: int) -> int: return sum(int(d) for d in str(n)) def prime_digit_sum_chain(n: int) -> list[int]: """ 35
Возвращает цепочку сумм цифр для числа n. Цепочка включает само n (если оно простое), затем суммы, пока не станет однозначным. """ chain = [n] current = n while current >= 10: current = digit_sum(current) chain.append(current) return chain def has_prime_chain_except_last_single(n: int) -> tuple[bool, list[int]]: """ Проверяет, что: - n — простое и палиндром - все элементы цепочки сумм (кроме, возможно, самого последнего однозначного) — простые. Возвращает (подходит ли, сама цепочка) """ if not (is_prime(n) and is_palindrome(n)): return False, [] chain = prime_digit_sum_chain(n) # Проверяем все элементы цепочки, кроме последнего (он может быть однозначным и простым) # Но если последний однозначный и не простой — это тоже нарушение. for x in chain: if not is_prime(x): return False, chain return True, chain # Основной цикл result = [] for num in range(1, 100001): ok, chain = has_prime_chain_except_last_single(num) if ok: result.append((num, chain)) print("Простые-палиндромы с полностью простой цепочкой сумм цифр:") for n, chain in result: print(f"{n}: цепочка сумм -> {chain}") print(f"\nВсего найдено: {len(result)} чисел") 36
Микстура Счастливые билеты 37
Во-первых, билет должен быть! Иначе получится, как в анекдоте. Бог вызвал апостола Петра и сразу вставил ему закавыку: Тут некий Иванов уже замучил меня жалобами, что он уже 20 лет не может выиграть в Спортлото. Эту несправедливость нужно срочно исправить. Ты понял? - Я понял, мой Бог, но я не могу выполнить Твоё задание. - И почему? - Дело в том, что этот Иванов за всё время не купил ни одного билета Спортлото. Во-вторых, если даже билет есть, то он не обязательно счастливый. Как показательно учит нас сценка Лотерея из шоу География Уральских Пельменей – Тунис, чужой выигрыш может привести к форменному мордобою! Вывод и мораль сего шоу таковы: никогда не дарите лотерейные билеты. Как учат нас евреи из тех же Уральских пельменей, везёт только соседям. А это уже вас может сильно огорчить. 38
Тоже очень редко счастливые билеты выпадают из рук на экзаменах. Такой неприятный случай произошёл со студентом-картёжником в комедии Операция Ы и другие приключения Шурика. Добрый профессор позволительно разрешил студенту взять 3 билета. Почему это возможно и даже хорошо, рассказывает старый анекдот. Точно такой же или даже подобный студент берёт билет, читает вопросы и спрашивает у профессора, можно ли ему взять другой билет. Профессор опять попался добрый, а это бывает редкое счастье, разрешил. Второй билет опять попался не в тему знаний, и студент запросил ещё один шанс. И тогда ассистент профессора возмутился непомерными запросами студента. На что добрый профессор (Айболит его фамилия) возразил: 39
- Если ищет, значит, что-то знает. Вот с кого мы должны брать пример! И тут мы подходим и приближаемся к теме нашего проекта. Опять же в шоу Уральских пельменей есть в наличии сценка Тяните билет. Она учит нас и вас, как правильно тянуть билет, чтобы он всегда оказался счастливым. А в этом проекте мы будем опознавать и считать счастливые билеты на транспорте. И тут, и здесь мы обязательно должны вспомнить рассказ Марка Твена Режьте, братцы, режьте, в коем также присутствует в наличии залипательный до навязчивости стишок: Кондуктор, отправляясь в путь, 40
Не рви билеты как-нибудь; Стриги как можно осторожней, Чтоб видел пассажир дорожный: Синий стоит восемь центов, Жёлтый стоит девять центов, Красный стоит только три. Осторожней режь, смотри! Вот фотогеничные примеры счастливых автобусных билетов: Ежели кто не чужд математики, тот, даже не ведая сути речи, догадается, что счастье удачи приносит не каждый билет, а особенный (как тайский массаж). Особенность таких билетов заключается в том, что сумма первых трёх цифр должна счастливо совпасть с суммой трёх последних цифр. Само по себе это ещё не счастье, а просто комбинаторно-вероятностная удача. Чтобы принести себе пользу счастья, билет нужно съесть. Если после этого вас не скрючит, а билет не вызовет отторжения, значит, с пищеварительным трактором у вас всё в полном порядке, а это, как учит нас медвеед Винни-Пух, и есть счастье на полный желудок. И как учит нас уже киножурнал Ералаш (Выпуск №45 На всякий случай), для счастливого и безопасного проезда в транспорте нужно покупать 2 билета, 41
поскольку один билет можно потерять, что иногда случалось и со мной. Но, как учит нас теория вероятностей, потерять можно и оба билета. И на этот случай нужно заранее позаботиться, озаботиться и купить за деньги проездной билет. Счастье уже близко! Поэтому мы смело можем браться за написание программы на Питоне, которая сосчитает все счастливые билеты и выдаст нам вероятность счастливого проезда на городском транспорте в час пик. Самый простой способ осчастливить себя питоническим кодом, перебрать весь миллион (106) шестизначных чисел от 000 000 до 999 999, посчитать и сравнить суммы на двух половинах этих чисел. Для современных компьютеров эта задача – не задача. Люди, слегка продвинутые в динамическом программировании, выберут более скоростной, но и весьма витиеватый способ – подсчитать, сколькими способами можно получить суммы трёх цифр от 0 до 27, а затем возвести в квадрат (у билета 2 цифровые половины) эти суммы и сложить. Для суммы 0 существует всего 1 счастливый билет – 000 000. Для суммы 1 – 9: для каждой половины получить сумму 1 можно тремя способами: 001, 42
010, 100. Комбинируя их между собой, получаем 32, то есть 9. Для двойки расклад сумм усугубляется: 62 = 36. Больше всего счастливых билетов с суммами 13 (!) и 14 – 752 = 5625. Всего существует 55 252 счастливых билета, что составляет примерно 5.5 процента. Более понятно – примерно каждый 18-й билет счастливый. Поэтому, входя в зад автобуса, пропустите перед собой 17 старушек – и будет вам счастье. Чтобы не лишать вас счастья покопаться в исходном коде программы, привожу её целиком для ваших самостоятельных разборок: import matplotlib.pyplot as plt import numpy as np import time def count_ways_dp(num_digits=3, base=10): """ Динамическое программирование: считаем, сколькими способами можно получить каждую сумму num_digits цифрами от 0 до base-1. """ max_sum = num_digits * (base - 1) # dp[s] = количество способов получить сумму s dp = [0] * (max_sum + 1) # 0 цифр дают сумму 0 одним способом (пустое множество) dp[0] = 1 for _ in range(num_digits): new_dp = [0] * (max_sum + 1) for s in range(max_sum + 1): if dp[s] > 0: for digit in range(base): if s + digit <= max_sum: new_dp[s + digit] += dp[s] dp = new_dp return dp def count_happy_tickets_dp(num_digits=6, base=10): """Быстрый подсчёт счастливых билетов через ДП.""" half = num_digits // 2 ways = count_ways_dp(half, base) # Счастливый билет = обе половины имеют одинаковую сумму 43
return sum(w * w for w in ways), ways def count_happy_tickets_naive(num_digits=6, base=10): """Наивный перебор — для проверки.""" count = 0 half = num_digits // 2 total = base ** num_digits for ticket in range(total): # Извлекаем цифры digits = [] temp = ticket for _ in range(num_digits): digits.append(temp % base) temp //= base digits.reverse() first_half = sum(digits[:half]) second_half = sum(digits[half:]) if first_half == second_half: count += 1 return count def find_all_happy_tickets(num_digits=6, base=10): """Находит все счастливые билеты (для малых размеров).""" happy = [] half = num_digits // 2 total = base ** num_digits for ticket in range(total): digits = [] temp = ticket for _ in range(num_digits): digits.append(temp % base) temp //= base digits.reverse() if sum(digits[:half]) == sum(digits[half:]): happy.append(ticket) return happy def visualize_distribution(ways, total_happy, total_tickets): 44
"""Визуализирует распределение сумм и вклад в счастливые билеты.""" fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5), facecolor='white') sums = np.arange(len(ways)) # График 1: Распределение способов получить сумму colors1 = plt.cm.Blues(np.linspace(0.3, 0.9, len(ways))) bars1 = ax1.bar(sums, ways, color=colors1, edgecolor='black', linewidth=0.8) ax1.set_xlabel('Сумма трёх цифр', fontsize=12, fontweight='bold') ax1.set_ylabel('Количество способов', fontsize=12, fontweight='bold') ax1.set_title('Сколькими способами можно получить сумму тремя цифрами', fontsize=13, fontweight='bold', pad=15) ax1.grid(True, alpha=0.3, axis='y') # Подписи для ключевых значений for bar, way in zip(bars1, ways): if way > 0: ax1.text(bar.get_x() + bar.get_width() / 2., bar.get_height(), f'{way}', ha='center', va='bottom', fontsize=8) # График 2: Вклад каждой суммы в счастливые билеты contributions = [w * w for w in ways] colors2 = plt.cm.Reds(np.linspace(0.3, 0.9, len(contributions))) bars2 = ax2.bar(sums, contributions, color=colors2, edgecolor='black', linewidth=0.8) ax2.set_xlabel('Сумма трёх цифр', fontsize=12, fontweight='bold') ax2.set_ylabel('Счастливых билетов с этой суммой', fontsize=12, fontweight='bold') ax2.set_title(f'Вклад каждой суммы в общее число счастливых билетов\nВсего: {total_happy}', fontsize=13, fontweight='bold', pad=15) ax2.grid(True, alpha=0.3, axis='y') for bar, contrib in zip(bars2, contributions): if contrib > 0: ax2.text(bar.get_x() + bar.get_width() / 2., bar.get_height(), f'{contrib}', ha='center', va='bottom', fontsize=8) plt.tight_layout() plt.show() def print_statistics(num_digits=6, base=10): """Выводит полную статистику.""" print(f"\n🎫 ЗАДАЧА О СЧАСТЛИВЫХ БИЛЕТАХ") 45
print("=" * 60) print(f"📐 Билет: {num_digits}-значный, основание: {base}") print(f"📊 Всего возможных номеров: {base ** num_digits:,}") # Быстрый способ start = time.time() total_happy, ways = count_happy_tickets_dp(num_digits, base) time_dp = time.time() - start print(f"\n⚡ Метод ДП: {total_happy:,} счастливых билетов") print(f" Время: {time_dp * 1000:.3f} мс") # Наивный способ (только для малых размеров) if base ** num_digits <= 1_000_000: start = time.time() total_naive = count_happy_tickets_naive(num_digits, base) time_naive = time.time() - start print(f"\n🐢 Наивный перебор: {total_naive:,} счастливых билетов") print(f" Время: {time_naive * 1000:.1f} мс") print(f" ✅ Результаты совпадают: {total_happy == total_naive}") # Вероятность prob = total_happy / (base ** num_digits) print(f"\n🎲 Вероятность получить счастливый билет: {prob:.6f}") print(f" ≈ 1 из {int(1 / prob):,}") print(f" ≈ {prob * 100:.4f}%") # Статистика по суммам print(f"\n📈 Распределение по суммам:") print(f"{'Сумма':<8}{'Способов':<12}{'Вклад в счастливые':<20}") print("-" * 40) for s, w in enumerate(ways): if w > 0: print(f"{s:<8}{w:<12}{w * w:<20}") return total_happy, ways def interactive_checker(): """Интерактивная проверка конкретных билетов.""" print("\n🔍 Интерактивная проверка билетов") print("=" * 60) print("Введи номер билета (или 'q' для выхода)") while True: user_input = input("\nНомер билета: ").strip() if user_input.lower() == 'q': 46
break # Убираем пробелы clean = user_input.replace(" ", "") if not clean.isdigit() or len(clean) != 6: print("❌ Введи ровно 6 цифр") continue digits = [int(d) for d in clean] first_sum = sum(digits[:3]) second_sum = sum(digits[3:]) print(f"📝 Билет: {clean}") print(f" Первая половина: {' + '.join(map(str, digits[:3]))} = {first_sum}") print(f" Вторая половина: {' + '.join(map(str, digits[3:]))} = {second_sum}") if first_sum == second_sum: print(f" 🎉 СЧАСТЛИВЫЙ БИЛЕТ!") else: print(f" 😔 Обычный билет (разность: {abs(first_sum - second_sum)})") def show_examples(): """Показывает примеры счастливых билетов.""" print("\n✨ Примеры счастливых билетов (первые 20):") print("=" * 60) happy = find_all_happy_tickets(6, 10)[:20] for i, ticket in enumerate(happy, 1): s = f"{ticket:06d}" digits = [int(d) for d in s] sum_first = sum(digits[:3]) print( f"{i:2d}. {s[:3]} {s[3:]} → {'+'.join(map(str, digits[:3]))} = {'+'.join(map(str, digits[3:]))} = {sum_first}") def main(): # Основная статистика для классической задачи total_happy, ways = print_statistics(6, 10) # Визуализация visualize_distribution(ways, total_happy, 10 ** 6) 47
# Примеры show_examples() # Обобщение на другие размеры print("\n\n🌐 ОБОБЩЕНИЕ НА ДРУГИЕ РАЗМЕРЫ:") print("=" * 60) print(f"{'Цифр':<10}{'Счастливых':<15}{'Вероятность':<15}") print("-" * 40) for n in [2, 4, 6, 8, 10]: total, _ = count_happy_tickets_dp(n, 10) prob = total / (10 ** n) print(f"{n:<10}{total:<15,}{prob:<15.6f}") # Интерактивный режим interactive_checker() if __name__ == "__main__": main() Если вам это не нужно и необязательно, то сразу ознакомьтесь с результатами наших исследований транспортного счастья: 🎫 ЗАДАЧА О СЧАСТЛИВЫХ БИЛЕТАХ ============================================================ 📐 Билет: 6-значный, основание: 10 📊 Всего возможных номеров: 1,000,000 ⚡ Метод ДП: 55,252 счастливых билетов Время: 0.061 мс 🐢 Наивный перебор: 55,252 счастливых билетов Время: 1254.7 мс ✅ Результаты совпадают: True 🎲 Вероятность получить счастливый билет: 0.055252 ≈ 1 из 18 ≈ 5.5252% 📈 Распределение по суммам: Сумма Способов Вклад в счастливые 48
---------------------------------------0 1 1 1 3 9 2 6 36 3 10 100 4 15 225 5 21 441 6 28 784 7 36 1296 8 45 2025 9 55 3025 10 63 3969 11 69 4761 12 73 5329 13 75 5625 14 75 5625 15 73 5329 16 69 4761 17 63 3969 18 55 3025 19 45 2025 20 36 1296 21 28 784 22 21 441 23 15 225 24 10 100 25 6 36 26 3 9 27 1 1 Для наглядности окулярно-зрелищного восприятия всмотритесь в колокольчатые графики, которые указывают нам на симметрию восхождения и заката счастья во всех его жизненных смыслах: 49
Счастье всегда рядом – либо сзади, либо спереди. И только изредка – в нас самих. Такова жёсткая правда о жизненном счастье… Есть и другой вид счастливых билетов, который называют ленинградским. Здесь для счастья нужно немного – чтобы совпали суммы цифр, стоящих на чётных и нечётных местах в номере билета. При этом некоторые московские счастливые билеты окажутся счастливыми и по-ленинградски. Тут мы можем до нужности вспомнить Признак делимости на 11 из книги Как приручить Питона. 50
Самое любопытное правило; не все его знают, но оно помогает очень просто определить, делится ли, например, номер автобусного билета на 11. Чтобы узнать, делится ли число на 11, нужно подсчитать отдельно сумму цифр, стоящих на нечётных и чётных местах в исходном числе. Если они равны, то число кратно 11. В противном случае нужно из первой суммы вычесть вторую. Если разность делится на 11, то и всё число делится на 11. Если сумма первых трёх цифр равна сумме трёх последних, то такой билет называется счастливым. Например, число 123453 делится на 11, так как 1 + 3 + 5 = 2 + 4 + 3 = 9. А число 123456 не делится (проверьте сами!). Было бы желательно и великолепно пересчитать все счастливые числа поновому, хотя нетрудно, но приятно сообразить, что таких чисел окажется ровно столько же. Для тренировки понимания пишем небольшую программу, без всяких там выкрутасов: def solve(): count = 0 total = 1000000 for ticket in range(total): s = str(ticket).zfill(6) sum_odd = sum(int(d) for d in s[0::2]) sum_even = sum(int(d) for d in s[1::2]) if sum_odd == sum_even: print(s) count += 1 return count def main(): count = solve() print(f"Всего счастливых чисел: {count}") 51
if __name__ == "__main__": main() Она может огорчить нас излишней недоверчивостью к интуиции, но и порадовать, что последняя нас не подвела: 000000 000011 000022 000033 . . . 123453 . . . 999933 999944 999955 999966 999977 999988 999999 Всего счастливых чисел: 55252 Теперь мы можем спать спокойно и даже счастливо - хоть ночью, хоть днём – ежели под одеялом. 52
Микстура Константа Капрекара 53
А вот не столь счастливая, но более неожиданная задача с числами. Индийский математик Даттарая Рамчандра Капрекар открыл удивительное свойство четырёхзначных чисел: • • • • • • Возьми любое 4-значное число (не все цифры одинаковые) Расположи цифры в порядке убывания → получишь число A Расположи цифры в порядке возрастания → получишь число B Вычти: A - B Повтори процесс с результатом Через максимум 7 шагов ты ВСЕГДА получишь число 6174! Это число называется константой Капрекара. После её получения процесс зацикливается: 7641 - 1467 = 6174. Программа получилась изрядно длинная, с излишествами, но ясно-понятная: import matplotlib.pyplot as plt import numpy as np def kaprekar_routine(number, verbose=True): """ Выполняет процесс Капрекара для 4-значного числа. Возвращает список шагов и количество итераций до 6174. """ KAPREKAR_CONSTANT = 6174 steps = [number] iterations = 0 while number != KAPREKAR_CONSTANT: iterations += 1 # Дополняем нулями слева если нужно num_str = f"{number:04d}" # Сортируем цифры digits_desc = sorted(num_str, reverse=True) digits_asc = sorted(num_str) # Создаём числа a = int(''.join(digits_desc)) 54
b = int(''.join(digits_asc)) # Вычисляем разность number = a - b steps.append(number) if verbose: print(f"Шаг {iterations}: {a} - {b} = {number}") return steps, iterations def visualize_kaprekar_path(number): """Визуализирует путь к константе Капрекара.""" print(f"\n🔢 Число: {number}") print("=" * 50) steps, iterations = kaprekar_routine(number, verbose=True) print(f"\n✅ Всего шагов: {iterations}") print(f"📊 Путь: {' → '.join(map(str, steps))}") # Визуализация fig, ax = plt.subplots(figsize=(12, 6), facecolor='white') x = np.arange(len(steps)) y = steps # Создаём градиент от красного к зелёному colors = plt.cm.RdYlGn_r(np.linspace(0.2, 0.8, len(steps))) ax.plot(x, y, marker='o', linewidth=2, markersize=10, color='steelblue') # Раскрашиваем точки for i, (xi, yi, color) in enumerate(zip(x, y, colors)): ax.scatter(xi, yi, color=color, s=200, zorder=5, edgecolors='black', linewidth=2) ax.annotate(str(yi), (xi, yi), textcoords="offset points", xytext=(0, 15), ha='center', fontsize=10, fontweight='bold') ax.set_xlabel('Шаг', fontsize=12, fontweight='bold') ax.set_ylabel('Значение', fontsize=12, fontweight='bold') ax.set_title(f'Путь к константе Капрекара (6174)\nСтартовое число: {number}', fontsize=14, fontweight='bold', pad=20) ax.grid(True, alpha=0.3) 55
ax.set_xticks(x) # Подсветка финального значения ax.axhline(y=6174, color='green', linestyle='--', linewidth=2, alpha=0.5, label='6174 (константа)') ax.legend(fontsize=10) plt.tight_layout() plt.show() def analyze_all_numbers(): """Анализирует все 4-значные числа и строит статистику.""" print("\n📊 Анализ всех 4-значных чисел...") print("=" * 50) iterations_count = {} max_iterations = 0 max_number = None for num in range(1000, 10000): # Пропускаем числа с одинаковыми цифрами if len(set(f"{num:04d}")) == 1: continue _, iterations = kaprekar_routine(num, verbose=False) if iterations not in iterations_count: iterations_count[iterations] = 0 iterations_count[iterations] += 1 if iterations > max_iterations: max_iterations = iterations max_number = num print(f"\n📈 Статистика количества шагов:") for iters in sorted(iterations_count.keys()): count = iterations_count[iters] bar = '█' * (count // 100) print(f"{iters} шагов: {count:5d} чисел {bar}") print(f"\n🏆 Максимум: {max_iterations} шагов для числа {max_number}") # Визуализация статистики fig, ax = plt.subplots(figsize=(10, 6), facecolor='white') x = sorted(iterations_count.keys()) 56
y = [iterations_count[i] for i in x] bars = ax.bar(x, y, color='steelblue', edgecolor='black', linewidth=1.5) # Добавляем значения на столбцы for bar, count in zip(bars, y): height = bar.get_height() ax.text(bar.get_x() + bar.get_width() / 2., height, f'{count}', ha='center', va='bottom', fontsize=10, fontweight='bold') ax.set_xlabel('Количество шагов до 6174', fontsize=12, fontweight='bold') ax.set_ylabel('Количество чисел', fontsize=12, fontweight='bold') ax.set_title('Распределение количества шагов для всех 4-значных чисел', fontsize=14, fontweight='bold', pad=20) ax.grid(True, alpha=0.3, axis='y') plt.tight_layout() plt.show() def main(): #analyze_all_numbers() #return print("🔮 Константа Капрекара (6174)") print("=" * 50) while True: n4 = int(input('Введите четырёхзначное число > ')) # Пропускаем числа с одинаковыми цифрами if len(set(f"{n4:04d}")) == 1: continue if 1000 <= n4 <= 9999: visualize_kaprekar_path(n4) else: print("❌ Число должно быть от 1000 до 9999") continue #visualize_kaprekar_path(n4) print() # Запускаем интерактивный режим #interactive_mode() if __name__ == "__main__": main() 57
Константа Капрекара (6174) Число: 2026 ================================================== Шаг 1: 6220 - 226 = 5994 Шаг 2: 9954 - 4599 = 5355 Шаг 3: 5553 - 3555 = 1998 Шаг 4: 9981 - 1899 = 8082 Шаг 5: 8820 - 288 = 8532 Шаг 6: 8532 - 2358 = 6174 Всего шагов: 6 Путь: 2026 → 5994 → 5355 → 1998 → 8082 → 8532 → 6174 Введите четырёхзначное число > 2027 Число: 2027 ================================================== Шаг 1: 7220 - 227 = 6993 Шаг 2: 9963 - 3699 = 6264 Шаг 3: 6642 - 2466 = 4176 Шаг 4: 7641 - 1467 = 6174 Всего шагов: 4 Путь: 2027 → 6993 → 6264 → 4176 → 6174 Дополнительно, то есть исключительно при полном согласии с желанием вы можете пошагово визуализовать процесс превращения вашего числа в число Капрекара: 58
Поскольку четырёхзначных чисел не зело много, вы можете, не выходя из этой программы, перебрать их все в автоматическом режиме и прямо на себе убедиться, что да. 59
Микстура LeetCode 1249. Minimum Remove to Make Valid Parentheses 60
Вот вся, целиком оригинальная трактовка задачи: Given a string s of '(' , ')' and lowercase English characters. Your task is to remove the minimum number of parentheses ( '(' or ')', in any positions ) so that the resulting parentheses string is valid and return any valid string. Formally, a parentheses string is valid if and only if: • • • It is the empty string, contains only lowercase characters, or It can be written as AB (A concatenated with B), where A and B are valid strings, or It can be written as (A), where A is a valid string. Example 1: Input: s = "lee(t(c)o)de)" Output: "lee(t(c)o)de" Explanation: "lee(t(co)de)" , "lee(t(c)ode)" would also be accepted. Example 2: Input: s = "a)b(c)d" Output: "ab(c)d" Example 3: Input: s = "))((" Output: "" Explanation: An empty string is also valid. Constraints: • • 1 <= s.length <= 105 s[i] is either '(' , ')', or lowercase English letter. В переводе на человеческий язык нам нужно удалить минимальное число скобок из заданной строки, чтобы оставшиеся скобки были правильными, то есть корректно открывались и закрывались. Для решения этой задачи можно использовать стек, чтобы отслеживать позиций открытых скобок и связанных с ними закрытых скобок, а затем удалить из строки ненужные скобки. 61
Алгоритм наших заскорузлых размышлений был таков: • Сначала мы проходим по строке и добавляем символ в список result_str. Если встречаем закрывающую скобку ), то проверяем баланс: если он равен нулю, игнорируем скобку; иначе, добавляем её и уменьшаем баланс. Если встречаем открывающую скобку (, увеличиваем баланс и сохраняем её позицию на стеке. • Затем удаляем из результата лишние открывающие скобки, используя ранее сохраненные позиции из стека. • Возвращаем строку, полученную из списка result_str, и печатаем результаты проверки: def solve(s: str) -> str: """ LeetCode 1249. Minimum Remove to Make Valid Parentheses. Удаляет минимальное число скобок, чтобы строка стала валидной. """ balance = 0 # индексы позиций непарных '(' в result_str: open_stack = [] result_str = [] # первый проход -> # удаляем лишние закрывающие скобки: for ch in s: if ch == '(': balance += 1 open_stack.append(len(result_str)) result_str.append(ch) elif ch == ')': if balance == 0: continue else: balance -= 1 result_str.append(ch) else: result_str.append(ch) # второй проход -> # удаляем лишние открывающие скобки: while open_stack: 62
if balance > 0: idx = open_stack.pop() del result_str[idx] balance -= 1 else: break # конвертируем список символов в строку: return ''.join(result_str) # ПРОВЕРЯЕМ, ЯВЛЯЕТСЯ ЛИ СТРОКА # ПРАВИЛЬНОЙ ПОСЛЕДОВАТЕЛЬНОСТЬЮ СКОБОК def is_valid_parentheses(s: str) -> bool: balance = 0 for ch in s: if ch == '(': balance += 1 elif ch == ')': balance -= 1 if balance < 0: return False return balance == 0 # ВОЗВРАЩАЕМ ЧИСЛО УДАЛЯЕМЫХ СИМВОЛОВ def removed_count(original: str, fixed: str) -> int: return len(original) - len(fixed) # ВОЗВРАЩАЕМ ТЕОРЕТИЧЕСКИЙ МИНИМУМ УДАЛЕНИЙ def min_removals_needed(s: str) -> int: balance = 0 extra_close = 0 for ch in s: if ch == '(': balance += 1 elif ch == ')': if balance == 0: extra_close += 1 else: balance -= 1 # balance - лишние '(' до конца строки: return extra_close + balance # ПРОВЕРЯЕМ РЕШЕНИЕ def check(s: str): result = solve(s) 63
valid = is_valid_parentheses(result) removed = removed_count(s, result) theoretical_min = min_removals_needed(s) print(f'Вход: {s}') print(f'Результат: {result}') print(f'Валидна: {valid}') print(f'Удалено символов: {removed} (минимум: {theoretical_min})') assert valid, 'Результат должен быть правильной скобочной последовательностью!' assert removed == theoretical_min, 'Удалено больше минимума символов!' print('OK: минимальное число удалений, результат верен.\n') # ОСНОВНАЯ ПРОГРАММА def main(): check('a)b(c)d') check('))((') check('(a(b(c)d)') # ожидаем "ab(c)d" # ожидаем "" # один из корректных ответов, напр. "(a(bc)d)" if __name__ == '__main__': main() Запускаем программу. Всё верно (пустую строку не видно!): Вход: a)b(c)d Результат: ab(c)d Валидна: True Удалено символов: 1 (минимум: 1) OK: минимальное число удалений, результат верен. Вход: ))(( Результат: Валидна: True Удалено символов: 4 (минимум: 4) OK: минимальное число удалений, результат верен. Вход: (a(b(c)d) Результат: (a(bc)d) Валидна: True Удалено символов: 1 (минимум: 1) OK: минимальное число удалений, результат верен. 64
Микстура Головоломные скобки 65
Давайте изловчимся и переделаем задачу в головоломку, чтобы не только программисты могли бы считать и удалять скобки. • Случайным образом расставляем заданное число скобок и точек. Если неожиданно получилось готовое решение, то составляем головоломку заново. • Решаем головоломку. • Печатаем число символов, условие головоломки и ответ. Мы пишем программу на Питоне, остальные – Деду Морозу: import random from enum import Enum # СЛОЖНОСТЬ ГОЛОВОЛОМКИ class Difficulty(Enum): EASY = 1 MEDIUM = 2 HARD = 3 # ПРОВЕРЯЕМ СКОБКИ def is_valid_parentheses(s: str) -> bool: balance = 0 for ch in s: if ch == '(': balance += 1 elif ch == ')': balance -= 1 if balance < 0: return False return balance == 0 # СОСТАВЛЯЕМ ГОЛОВОЛОМКУ def generate_puzzle(difficulty: Difficulty) -> str: sym = '().' puzzle = [] if difficulty == Difficulty.EASY: total = 12 66
elif difficulty == Difficulty.MEDIUM: total = 16 else: total = 20 print(total, end=' ') for _ in range(total): p = random.randrange(3) + 1 puzzle.append(sym[p - 1]) s = ''.join(puzzle) if is_valid_parentheses(s): generate_puzzle(difficulty) return s # РЕШАЕМ ГОЛОВОЛОМКУ def solve(s: str) -> str: balance = 0 open_stack = [] result_str = [] # первый проход -> # удаляем лишние закрывающие скобки: for ch in s: if ch == '(': balance += 1 open_stack.append(len(result_str)) result_str.append(ch) elif ch == ')': if balance == 0: continue else: balance -= 1 result_str.append(ch) else: result_str.append(ch) # второй проход -> # удаляем лишние открывающие скобки: while open_stack: if balance > 0: idx = open_stack.pop() del result_str[idx] 67
balance -= 1 else: break # конвертируем список символов в строку: rs = ''.join(result_str) return rs # ОСНОВНАЯ ПРОГРАММА def main(): # составляем головоломку: puzzle = generate_puzzle(Difficulty.EASY) # печатаем условие и ответ: print('Easy: ') print(puzzle) res = solve(puzzle) print(res) print(is_valid_parentheses(res)) # puzzle = generate_puzzle(Difficulty.MEDIUM) # puzzle = generate_puzzle(Difficulty.HARD) if __name__ == '__main__': main() Неожиданно для себя предположим, что наша любезная программа напечатала нам условие и решение скобочной головоломки: 12 Easy: ))(.((().(() (.().) Поскольку нам нужна головоломка для обычных граждан, то давайте обозначим открывающие скобки зелёным цветом, закрывающие – красным, а посторонние примеси – жёлтым. Тогда головоломка предстанет перед нами во всей своей красе: 68
Из неё нужно убрать минимальное число прямоугольников, или блоков, чтобы каждому зелёному блоку соответствовал красный по правилу матрёшки. При этом мы должны сохранить все жёлтые блоки в целости и сохранности, что облегчает нашу задачу. После тщательной уборки лишних блоков удалением, сдвигаем остальные блоки до соприкосновения, поскольку природа не любит пустоты – она любительница тесноты! В данной головоломке находим первый зелёный блок (открывающий), после него стоят только 2 красных блока. Это значит, что зелёных блоков не больше двух. После этого задача легко решается: Нужно удалить 6 блоков. Чтобы избежать двусмысленности при удалении блоков, сдвигаем оставшиеся блоки так, чтобы получить безукоризненный ответ. → Теперь вы можете в своё или чужое удовольствие решать программистские задачи, даже не зная, что это такое! 69
Задача 2 – для самостоятельного решения Задача 3 Задача 4 Задача 5 70
Задача 6 Задача 7 Задача 8 Задача 9 71
Задача 10 72
Микстура Головоломные палиндромы 73
В некоторых случаях, то есть редко головоломки со скобками получаются симметричными, то есть палиндромическими. Мы не можем и не должны упускать эту возможность! Наша новая созидательная программа будет придумывать задания, аналогичные предыдущим, но не со скобками, а с палиндромами. Причудливый алгоритм составления головоломок такой: • Составляем короткий палиндром из трёх символов случайной длины. • Вставляем в некоторые позиции короткого палиндрома случайные символы, чтобы общая длина строки была равна 12. • Используя метод динамического программирования, выделяем из длинной строки максимальный палиндром. • Печатаем задание и ответ. import random # массив доступных символов SYMBOLS = ['З', 'Ж', 'К', 'С', 'Ф'] def generate_palindrome(length: int, num_symbols: int) -> str: # слишком маленькая длина: if length < 1: return '' # слишком мало добавляем: if num_symbols < 1: num_symbols = 1 # составляем палиндром: half_length = length // 2 result = '' for _ in range(half_length): result += SYMBOLS[random.randrange(num_symbols)] # Если длина палиндрома нечетная, # добавляем символ в середину, # иначе добавляем только обратную часть: if length % 2 == 0: result = result + result[::-1] else: result = result + SYMBOLS[random.randrange(num_symbols)] + result[::-1] 74
return result # СОЗДАЁМ ГОЛОВОЛОМКУ def generate_puzzle(base: str, extra_chars: int) -> str: puzzle = base for _ in range(extra_chars): # точка вставки: от начала строки (0) до самого конца (len(puzzle)) pos = random.randrange(len(puzzle) + 1) c = SYMBOLS[random.randrange(3)] # вставляем ровно один символ c в позицию pos, не дублируя соседние символы puzzle = puzzle[:pos] + c + puzzle[pos:] return puzzle # ВОЗВРАЩАЕМ ДЛИННЫЙ ПАЛИНДРОМ def extract_max_palindrome(s: str) -> str: n = len(s) dp = [[0] * n for _ in range(n)] # динамическое программирование: for i in range(n): dp[i][i] = 1 # заполняем таблицу: for k in range(2, n + 1): for i in range(0, n - k + 1): j = i + k - 1 if s[i] == s[j]: dp[i][j] = dp[i + 1][j - 1] + 2 else: dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]) # восстанавливаем палиндром: i, j = 0, n - 1 result_left = '' result_right = '' while i <= j: if s[i] == s[j]: result_left += s[i] if i != j: result_right = s[j] + result_right i += 1 j -= 1 elif dp[i + 1][j] > dp[i][j - 1]: i += 1 75
else: j -= 1 return result_left + result_right # ОСНОВНАЯ ПРОГРАММА def main(): # длина строки из трёх символов: length = random.randrange(3) + 5 palindrome = generate_palindrome(length, 3) print('Заготовка палиндрома:', palindrome) # вставляем дополнительные символы: extra = 12 - length puzzle = generate_puzzle(palindrome, extra) # извлекаем максимальный палиндром: solution = extract_max_palindrome(puzzle) # печатаем задание и ответ: print('Задание:', puzzle) print('Решение:', solution) if __name__ == '__main__': main() Вот и первое заданье: ЖЗКЖКЗЖЗКЗЗК ЖЗКЖКЗЖ Или в переводе на цветные блоки: 76
Тут нам сразу и сильно повезло! Легко заметить, что первые 7 блоков расположены палиндромично. Остальная пятёрка блоков палиндромичности не добавляет. И вот ответ уже готов: Или в компактном, ужатом виде: Как ни крути, а палиндром будет! Не следует думать и надеяться, что так будет везти всегда, поэтому придётся перебирать варианты, идя навстречу себе одновременно слева и справа. Задача 2 – для самостоятельного решения 77
Задача 3 Задача 4 Задача 5 Задача 6 78
Задача 7 Задача 8 Задача 9 Задача 10 79
Красивые шейдеры 80
Мы научились писать простые шейдеры в книге Матеграфика на Питоне. Но есть мастера своего дела, которые изловчились писать великолепные шейдеры, которые мы не можем обойти стороной – ими нужно любоваться! 81
Проект Polygons & Patterns Первый шейдер называется Polygons & Patterns. Вы можете найти его на сайте: https://www.shadertoy.com/view/lsBSDz Вот оригинальный код шейдера: // Created by Vinicius Graciano Santos - vgs/2014 // https://www.shadertoy.com/view/lsBSDz #define TAU 6.28318530718 float segment(vec2 p, vec2 a, vec2 b) { vec2 ab = b - a; vec2 ap = p - a; float k = clamp(dot(ap, ab)/dot(ab, ab), 0.0, 1.0); return smoothstep(0.0, 5.0/iResolution.y, length(ap - k*ab) - 0.001); } float shape(vec2 p, float angle) { float d = 100.0; vec2 a = vec2(1.0, 0.0), b; vec2 rot = vec2(cos(angle), sin(angle)); for (int i = 0; i < 6; ++i) { b = a; for (int j = 0; j < 18; ++j) { b = vec2(b.x*rot.x - b.y*rot.y, b.x*rot.y + b.y*rot.x); d = min(d, segment(p, a, b)); } a = vec2(a.x*rot.x - a.y*rot.y, a.x*rot.y + a.y*rot.x); } return d; } void mainImage( out vec4 fragColor, in vec2 fragCoord ) { vec2 uv = fragCoord.xy / iResolution.xy; vec2 cc = (-iResolution.xy + 2.0*fragCoord.xy) / iResolution.y; float col = shape(abs(cc), cos(0.01*(iTime+22.0))*TAU); col *= 0.5 + 1.5*pow(uv.x*uv.y*(1.0-uv.x)*(1.0-uv.y), 0.3); fragColor = vec4(vec3(pow(col, 0.45)),1.0); } Это не шейдер GLSL, поэтому мы должны его переосмыслить и переписать! 82
На этот случай у нас есть переводная таблица: Shadertoy py5 / GLSL iResolution.xy resolution (uniform vec2) iTime time (uniform float) iFrame frame (uniform int, если нужно) mainImage(out vec4 col, in vec2 coord) void main(void) fragColor = ... gl_FragColor = ... fragCoord gl_FragCoord.xy Также мы должны учитывать, что на Shadertoy ось Y она идёт снизу вверх, а в py5 экранная Y идёт сверху вниз. В итоге у нас получился такой шейдер: uniform vec2 resolution; uniform float time; #define TAU 6.28318530718 float segment(vec2 p, vec2 a, vec2 b) { vec2 ab = b - a; vec2 ap = p - a; float k = clamp(dot(ap, ab) / dot(ab, ab), 0.0, 1.0); return smoothstep(0.0, 5.0 / resolution.y, length(ap - k * ab) - 0.001); } float shape(vec2 p, float angle) { float d = 100.0; vec2 a = vec2(1.0, 0.0), b; vec2 rot = vec2(cos(angle), sin(angle)); for (int i = 0; i < 6; ++i) { b = a; for (int j = 0; j < 18; ++j) { b = vec2(b.x*rot.x - b.y*rot.y, b.x*rot.y + b.y*rot.x); d = min(d, segment(p, a, b)); } a = vec2(a.x*rot.x - a.y*rot.y, a.x*rot.y + a.y*rot.x); } return d; } 83
void main(void) { // uv: [0..1] для виньетки vec2 uv = gl_FragCoord.xy / resolution.xy; // cc: центрированные координаты, Y вверх (как в Shadertoy) vec2 cc = (-resolution.xy + 2.0 * gl_FragCoord.xy) / resolution.y; // В py5 ось Y экрана направлена вниз, поэтому инвертируем Y cc.y = -cc.y; float col = shape(abs(cc), cos(0.01 * (time + 22.0)) * TAU); col *= 0.5 + 1.5 * pow(uv.x * uv.y * (1.0 - uv.x) * (1.0 - uv.y), 0.3); gl_FragColor = vec4(vec3(pow(col, 0.45)), 1.0); } К нему прилагается код на Питоне: #!/usr/bin/env python3 """ Пробел — пауза/продолжение анимации R — перезапустить время с нуля S — сохранить скриншот """ import os os.environ['JAVA_HOME'] = r'C:\Program Files\Java\jdk-26.0.1' import py5 from pathlib import Path WIN_W = 780 WIN_H = 780 shader t paused = None = 0.0 = False def setup(): global shader py5.size(WIN_W, WIN_H, py5.P2D) py5.frame_rate(60) py5.window_title("Polygons & Patterns") py5.rect_mode(py5.CORNER) 84
path = Path(py5.sketch_path()) / "shaders" / "shadertoy.glsl" shader = py5.load_shader(str(path)) def draw(): global t if not paused: # секунды, как iTime на Shadertoy t += 1.0 / 60.0 shader.set("resolution", float(WIN_W), float(WIN_H)) shader.set("time", t) py5.shader(shader) py5.rect(0, 0, WIN_W, WIN_H) py5.reset_shader() # Подпись py5.fill(255, 255, 255, 140) py5.no_stroke() py5.text_size(12) py5.text_align(py5.LEFT, py5.TOP) status = "⏸ пауза" if paused else f"t = {t:.1f} с" py5.text(status, 8, 8) def key_pressed(): global paused, t k = py5.key if k == ' ': paused = not paused elif k in ('r', 'R', 'к', 'К'): t = 0.0 elif k in ('s', 'S', 'ы', 'Ы'): py5.save_frame("shadertoy.png") py5.run_sketch() Испытания конвертированного шейдера и питоньего кода прошли успешно! Картинка динамично изменяется со временем. Анимацию в книге не покажешь, поэтому любуйтесь работой шейдера в работающей программе. 85
86
Проект Rosette Этот шейдер с трудом поддаётся конвертированию! Вы можете найти его на сайте: https://www.shadertoy.com/view/73SSRw // Circle Rosette // By Noztol #define NUM_CIRCLES 36.0 #define CYCLE_TIME 8.0 #define DRAW_SPEED 1.0 // Total number of circles // Seconds before the animation resets // Seconds it takes to draw one circle void mainImage( out vec4 fragColor, in vec2 fragCoord ) { vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; float t = mod(iTime, CYCLE_TIME); vec3 finalColor = vec3(0.0); float pathRadius = 0.22; float circleRadius = 0.35; for(float i = 0.0; i < NUM_CIRCLES; i++) { float startT = i * (CYCLE_TIME * 0.5 / NUM_CIRCLES); if(t > startT) { float localTime = t - startT; float angle = i * (6.28318 / NUM_CIRCLES); vec2 center = vec2(cos(angle), sin(angle)) * pathRadius; vec2 p = uv - center; float d = length(p); float float float float pixelAngle = atan(p.y, p.x); normAngle = (pixelAngle + 3.14159) / 6.28318; drawProgress = clamp(localTime / DRAW_SPEED, 0.0, 1.0); drawMask = step(normAngle, drawProgress); float outline = smoothstep(0.004, 0.002, abs(d - circleRadius)); float hueOffset = i / NUM_CIRCLES; vec3 circleColor = 0.5 + 0.5 * cos(6.28318 * (hueOffset + vec3(0.0, 0.33, 0.67))); float fadeOut = smoothstep(CYCLE_TIME, CYCLE_TIME - 1.0, t); finalColor += outline * drawMask * circleColor * fadeOut; 87
} } fragColor = vec4(finalColor, 1.0); } Применяем к нему инструмент: https://shadertoyporter.github.io/ 88
Затем прикладываем умелые ручки – и только тогда получаем рабочий шейдер: uniform vec2 resolution; uniform float time; #define NUM_CIRCLES 36.0 #define CYCLE_TIME 8.0 #define DRAW_SPEED 1.0 void main() { vec2 uv = (gl_FragCoord.xy - 0.5 * resolution.xy) / resolution.y; float t = mod(time, CYCLE_TIME); vec3 finalColor = vec3(0.0); float pathRadius = 0.14;// 0.22; float circleRadius = 0.35; for (float i = 0.0; i < NUM_CIRCLES; i++) { float startT = i * (CYCLE_TIME * 0.5 / NUM_CIRCLES); if (t > startT) { float localTime = t - startT; float angle = i * (6.28318 / NUM_CIRCLES); vec2 circleCenter = vec2(cos(angle), sin(angle)) * pathRadius; vec2 p = uv - circleCenter; float d = length(p); 89
float pixelAngle = atan(p.y, p.x); float normAngle = (pixelAngle + 3.14159) / 6.28318; float drawProgress = clamp(localTime / DRAW_SPEED, 0.0, 1.0); float drawMask = step(normAngle, drawProgress); float outline = smoothstep(0.004, 0.002, abs(d - circleRadius)); float hueOffset = i / NUM_CIRCLES; vec3 circleColor = 0.5 + 0.5 * cos(6.28318 * (hueOffset + vec3(0.0, 0.33, 0.67))); float fadeOut = smoothstep(CYCLE_TIME, CYCLE_TIME - 1.0, t); finalColor += outline * drawMask * circleColor * fadeOut; } } gl_FragColor = vec4(finalColor, 1.0); } Код на Питоне почти не изменился: #!/usr/bin/env python3 """ Пробел — пауза/продолжение анимации R — перезапустить время с нуля S — сохранить скриншот """ import os os.environ['JAVA_HOME'] = r'C:\Program Files\Java\jdk-26.0.1' import py5 from pathlib import Path WIN_W = 780 WIN_H = 780 shader t paused = None = 0.0 = False def setup(): global shader py5.size(WIN_W, WIN_H, py5.P2D) py5.frame_rate(60) 90
py5.window_title("Rosette") py5.rect_mode(py5.CORNER) path = Path(py5.sketch_path()) / "shaders" / "rosette.glsl" shader = py5.load_shader(str(path)) def draw(): global t if not paused: # секунды, как iTime на Shadertoy t += 1.0 / 60.0 shader.set("resolution", float(WIN_W), float(WIN_H)) shader.set("time", t) py5.shader(shader) py5.rect(0, 0, WIN_W, WIN_H) py5.reset_shader() # Подпись py5.fill(255, 255, 255, 140) py5.no_stroke() py5.text_size(12) py5.text_align(py5.LEFT, py5.TOP) status = "⏸ пауза" if paused else f"t = {t:.1f} с" py5.text(status, 8, 8) def key_pressed(): global paused, t k = py5.key if k == ' ': paused = not paused elif k in ('r', 'R', 'к', 'К'): t = 0.0 elif k in ('s', 'S', 'ы', 'Ы'): py5.save_frame("shadertoy.png") py5.run_sketch() Но не зря мы старались – шейдер работает великолепно! 91
92
Проект Spiral Rosette Действуем и воздействуем на оригинальный шейдер тем же макаром! Вы можете найти этот шейдер на сайте: https://www.shadertoy.com/view/73BXRm // Cornucopia // By noztol #define NUM_CIRCLES 120.0 #define CYCLE_TIME 12.0 #define DRAW_SPEED 1.0 void mainImage( out vec4 fragColor, in vec2 fragCoord ) { vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; float t = mod(iTime, CYCLE_TIME); vec3 finalColor = vec3(0.0); for(float i = 0.0; i < NUM_CIRCLES; i++) { float startT = i * (CYCLE_TIME * 0.7 / NUM_CIRCLES); if(t > startT) { float localTime = t - startT; float angle = i * 0.13; float b = 0.306349; float pathRadius = 0.004 * exp(b * angle); float circleRadius = 0.005 * exp(b * angle); vec2 center = vec2(cos(angle), sin(angle)) * pathRadius; vec2 p = uv - center; float d = length(p); float pixelAngle = atan(p.y, p.x); float normAngle = (pixelAngle + 3.14159) / 6.28318; float drawProgress = clamp(localTime / DRAW_SPEED, 0.0, 1.0); float drawMask = step(normAngle, drawProgress); float outline = smoothstep( 1.5/iResolution.y, 0., abs(d - circleRadius) - .001 ); float hueOffset = i / NUM_CIRCLES; vec3 circleColor = 0.5 + 0.5 * cos(6.28318 * (hueOffset + vec3(0.0, 0.33, 0.67))); float fadeOut = smoothstep(CYCLE_TIME, CYCLE_TIME - 1.0, t); finalColor += outline * drawMask * circleColor * fadeOut; } } fragColor = vec4(finalColor, 1.0); } 93
Переделка: uniform vec2 resolution; uniform float time; #define NUM_CIRCLES 120.0 #define CYCLE_TIME 12.0 #define DRAW_SPEED 1.0 void main() { vec2 uv = (gl_FragCoord.xy - 0.5 * resolution.xy) / resolution.y; float t = mod(time, CYCLE_TIME); vec3 finalColor = vec3(0.0); for (float i = 0.0; i < NUM_CIRCLES; i++) { float startT = i * (CYCLE_TIME * 0.7 / NUM_CIRCLES); if (t > startT) { float localTime = t - startT; float angle = i * 0.13; float b = 0.306349; float pathRadius = 0.003 * exp(b * angle); //float pathRadius = 0.004 * exp(b * angle); //float circleRadius = 0.005 * exp(b * angle); float circleRadius = 0.0035 * exp(b * angle); vec2 circleCenter = vec2(cos(angle) + 0.75, sin(angle) -0.4) * 94
pathRadius; vec2 float float float float float float p = d = pixelAngle normAngle drawProgress drawMask outline uv - circleCenter; length(p); = atan(p.y, p.x); = (pixelAngle + 3.14159) / 6.28318; = clamp(localTime / DRAW_SPEED, 0.0, 1.0); = step(normAngle, drawProgress); = smoothstep(1.5 / resolution.y, 0.0, abs(d - circleRadius) - 0.001); float hueOffset = i / NUM_CIRCLES; vec3 circleColor = 0.5 + 0.5 * cos(6.28318 * (hueOffset + vec3(0.0, 0.33, 0.67))); float fadeOut = smoothstep(CYCLE_TIME, CYCLE_TIME - 1.0, t); finalColor += outline * drawMask * circleColor * fadeOut; } } gl_FragColor = vec4(finalColor, 1.0); } Код на Питоне: #!/usr/bin/env python3 """ Пробел — пауза/продолжение анимации R — перезапустить время с нуля S — сохранить скриншот """ import os os.environ['JAVA_HOME'] = r'C:\Program Files\Java\jdk-26.0.1' import py5 from pathlib import Path WIN_W = 780 WIN_H = 780 shader = None t = 0.0 paused = False def setup(): global shader py5.size(WIN_W, WIN_H, py5.P2D) 95
py5.frame_rate(60) py5.window_title("Spiral Rosette") py5.rect_mode(py5.CORNER) path = Path(py5.sketch_path()) / "shaders" / "spiral_rosette.glsl" shader = py5.load_shader(str(path)) def draw(): global t if not paused: # секунды, как iTime на Shadertoy t += 1.0 / 60.0 shader.set("resolution", float(WIN_W), float(WIN_H)) shader.set("time", t) py5.shader(shader) py5.rect(0, 0, WIN_W, WIN_H) py5.reset_shader() # Подпись py5.fill(255, 255, 255, 140) py5.no_stroke() py5.text_size(12) py5.text_align(py5.LEFT, py5.TOP) status = "⏸ пауза" if paused else f"t = {t:.1f} с" py5.text(status, 8, 8) def key_pressed(): global paused, t k = py5.key if k == ' ': paused = not paused elif k in ('r', 'R', 'к', 'К'): t = 0.0 elif k in ('s', 'S', 'ы', 'Ы'): py5.save_frame("shadertoy.png") py5.run_sketch() Благодаря нашему старательному усердию картинка получилась на загляденье! 96
97
Проект Crystal Шейдер Crystal Distortion Flow здесь: https://www.shadertoy.com/view/wfjcWt Оригинальный код: void mainImage(out vec4 fragColor, in vec2 fragCoord) { vec2 uv = fragCoord / iResolution.xy; uv.x *= iResolution.x / iResolution.y; // Масштабирование и центрирование vec2 p = uv * 2.0 - 1.0; float t = iTime * 0.4; // Искажение координат float distortX = sin(p.y * 5.0 + t * 1.2) * 0.2; float distortY = cos(p.x * 4.0 - t * 0.9) * 0.3; p += vec2(distortX, distortY); // Резкий геометрический узор float crystal = fract(p.x * 3.0 + p.y * 2.0 + t); crystal = step(0.5, crystal); // Градиент с резкими переходами float gradient = fract(p.x + p.y + t * 0.5); gradient = floor(gradient * 5.0) / 5.0; gradient = clamp(gradient, 0.0, 1.0); // Цветовая палитра vec3 col = vec3(0.0); col.r = gradient * (0.6 + 0.4 * sin(t * 0.8)); col.g = gradient * (0.5 + 0.3 * cos(t * 1.0)); col.b = gradient * (0.7 + 0.2 * sin(t * 1.2)); // Усиление искажения col *= crystal * 0.8 + 0.2; fragColor = vec4(col, 1.0); 98
} Адаптация: uniform vec2 resolution; uniform float time; void main() { vec2 uv = gl_FragCoord.xy / resolution.xy; uv.x *= resolution.x / resolution.y; vec2 p = uv * 2.0 - 1.0; float t = time * 0.4; float distortX = sin(p.y * 5.0 + t * 1.2) * 0.2; float distortY = cos(p.x * 4.0 - t * 0.9) * 0.3; p += vec2(distortX, distortY); float crystal = fract(p.x * 3.0 + p.y * 2.0 + t); crystal = step(0.5, crystal); float gradient = fract(p.x + p.y + t * 0.5); 99
gradient = floor(gradient * 5.0) / 5.0; gradient = clamp(gradient, 0.0, 1.0); vec3 col = vec3(0.0); col.r = gradient * (0.6 + 0.4 * sin(t * 0.8)); col.g = gradient * (0.5 + 0.3 * cos(t * 1.0)); col.b = gradient * (0.7 + 0.2 * sin(t * 1.2)); col *= crystal * 0.8 + 0.2; gl_FragColor = vec4(col, 1.0); } Код на Питоне: #!/usr/bin/env python3 """ Пробел — пауза/продолжение анимации R — перезапустить время с нуля S — сохранить скриншот """ import os os.environ['JAVA_HOME'] = r'C:\Program Files\Java\jdk-26.0.1' import py5 from pathlib import Path WIN_W = 780 WIN_H = 780 shader t paused = None = 0.0 = False def setup(): global shader py5.size(WIN_W, WIN_H, py5.P2D) py5.frame_rate(60) py5.window_title("Crystal") py5.rect_mode(py5.CORNER) 100
path = Path(py5.sketch_path()) / "shaders" / "crystal.glsl" shader = py5.load_shader(str(path)) def draw(): global t if not paused: # секунды, как iTime на Shadertoy t += 1.0 / 60.0 shader.set("resolution", float(WIN_W), float(WIN_H)) shader.set("time", t) py5.shader(shader) py5.rect(0, 0, WIN_W, WIN_H) py5.reset_shader() # Подпись py5.fill(255, 255, 255, 140) py5.no_stroke() py5.text_size(12) py5.text_align(py5.LEFT, py5.TOP) status = "⏸ пауза" if paused else f"t = {t:.1f} с" py5.text(status, 8, 8) def key_pressed(): global paused, t k = py5.key if k == ' ': paused = not paused elif k in ('r', 'R', 'к', 'К'): t = 0.0 elif k in ('s', 'S', 'ы', 'Ы'): py5.save_frame("shadertoy.png") py5.run_sketch() Кристаллические искажения впечатляют! 101
102
Проект Glossy Gradients Шейдер Glossy Gradients здесь: https://www.shadertoy.com/view/lX2GDR Оригинальный код: void mainImage(out vec4 fragColor, vec2 fragCoord) { float mr = min(iResolution.x, iResolution.y); vec2 uv = (fragCoord * 2.0 - iResolution.xy) / mr; } float d = -iTime * 0.5; float a = 0.0; for (float i = 0.0; i < 8.0; ++i) { a += cos(i - d - a * uv.x); d += sin(uv.y * i + a); } d += iTime * 0.5; vec3 col = vec3(cos(uv * vec2(d, a)) * 0.6 + 0.4, cos(a + d) * 0.5 + 0.5); col = cos(col * cos(vec3(d, a, 2.5)) * 0.5 + 0.5); fragColor = vec4(col, 1); Этот шейдер не столь мрачный, как предыдущий! Поэтому переделываем его с радостным удовольствием: uniform vec2 resolution; uniform float time; void main(){ float mr = min(resolution.x, resolution.y); vec2 uv = (gl_FragCoord.xy * 2.0 - resolution.xy) / mr; float d = -time * 0.5; float a = 0.0; for (float i = 0.0; i < 8.0; ++i) { a += cos(i - d - a * uv.x); d += sin(uv.y * i + a); 103
} d += time * 0.5; vec3 col = vec3(cos(uv * vec2(d, a)) * 0.6 + 0.4, cos(a + d) * 0.5 + 0.5); col = cos(col * cos(vec3(d, a, 2.5)) * 0.5 + 0.5); gl_FragColor = vec4(col, 1); } Питонячий код прост и понятен: #!/usr/bin/env python3 """ Пробел — пауза/продолжение анимации R — перезапустить время с нуля S — сохранить скриншот """ import os os.environ['JAVA_HOME'] = r'C:\Program Files\Java\jdk-26.0.1' import py5 from pathlib import Path WIN_W = 780 WIN_H = 780 shader t paused = None = 0.0 = False def setup(): global shader py5.size(WIN_W, WIN_H, py5.P2D) py5.frame_rate(60) py5.window_title("Glossy Gradients") py5.rect_mode(py5.CORNER) path = Path(py5.sketch_path()) / "shaders" / "GlossyGradients.glsl" shader = py5.load_shader(str(path)) def draw(): 104
global t if not paused: # секунды, как iTime на Shadertoy t += 1.0 / 60.0 shader.set("resolution", float(WIN_W), float(WIN_H)) shader.set("time", t) py5.shader(shader) py5.rect(0, 0, WIN_W, WIN_H) py5.reset_shader() # Подпись py5.fill(255, 255, 255, 140) py5.no_stroke() py5.text_size(12) py5.text_align(py5.LEFT, py5.TOP) status = "⏸ пауза" if paused else f"t = {t:.1f} с" py5.text(status, 8, 8) def key_pressed(): global paused, t k = py5.key if k == ' ': paused = not paused elif k in ('r', 'R', 'к', 'К'): t = 0.0 elif k in ('s', 'S', 'ы', 'Ы'): py5.save_frame("shadertoy.png") py5.run_sketch() Шейдер выписывает радостные динамические градиенты! 105
106
Проект Purple-blue liquid gradient Продолжаем восхищаться градиентами! Шейдер Purple-blue liquid gradient здесь: https://www.shadertoy.com/view/dsXyzf Оригинальный код: #define PI 3.14159265359 // Hash without Sine // Creative Commons Attribution-ShareAlike 4.0 International Public License // Created by David Hoskins. // // // // // // https://www.shadertoy.com/view/4djSRW Trying to find a Hash function that is the same on ALL systens and doesn't rely on trigonometry functions that change accuracy depending on GPU. New one on the left, sine function on the right. It appears to be the same speed, but I suppose that depends. // * Note. It still goes wrong eventually! // * Try full-screen paused to see details. #define barWidth 0.002 #define slideValY 0.5 //(cos(iTime)+1.)/2. #define slideValX 0.5 //(sin(iTime/2.)+1.)/2. #define ITERATIONS 4 // *** Change these to suit your range of random numbers.. // *** Use this for integer stepped ranges, ie Value-Noise/Perlin noise functions. #define HASHSCALE1 .1031 #define HASHSCALE3 vec3(.1031, .1030, .0973) #define HASHSCALE4 vec4(.1031, .1030, .0973, .1099) // For smaller input rangers like audio tick or 0-1 UVs use these... //#define HASHSCALE1 443.8975 //#define HASHSCALE3 vec3(443.897, 441.423, 437.195) 107
//#define HASHSCALE4 vec3(443.897, 441.423, 437.195, 444.129) //----------------------------------------------------------------------------// 1 out, 1 in... float hash11(float p) { vec3 p3 = fract(vec3(p) * HASHSCALE1); p3 += dot(p3, p3.yzx + 19.19); return fract((p3.x + p3.y) * p3.z); } //----------------------------------------------------------------------------// 1 out, 2 in... float hash12(vec2 p) { vec3 p3 = fract(vec3(p.xyx) * HASHSCALE1); p3 += dot(p3, p3.yzx + 19.19); return fract((p3.x + p3.y) * p3.z); } //----------------------------------------------------------------------------// 1 out, 3 in... float hash13(vec3 p3) { p3 = fract(p3 * HASHSCALE1); p3 += dot(p3, p3.yzx + 19.19); return fract((p3.x + p3.y) * p3.z); } //----------------------------------------------------------------------------// 2 out, 1 in... vec2 hash21(float p) { vec3 p3 = fract(vec3(p) * HASHSCALE3); p3 += dot(p3, p3.yzx + 19.19); return fract((p3.xx+p3.yz)*p3.zy); } //----------------------------------------------------------------------------/// 2 out, 2 in... vec2 hash22(vec2 p) { vec3 p3 = fract(vec3(p.xyx) * HASHSCALE3); p3 += dot(p3, p3.yzx+19.19); return fract((p3.xx+p3.yz)*p3.zy); 108
} //----------------------------------------------------------------------------/// 2 out, 3 in... vec2 hash23(vec3 p3) { p3 = fract(p3 * HASHSCALE3); p3 += dot(p3, p3.yzx+19.19); return fract((p3.xx+p3.yz)*p3.zy); } //----------------------------------------------------------------------------// 3 out, 1 in... vec3 hash31(float p) { vec3 p3 = fract(vec3(p) * HASHSCALE3); p3 += dot(p3, p3.yzx+19.19); return fract((p3.xxy+p3.yzz)*p3.zyx); } //----------------------------------------------------------------------------/// 3 out, 2 in... vec3 hash32(vec2 p) { vec3 p3 = fract(vec3(p.xyx) * HASHSCALE3); p3 += dot(p3, p3.yxz+19.19); return fract((p3.xxy+p3.yzz)*p3.zyx); } //----------------------------------------------------------------------------/// 3 out, 3 in... vec3 hash33(vec3 p3) { p3 = fract(p3 * HASHSCALE3); p3 += dot(p3, p3.yxz+19.19); return fract((p3.xxy + p3.yxx)*p3.zyx); } //----------------------------------------------------------------------------// 4 out, 1 in... vec4 hash41(float p) { vec4 p4 = fract(vec4(p) * HASHSCALE4); p4 += dot(p4, p4.wzxy+19.19); return fract((p4.xxyz+p4.yzzw)*p4.zywx); 109
} //----------------------------------------------------------------------------// 4 out, 2 in... vec4 hash42(vec2 p) { vec4 p4 = fract(vec4(p.xyxy) * HASHSCALE4); p4 += dot(p4, p4.wzxy+19.19); return fract((p4.xxyz+p4.yzzw)*p4.zywx); } //----------------------------------------------------------------------------// 4 out, 3 in... vec4 hash43(vec3 p) { vec4 p4 = fract(vec4(p.xyzx) * HASHSCALE4); p4 += dot(p4, p4.wzxy+19.19); return fract((p4.xxyz+p4.yzzw)*p4.zywx); } //----------------------------------------------------------------------------// 4 out, 4 in... vec4 hash44(vec4 p4) { p4 = fract(p4 * HASHSCALE4); p4 += dot(p4, p4.wzxy+19.19); return fract((p4.xxyz+p4.yzzw)*p4.zywx); } Как видите, работы нам предстоит много, но это нас не пугает: uniform vec2 resolution; uniform float time; const const const const const const const const const float float int int int float float float float arrow_density = 4.5; arrow_length = 0.45; iterationTime1 = 20; iterationTime2 = 20; vector_field_mode = 0; scale = 6.0; velocity_x = 0.1; velocity_y = 0.2; mode_2_speed = 2.5; 110
const const const const float float bool vec3 mode_1_detail mode_1_twist isArrow luma = = = = 200.0; 50.0; true; vec3(0.2126, 0.7152, 0.0722); float f(in vec2 p) { return sin(p.x + sin(p.y + time * velocity_x)) * sin(p.y * p.x * 0.1 + time * velocity_y); } struct Field { vec2 vel; vec2 pos; }; Field field(in vec2 p, in int mode) { Field fld; if (mode == 0) { vec2 ep = vec2(0.05, 0.0); vec2 rz = vec2(0.0); for (int i = 0; i < iterationTime1; i++) { float t0 = f(p); float t1 = f(p + ep.xy); float t2 = f(p + ep.yx); vec2 g = vec2(t1 - t0, t2 - t0) / ep.xx; vec2 tv = vec2(-g.y, g.x); p += (mode_1_twist * 0.01) * tv + g * (1.0 / mode_1_detail); p.x += sin(time * mode_2_speed / 10.0) / 10.0; p.y += cos(time * mode_2_speed / 10.0) / 10.0; rz = g; } fld.vel = rz; return fld; } if (mode == 1) { vec2 ep = vec2(0.05, 0.0); vec2 rz = vec2(0.0); for (int i = 0; i < iterationTime1; i++) { float t0 = f(p); float t1 = f(p + ep.xy); float t2 = f(p + ep.yx); vec2 g = vec2(t1 - t0, t2 - t0) / ep.xx; vec2 tv = vec2(-g.y, g.x); 111
p += (mode_1_twist * 0.01) * tv + g * (1.0 / mode_1_detail); p.x += sin(time * mode_2_speed / 10.0) / 10.0; p.y += cos(time * mode_2_speed / 10.0) / 10.0; rz = g; } fld.vel = rz; for (int i = 1; i < iterationTime2; i++) { p.x += 0.3 / float(i) * sin(float(i) * 3.0 * p.y + time * mode_2_speed) + 0.5; p.y += 0.3 / float(i) * cos(float(i) * 3.0 * p.x + time * mode_2_speed) + 0.5; } fld.pos = p; return fld; } return fld; } float segm(in vec2 p, in vec2 a, in vec2 b) { vec2 pa = p - a; vec2 ba = b - a; float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); return length(pa - ba * h) * 20.0 * arrow_density; } float fieldviz(in vec2 p, in int mode) { vec2 ip = floor(p * arrow_density) / arrow_density + 0.5 / arrow_density; vec2 t = field(ip, mode).vel; float m = min(0.1, pow(length(t), 0.5) * (arrow_length / arrow_density)); vec2 b = normalize(t) * m; float rz = segm(p, ip, ip + b); vec2 prp = vec2(-b.y, b.x); rz = min(rz, segm(p, ip + b, ip + b * 0.65 + prp * 0.3)); return clamp(min(rz, segm(p, ip + b, ip + b * 0.65 - prp * 0.3)), 0.0, 1.0); } vec3 getRGB(in Field fld, in int mode) { if (mode == 0) { vec2 p = fld.vel; return vec3(p * 0.5 + 0.5, 1.5); } if (mode == 1) { vec2 p = fld.pos; float r = cos(p.x + p.y + 1.0) * 0.5 + 0.5; float g = sin(p.x + p.y + 1.0) * 0.5 + 0.5; 112
float b = (sin(p.x + p.y) + cos(p.x + p.y)) * 0.3 + 0.5; return vec3(r, g, b); } return vec3(0.0); } void main() { vec2 p = gl_FragCoord.xy / resolution.xy - 0.5; p.x *= resolution.x / resolution.y; p *= scale; int vector_mode = 0; Field fld = field(p, vector_mode); vec3 col = getRGB(fld, vector_mode) * 0.85; gl_FragColor = vec4(col, 1.0); } В награду за старание код на Питоне достаётся нам без труда! #!/usr/bin/env python3 """ Пробел — пауза/продолжение анимации R — перезапустить время с нуля S — сохранить скриншот """ import os os.environ['JAVA_HOME'] = r'C:\Program Files\Java\jdk-26.0.1' import py5 from pathlib import Path WIN_W = 780 WIN_H = 780 shader t paused = None = 0.0 = False def setup(): global shader py5.size(WIN_W, WIN_H, py5.P2D) py5.frame_rate(60) 113
py5.window_title("Purple-blue liquid gradient") py5.rect_mode(py5.CORNER) path = Path(py5.sketch_path()) / "shaders" / "purple-blue_liquid_gradient.glsl" shader = py5.load_shader(str(path)) def draw(): global t if not paused: # секунды, как iTime на Shadertoy t += 1.0 / 60.0 shader.set("resolution", float(WIN_W), float(WIN_H)) shader.set("time", t) py5.shader(shader) py5.rect(0, 0, WIN_W, WIN_H) py5.reset_shader() # Подпись py5.fill(255, 255, 255, 140) py5.no_stroke() py5.text_size(12) py5.text_align(py5.LEFT, py5.TOP) status = "⏸ пауза" if paused else f"t = {t:.1f} с" py5.text(status, 8, 8) def key_pressed(): global paused, t k = py5.key if k == ' ': paused = not paused elif k in ('r', 'R', 'к', 'К'): t = 0.0 elif k in ('s', 'S', 'ы', 'Ы'): py5.save_frame("shadertoy.png") py5.run_sketch() А жидкий градиент получился просто отлично! 114
115
Проект Contoured Layers Контурный градиент ещё краше! Шейдер Contoured Layers здесь: https://www.shadertoy.com/view/3lj3zt Оригинальный код: /* Contoured Layers ---------------Constructing some concise contoured layers, then applying various edge and shading effects to produce some faux depth. Technically, that's what is happening here, but this example was mainly put together as a means to demonstrate various layering effects, like strokes, highlights, shadows, etc. No 3D was harmed during the making of this example. :) I love those contoured noise-based paper layer images that various graphic artists from places like Adobe distribute with their applications. Most consist of some antialiased noise layers rendered in a flat tone with drop shadows for each. The fancier ones sometimes include highlighted edges, etc, which is what I've put together here. None of it is difficult to produce, provided you're happy with concept of smoothing layers at a particular threshold with respect to the field derivative. I put in a few diferent aesthetic options to try, just to show how much something like a simple palette change, drop shadow, etc, can effect the overall feel. I've tuned most settings on by default, but turning them off can give a cleaner look. 116
Turning everything off, except the shadows will give the minimal contoured images you see around, which I happen to like also. Anyway, feel free to play around with the defines below. At some stage, I might render some icons and allow the various options to be manipulated via the mouse, but for now, this will suffice. I also have a few raymarched 3D versions that I'll put up at a later date. */ // Dropping down a blurry dark layer to give a fake ambient occlusion effect. // It's subtle, but gives things a bit more depth. However, turning it off gives // a crisper look. I guess it depends what you're after. #define FAKE_AO // Contour strokes are great for that hand drawn look, or just to give some definition // to geometry. This one is dark, but it can be any color. #define STROKE // Highlights, to give the impression that light is hitting the surface. #define HILIGHT // Shadows: There aren't too many times when I wouldn't want shadows, but I can think // of a few. If expense if a problem, you can fake it with a thicker AO layer, but it's // not quite the same. #define SHADOW // Including the metallic texture: I overuse this particular texture, but it's the only // one on Shadertoy with a fine enough grade on it. I'm hoping more subtle textures // will get added at some stage. :) #define TEXTURED // Running a cheap hatch-like algorithm over the layers for a bit of extra texture. // Come to think of it, it's probably more of a stipple pattern... Either way, it's 117
// just a little extra texture. #define HATCH // Very subtle paper grain. It's pretty simple... and I think it's based on one of // Flockaroo's snippets. If you're interested in post processing effects, his examples // are definitely worth looking at. //#define PAPER_GRAIN // Palette: It's amazing how something as simple as color choice can effect the feel // of an image. // Settings: Greyscale: 0, Red: 1, Blue: 2, Earth: 3, Pink and grey: 4, // Grey and pink: 5, Green and white: 6. #define PALETTE 0 // Standard 2D rotation formula. mat2 rot2(in float a){ float c = cos(a), s = sin(a); return mat2(c, -s, s, c); } // vec3 to float hash. float hash21( vec2 p ){ return fract(sin(dot(p, vec2(41, 289)))*45758.5453); //p.x = fract(sin(dot(p, vec2(1, 113)))*45758.5453); //return sin(p.x*6.2831853 + iTime); } // vec2 to vec2 hash. vec2 hash22(vec2 p) { // Faster, but doesn't disperse things quite as nicely. However, when framerate // is an issue, and it often is, this is a good one to use. Basically, it's a tweaked // amalgamation I put together, based on a couple of other random algorithms I've // seen around... so use it with caution, because I make a tonne of mistakes. :) float n = sin(dot(p, vec2(1, 113))); //return fract(vec2(262144, 32768)*n)*2. - 1.; // Animated. 118
p = fract(vec2(262144, 32768)*n); // Note the ".45," insted of ".5" that you'd expect to see. When edging, it can open // up the cells ever so slightly for a more even spread. In fact, lower numbers work // even better, but then the random movement would become too restricted. Zero would // give you square cells. return sin(p*6.2831853 + iTime); } // Cheap and nasty 2D smooth noise function with inbuilt hash function -- based on IQ's // original. Very trimmed down. In fact, I probably went a little overboard. I think it // might also degrade with large time values, but that's not an issue here. float n2D(vec2 p) { vec2 i = floor(p); p -= i; p *= p*(3. - p*2.); return dot(mat2(fract(sin(vec4(0, 1, 113, 114) + dot(i, vec2(1, 113)))*43758.5453))* vec2(1. - p.y, p.y), vec2(1. - p.x, p.x) ); } // Based on IQ's gradient noise formula. float n2D3G( in vec2 p ){ vec2 i = floor(p); p -= i; vec4 v; v.x = dot(hash22(i), v.y = dot(hash22(i + v.z = dot(hash22(i + v.w = dot(hash22(i + p); vec2(1, 0)), p - vec2(1, 0)); vec2(0, 1)), p - vec2(0, 1)); 1.), p - 1.); #if 1 // Quintic interpolation. p = p*p*p*(p*(p*6. - 15.) + 10.); #else // Cubic interpolation. p = p*p*(3. - 2.*p); #endif 119
return mix(mix(v.x, v.y, p.x), mix(v.z, v.w, p.x), p.y); //return v.x + p.x*(v.y - v.x) + p.y*(v.z - v.x) + p.x*p.y*(v.x - v.y - v.z + v.w); } // The map function. Just two layers of gradient noise. Way more interesting // functions are possible, but we're keeping things simple. float map(vec3 p, float i){ /* // Video version: Load one of the videos into iChannel1, preferably // with the filter setting to mipmap. p.xy *= vec2(iResolution.y/iResolution.x, 1); vec3 tx = texture(iChannel1, p.xy + .5, 1.).xyz; tx *= tx; return .15 - dot(tx, vec3(.299, .587, .114)) + i/10.*1. - .15; */ return n2D3G(p.xy*3.)*.66 + n2D3G(p.xy*6.)*.34 + i/10.*1. - .15; } // 2D derivative function. vec3 getNormal(in vec3 p, float m, float i) { vec2 e = vec2(.001, 0); // Four extra samples. Slightly better, but not really needed here. //return (vec3(map(p + e.xyy, i) - map(p - e.xyy, i), map(p + e.yxy, i) map(p - e.yxy, i), 0.))/e.x*.7071; // Three samples, but only two extra sample calculations. return (vec3(m - map(p - e.xyy, i), m - map(p - e.yxy, i), 0.))/e.x*1.4142; } // The map layer and its derivative. To produce constant width layer edges, the derivative // is necessary, so the distance field value and the derivative at the point is returned. vec4 mapLayer(in vec3 p, float i){ vec4 d; d.x = map(p, i); // Distance field value. 120
d.yzw = getNormal(p, d.x, i); // Derivative. return d; } // Layer color. Based on the shade, layer number and smoothing factor. vec3 getCol(vec2 p, float sh, float fi, float sf){ // Color. vec3 col; #if PALETTE == 0 // Light attenuation palette. col = vec3(1)*(1. - .75/(1. + sh*sh*2.)); //col = vec3(sh*sh*.65 + .22); //col = vec3(sh*.5 + .2); // Etc. #elif PALETTE == 1 col = pow(min(vec3(1.5, 1, 1)*(sh*.35 + .6), 1.), vec3(1, 3, 16)); if(fi==0.) col = vec3(.35, .05, .3); col = mix(col.xzy, col, sh*.5 + .5); #elif PALETTE == 2 col = pow(min(vec3(1.5, 1, 1)*(sh*.35 + .6), 1.), vec3(1, 3, 16)); if(fi==0.) col = vec3(.6, .2, .07); col = mix(col.xzy, col, sh*.5 + .5).zyx; #elif PALETTE == 3 if(fi==0.) col = vec3(.25, .52, .75); if(fi==1.) col = vec3(.8, .8, .6); if(fi==2.) col = vec3(.75, .6, .5); if(fi==3.) col = vec3(.6, .58, .5); if(fi==4.) col = vec3(.5, .72, .4); if(fi==5.) col = vec3(.65, .85, .5); else col = min(vec3(.65, .85, .5)*1.25, 1.); #elif PALETTE == 4 if(mod(fi, 2.)<.5) col = vec3(.25, .15, .2); else col = vec3(1, .15, .4)*.8; #elif PALETTE == 5 if(mod(fi, 2.)<.5) col = vec3(1, .15, .4)*.8; else col = vec3(.25, .15, .2); #else if(mod(fi, 2.)>.5) col = vec3(.6); else col = vec3(.6, 1, .15)*.6; #endif 121
#ifdef TEXTURED vec3 tx = texture(iChannel0, p + hash21(vec2(sh, fi))).xyz; tx *= tx; col = min(col*tx*3., 1.); #endif return col; } // A hatch-like algorithm, or a stipple... or some kind of textured pattern. float doHatch(vec2 p, float res){ // If the "HATCH" define is on, produce the pattern. #ifdef HATCH // The pattern is physically based, so needs to factor in screen resolution. p *= res/16.; // Random looking diagonal hatch lines. float hatch = clamp(sin((p.x - p.y)*3.14159*200.)*2. + .5, 0., 1.); // Diagonal lines. // Slight randomization of the diagonal lines, but the trick is to do it with // tiny squares instead of pixels. float hRnd = hash21(floor(p*6.) + .73); if(hRnd>.66) hatch = hRnd; #ifdef TEXTURED hatch = hatch*.75 + .5; // Stronger hatching for the textured version. #else hatch = hatch*.5 + .75; #endif return hatch; // If the hatch define is off, cancel it out. #else return 1.; #endif } 122
void mainImage(out vec4 fragColor, in vec2 fragCoord){ // Aspect correct screen coordinates. Setting a minumum resolution on the // fullscreen setting in an attempt to keep things relatively crisp. float res = min(iResolution.y, 700.); vec2 uv = (fragCoord - iResolution.xy*.5)/res; // Scaling and translation. vec2 p = uv;// + vec2(1, 0)*iTime; // Resolution based smoothing factor. Later, the contour derivative will // be factored in. float sf = 1./iResolution.y; // Initialize to the first layer color. vec3 col = getCol(p, 0., 0., sf); // Previous layer variable. float pL = 0.; // Random looking diagonal hatch lines. float hatch = doHatch(p, res); // Applying the cross hatch. col *= hatch; // Number of layers. int lNum = 5; float flNum = float(lNum); for(int i = 0; i<lNum; i++){ float fi = float(i); // Repositioning the hatch lines on each layer to reduce the "shower screen effect," // as per Fabrice Neyret's suggestion. :) hatch = doHatch(p + sin(vec2(41, 289)*(fi + 1.)), res); // The map layer value (just some gradient noise), and its derivative. 123
vec4 c = mapLayer(vec3(p, 1.), fi); // Offset noise layer value with derivative. It's offset so as to coincide with // the directional lighting. vec4 cSh = mapLayer(vec3(p - vec2(.03, -.03)*((flNum - fi)/flNum*.5 + .5), 1.), fi); // Shade. float sh = (fi + 1.)/(flNum); // Layer color. vec3 lCol = getCol(p, sh, fi + 1., sf); // Some standard direct lighting to apply to the edge layer. It's set in a // direction to compliment the shadow layer. vec3 ld = normalize(vec3(-1, 1, -.25)); vec3 n = normalize(vec3(0, 0, -1) + c.yzw); float diff = max(dot(ld, n), 0.); #ifdef TEXTURED diff *= 2.5; // Add a bit more light to the edges for the textured version. #else diff *= 2.; #endif // Applying the diffuse lighting to the edge layer. vec3 eCol = lCol*(diff + 1.); // Apply the layers. // Smoothing factor, based on the distance field derivative. float sfL = sf*length(c.yzx)*2.; float sfLSh = sf*length(cSh.yzx)*6.; // Drop shadow. #ifdef SHADOW #ifdef TEXTURED const float shF = .5; #else const float shF = .35; #endif col = mix(col, vec3(0), (1. - smoothstep(0., sfLSh, max(cSh.x, pL)))*shF); #endif // Dark blurred layer. 124
#ifdef FAKE_AO col = mix(col, vec3(0), (1. - smoothstep(0., sfL*3., c.x))*.25); #endif // Dark edge stroke. #ifdef STROKE col = mix(col, vec3(0), (1. - smoothstep(0., sfL, c.x))*.85); #endif // Hilight and color layer. #ifdef HILIGHT col = mix(col, eCol*hatch, (1. - smoothstep(0., sfL, c.x + length(c.yzx)*.003))); col = mix(col, lCol*hatch, (1. - smoothstep(0., sfL, c.x + length(c.yzx)*.006))); #else col = mix(col, lCol*hatch, (1. - smoothstep(0., sfL, c.x + length(c.yzx)*.0025))); #endif // Previous layer, to take away from the shadow. pL = c.x; } // Mixing in a little extra noisy color for the default greyscale textured setting. #ifdef TEXTURED #if PALETTE == 0 col *= mix(vec3(1.8, 1, .7).zyx, vec3(1.8, 1, .7).xzy, n2D(p*2.)); #endif #endif // Paper. #ifdef PAPER_GRAIN vec3 rn3 = vec3(n2D((uv*iResolution.y/1. + 1.7)) - n2D(vec2(uv*iResolution.y/1. + 3.4))); col *= .93 + .07*rn3.xyz + .07*dot(rn3, vec3(.299, .587, .114)); #endif // Subtle vignette. uv = fragCoord/iResolution.xy; col *= pow(16.*uv.x*uv.y*(1. - uv.x)*(1. - uv.y) , .0625); 125
// Colored variation. //col = mix(col*vec3(.3, 0, 1), col, pow(16.*uv.x*uv.y*(1. - uv.x)*(1. uv.y) , .125)); // Rough gamma correction, then output to the screen. fragColor = vec4(sqrt(max(col, 0.)), 1); } Для переделки кода нужны титанические усилия, но мы их приложим! // Текстура iChannel0 заменена на процедурный шум (n2D). // Для использования с реальной текстурой раскомментируйте: // uniform sampler2D texture0; // и передайте её из Питона через shader.set("texture0", pg) uniform vec2 resolution; uniform float time; // ── Препроцессорные флаги ────────────────────────────────────────────────── #define FAKE_AO #define STROKE #define HILIGHT #define SHADOW #define TEXTURED #define HATCH #define PALETTE 0 // ── Вспомогательные функции ──────────────────────────────────────────────── mat2 rot2(in float a) { float c = cos(a), s = sin(a); return mat2(c, -s, s, c); } float hash21(vec2 p) { return fract(sin(dot(p, vec2(41.0, 289.0))) * 45758.5453); } vec2 hash22(vec2 p) { float n = sin(dot(p, vec2(1.0, 113.0))); 126
p = fract(vec2(262144.0, 32768.0) * n); return sin(p * 6.2831853 + time); } float n2D(vec2 p) { vec2 i = floor(p); p -= i; p *= p * (3.0 - p * 2.0); return dot( mat2(fract(sin(vec4(0.0, 1.0, 113.0, 114.0) + dot(i, vec2(1.0, 113.0))) * 43758.5453)) * vec2(1.0 - p.y, p.y), vec2(1.0 - p.x, p.x) ); } float n2D3G(in vec2 p) { vec2 i = floor(p); p -= i; vec4 v; v.x = dot(hash22(i), p); v.y = dot(hash22(i + vec2(1,0)), p - vec2(1,0)); v.z = dot(hash22(i + vec2(0,1)), p - vec2(0,1)); v.w = dot(hash22(i + 1.0), p - 1.0); p = p*p*p*(p*(p*6.0 - 15.0) + 10.0); return mix(mix(v.x, v.y, p.x), mix(v.z, v.w, p.x), p.y); } float map(vec3 p, float i) { return n2D3G(p.xy*3.0)*0.66 + n2D3G(p.xy*6.0)*0.34 + i/10.0 - 0.15; } vec3 getNormal(in vec3 p, float m, float i) { vec2 e = vec2(0.001, 0.0); return (vec3(m - map(p - e.xyy, i), m - map(p - e.yxy, i), 0.0)) / e.x * 1.4142; } vec4 mapLayer(in vec3 p, float i) { vec4 d; d.x = map(p, i); d.yzw = getNormal(p, d.x, i); return d; 127
} vec3 proceduralTexture(vec2 p) { float r = n2D(p * 8.0 + vec2(0.0, 0.0)); float g = n2D(p * 8.0 + vec2(3.3, 1.7)); float b = n2D(p * 8.0 + vec2(6.6, 3.4)); vec3 tx = vec3(r, g, b); return tx * tx; } vec3 getCol(vec2 p, float sh, float fi, float sf) { vec3 col; #if PALETTE == 0 col = vec3(1.0) * (1.0 - 0.75 / (1.0 + sh*sh*2.0)); #elif PALETTE == 1 col = pow(min(vec3(1.5,1,1)*(sh*0.35+0.6), 1.0), vec3(1,3,16)); if (fi == 0.0) col = vec3(0.35, 0.05, 0.3); col = mix(col.xzy, col, sh*0.5+0.5); #elif PALETTE == 2 col = pow(min(vec3(1.5,1,1)*(sh*0.35+0.6), 1.0), vec3(1,3,16)); if (fi == 0.0) col = vec3(0.6, 0.2, 0.07); col = mix(col.xzy, col, sh*0.5+0.5); col = col.zyx; #elif PALETTE == 3 if (fi == 0.0) col = vec3(0.25, 0.52, 0.75); else if (fi == 1.0) col = vec3(0.8, 0.8, 0.6); else if (fi == 2.0) col = vec3(0.75, 0.6, 0.5); else if (fi == 3.0) col = vec3(0.6, 0.58, 0.5); else if (fi == 4.0) col = vec3(0.5, 0.72, 0.4); else if (fi == 5.0) col = vec3(0.65, 0.85, 0.5); else col = min(vec3(0.65, 0.85, 0.5)*1.25, 1.0); #elif PALETTE == 4 if (mod(fi, 2.0) < 0.5) col = vec3(0.25, 0.15, 0.2); else col = vec3(1.0, 0.15, 0.4)*0.8; #elif PALETTE == 5 if (mod(fi, 2.0) < 0.5) col = vec3(1.0, 0.15, 0.4)*0.8; else col = vec3(0.25, 0.15, 0.2); #else if (mod(fi, 2.0) > 0.5) col = vec3(0.6); else col = vec3(0.6, 1.0, 0.15)*0.6; #endif #ifdef TEXTURED vec3 tx = proceduralTexture(p + hash21(vec2(sh, fi))); col = min(col * tx * 3.0, 1.0); #endif 128
return col; } float doHatch(vec2 p, float res) { #ifdef HATCH p *= res / 16.0; float hatch = clamp(sin((p.x - p.y) * 3.14159 * 200.0) * 2.0 + 0.5, 0.0, 1.0); float hRnd = hash21(floor(p * 6.0) + 0.73); if (hRnd > 0.66) hatch = hRnd; #ifdef TEXTURED hatch = hatch * 0.75 + 0.5; #else hatch = hatch * 0.5 + 0.75; #endif return hatch; #else return 1.0; #endif } void main() { float res = min(resolution.y, 700.0); vec2 uv = (gl_FragCoord.xy - resolution.xy * 0.5) / res; vec2 p = uv; float sf = 1.0 / resolution.y; vec3 col = getCol(p, 0.0, 0.0, sf); float pL = 0.0; float hatch = doHatch(p, res); col *= hatch; int lNum = 5; float flNum = float(lNum); vec3 n = vec3(0.0); for (int i = 0; i < lNum; i++) { float fi = float(i); hatch = doHatch(p + sin(vec2(41.0, 289.0) * (fi + 1.0)), res); vec4 c = mapLayer(vec3(p, 1.0), fi); vec4 cSh = mapLayer(vec3(p - vec2(0.03, -0.03) * ((flNum - fi) / flNum * 0.5 + 0.5), 1.0), fi); float sh = (fi + 1.0) / flNum; 129
vec3 lCol = getCol(p, sh, fi + 1.0, sf); vec3 ld = normalize(vec3(-1.0, 1.0, -0.25)); n = normalize(vec3(0.0, 0.0, -1.0) + c.yzw); float diff = max(dot(ld, n), 0.0); #ifdef TEXTURED diff *= 2.5; #else diff *= 2.0; #endif vec3 eCol = lCol * (diff + 1.0); float sfL = sf * length(c.yzx) * 2.0; float sfLSh = sf * length(cSh.yzx) * 6.0; #ifdef SHADOW #ifdef TEXTURED const float shF = 0.5; #else const float shF = 0.35; #endif col = mix(col, vec3(0.0), (1.0 - smoothstep(0.0, sfLSh, max(cSh.x, pL))) * shF); #endif #ifdef FAKE_AO col = mix(col, vec3(0.0), (1.0 - smoothstep(0.0, sfL*3.0, c.x)) * 0.25); #endif #ifdef STROKE col = mix(col, vec3(0.0), (1.0 - smoothstep(0.0, sfL, c.x)) * 0.85); #endif #ifdef HILIGHT col = mix(col, eCol*hatch, 1.0 - smoothstep(0.0, sfL, c.x + length(c.yzx)*0.003)); col = mix(col, lCol*hatch, 1.0 - smoothstep(0.0, sfL, c.x + length(c.yzx)*0.006)); #else col = mix(col, lCol*hatch, 1.0 - smoothstep(0.0, sfL, c.x + length(c.yzx)*0.0025)); #endif pL = c.x; } #ifdef TEXTURED #if PALETTE == 0 130
col *= mix(vec3(1.8, 1.0, 0.7).zyx, vec3(1.8, 1.0, 0.7).xzy, n2D(p*2.0)); #endif #endif uv = gl_FragCoord.xy / resolution.xy; col *= pow(16.0 * uv.x*uv.y*(1.0-uv.x)*(1.0-uv.y), 0.0625); gl_FragColor = vec4(sqrt(max(col, 0.0)), 1.0); } Незыблемый код на Питоне: #!/usr/bin/env python3 """ Пробел — пауза/продолжение анимации R — перезапустить время с нуля S — сохранить скриншот """ import os os.environ['JAVA_HOME'] = r'C:\Program Files\Java\jdk-26.0.1' import py5 from pathlib import Path WIN_W = 780 WIN_H = 780 shader t paused = None = 0.0 = False def setup(): global shader py5.size(WIN_W, WIN_H, py5.P2D) py5.frame_rate(60) py5.window_title("Contoured Layers") py5.rect_mode(py5.CORNER) path = Path(py5.sketch_path()) / "shaders" / "ContouredLayers.glsl" shader = py5.load_shader(str(path)) 131
def draw(): global t if not paused: # секунды, как iTime на Shadertoy t += 1.0 / 60.0 shader.set("resolution", float(WIN_W), float(WIN_H)) shader.set("time", t) py5.shader(shader) py5.rect(0, 0, WIN_W, WIN_H) py5.reset_shader() # Подпись py5.fill(255, 255, 255, 140) py5.no_stroke() py5.text_size(12) py5.text_align(py5.LEFT, py5.TOP) status = "⏸ пауза" if paused else f"t = {t:.1f} с" py5.text(status, 8, 8) def key_pressed(): global paused, t k = py5.key if k == ' ': paused = not paused elif k in ('r', 'R', 'к', 'К'): t = 0.0 elif k in ('s', 'S', 'ы', 'Ы'): py5.save_frame("shadertoy.png") py5.run_sketch() Контурная анимация восхищает! 132
133
Проект Satori 134
Градиентные пузыри бьются друг о друга! Шейдер Satori здесь: https://www.shadertoy.com/view/XsS3DV Оригинальный код: float k = 20.0; float field = 0.0; vec2 coord; vec2 center ( vec2 border , vec2 offset , vec2 vel ) vec2 c; if ( vel.x == 0.0 && vel.y == 0.0 ) { c = vec2(iMouse.x, iMouse.y); } else { c = offset + vel * iTime * 0.5; c = mod ( c , 2. - 4. * border ); if ( c.x > 1. - border.x ) c.x = 2. - c.x if ( c.x < border.x ) c.x = 2. * border.x if ( c.y > 1. - border.y ) c.y = 2. - c.y if ( c.y < border.y ) c.y = 2. * border.y } return c; } { - 2. * border.x; c.x; 2. * border.y; c.y; void circle ( float r , vec3 col , vec2 offset , vec2 vel ) { vec2 pos = coord.xy / iResolution.y; float aspect = iResolution.x / iResolution.y; vec2 c = center ( vec2 ( r / aspect , r ) , offset , vel ); c.x *= aspect; float d = distance ( pos , c ); field += ( k * r ) / ( d*d ); } vec3 band ( float shade, float low, float high, vec3 col1, vec3 col2 ) { if ( (shade >= low) && (shade <= high) ) { float delta = (shade - low) / (high - low); vec3 colDiff = col2 - col1; return col1 + (delta * colDiff); } else return vec3(0.0,0.0,0.0); } 135
vec3 gradient ( float shade ) { vec3 colour = vec3( (sin(iTime/2.0)*0.25)+0.25,0.0,(cos(iTime/2.0)*0.25)+0.25); vec2 mouseScaled = iMouse.xy/iResolution.xy; vec3 col1 = vec3(mouseScaled.x, 0.0, 1.0-mouseScaled.x); vec3 col2 = vec3(1.0-mouseScaled.x, 0.0, mouseScaled.x); vec3 col3 = vec3(mouseScaled.y, 1.0-mouseScaled.y, mouseScaled.y); vec3 col4 = vec3((mouseScaled.x+mouseScaled.y)/2.0, (mouseScaled.x+mouseScaled.y)/2.0, 1.0 - (mouseScaled.x+mouseScaled.y)/2.0); vec3 col5 = vec3(mouseScaled.y, mouseScaled.y, mouseScaled.y); colour += band ( shade, 0.0, 0.3, colour, col1 ); colour += band ( shade, 0.3, 0.6, col1, col2 ); colour += band ( shade, 0.6, 0.8, col2, col3 ); colour += band ( shade, 0.8, 0.9, col3, col4 ); colour += band ( shade, 0.9, 1.0, col4, col5 ); return colour; } void mainImage( out vec4 fragColor, in vec2 fragCoord ) { coord = fragCoord; circle ( .03 , vec3 ( 0.7 , 0.2 , 0.8 ) , vec2 ( .6 ) , circle ( .05 , vec3 ( 0.7 , 0.9 , 0.6 ) , vec2 ( .1 ) , circle ( .07 , vec3 ( 0.3 , 0.4 , 0.1 ) , vec2 ( .1 ) , circle ( .10 , vec3 ( 0.2 , 0.5 , 0.1 ) , vec2 ( .3 ) , circle ( .20 , vec3 ( 0.1 , 0.3 , 0.7 ) , vec2 ( .2 ) , circle ( .30 , vec3 ( 0.9 , 0.4 , 0.2 ) , vec2 ( .0 ) , circle ( .30 , vec3 ( 0.0 , 0.0 , 0.0 ) , vec2 ( .0 ), float shade = min ( 1.0, max ( field/256.0, 0.0 ) ); fragColor = vec4( gradient(shade), 1.0 ); } vec2 vec2 vec2 vec2 vec2 vec2 vec2 ( ( ( ( ( ( ( .30 , .70 ) ); .02 , .20 ) ); .10 , .04 ) ); .10 , .20 ) ); .40 , .25 ) ); .15 , .20 ) ); 0.0, 0.0 ) ); Исправляем: uniform vec2 resolution; uniform float time; uniform vec2 mouse; float k = 20.0; float field = 0.0; vec2 coord; vec2 center ( vec2 border , vec2 offset , vec2 vel ) { vec2 c; if ( vel.x == 0.0 && vel.y == 0.0 ) { 136
c = vec2(mouse.x, mouse.y); } else { c = offset + vel * time * 0.5; c = mod ( c , 2. - 4. * border if ( c.x > 1. - border.x ) c.x if ( c.x < border.x ) c.x = 2. if ( c.y > 1. - border.y ) c.y if ( c.y < border.y ) c.y = 2. } return c; ); = 2. - c.x * border.x = 2. - c.y * border.y - 2. * border.x; c.x; 2. * border.y; c.y; } void circle ( float r , vec3 col , vec2 offset , vec2 vel ) { vec2 pos = coord.xy / resolution.y; float aspect = resolution.x / resolution.y; vec2 c = center ( vec2 ( r / aspect , r ) , offset , vel ); c.x *= aspect; float d = distance ( pos , c ); field += ( k * r ) / ( d*d ); } vec3 band ( float shade, float low, float high, vec3 col1, vec3 col2 ) { if ( (shade >= low) && (shade <= high) ) { float delta = (shade - low) / (high - low); vec3 colDiff = col2 - col1; return col1 + (delta * colDiff); } else return vec3(0.0,0.0,0.0); } vec3 gradient ( float shade ) { vec3 colour = vec3( (sin(time/2.0)*0.25)+0.25,0.0,(cos(time/2.0)*0.25)+0.25); vec2 mouseScaled = mouse.xy/resolution.xy; vec3 col1 = vec3(mouseScaled.x, 0.0, 1.0-mouseScaled.x); vec3 col2 = vec3(1.0-mouseScaled.x, 0.0, mouseScaled.x); vec3 col3 = vec3(mouseScaled.y, 1.0-mouseScaled.y, mouseScaled.y); vec3 col4 = vec3((mouseScaled.x+mouseScaled.y)/2.0, (mouseScaled.x+mouseScaled.y)/2.0, 1.0 - (mouseScaled.x+mouseScaled.y)/2.0); vec3 col5 = vec3(mouseScaled.y, mouseScaled.y, mouseScaled.y); colour += band ( shade, 0.0, 0.3, colour, col1 ); colour += band ( shade, 0.3, 0.6, col1, col2 ); colour += band ( shade, 0.6, 0.8, col2, col3 ); colour += band ( shade, 0.8, 0.9, col3, col4 ); colour += band ( shade, 0.9, 1.0, col4, col5 ); return colour; } 137
void main(){ coord = gl_FragCoord.xy; circle ( .03 , vec3 ( 0.7 , 0.2 , 0.8 ) , vec2 ( .6 ) , circle ( .05 , vec3 ( 0.7 , 0.9 , 0.6 ) , vec2 ( .1 ) , circle ( .07 , vec3 ( 0.3 , 0.4 , 0.1 ) , vec2 ( .1 ) , circle ( .10 , vec3 ( 0.2 , 0.5 , 0.1 ) , vec2 ( .3 ) , circle ( .20 , vec3 ( 0.1 , 0.3 , 0.7 ) , vec2 ( .2 ) , circle ( .30 , vec3 ( 0.9 , 0.4 , 0.2 ) , vec2 ( .0 ) , circle ( .30 , vec3 ( 0.0 , 0.0 , 0.0 ) , vec2 ( .0 ), float shade = min ( 1.0, max ( field/256.0, 0.0 ) ); gl_FragColor = vec4( gradient(shade), 1.0 ); } vec2 vec2 vec2 vec2 vec2 vec2 vec2 ( ( ( ( ( ( ( .30 , .70 ) ); .02 , .20 ) ); .10 , .04 ) ); .10 , .20 ) ); .40 , .25 ) ); .15 , .20 ) ); 0.0, 0.0 ) ); Кормим Питона кодом: #!/usr/bin/env python3 """ Пробел — пауза/продолжение анимации R — перезапустить время с нуля S — сохранить скриншот """ import os os.environ['JAVA_HOME'] = r'C:\Program Files\Java\jdk-26.0.1' import py5 from pathlib import Path WIN_W = 780 WIN_H = 780 shader t paused = None = 0.0 = False def setup(): global shader py5.size(WIN_W, WIN_H, py5.P2D) py5.frame_rate(60) py5.window_title("Satori") 138
py5.rect_mode(py5.CORNER) path = Path(py5.sketch_path()) / "shaders" / "Satori.glsl" shader = py5.load_shader(str(path)) def draw(): global t if not paused: # секунды, как iTime на Shadertoy t += 1.0 / 60.0 shader.set("resolution", float(WIN_W), float(WIN_H)) shader.set("time", t) py5.shader(shader) py5.rect(0, 0, WIN_W, WIN_H) py5.reset_shader() # Подпись py5.fill(255, 255, 255, 140) py5.no_stroke() py5.text_size(12) py5.text_align(py5.LEFT, py5.TOP) status = "⏸ пауза" if paused else f"t = {t:.1f} с" py5.text(status, 8, 8) def key_pressed(): global paused, t k = py5.key if k == ' ': paused = not paused elif k in ('r', 'R', 'к', 'К'): t = 0.0 elif k in ('s', 'S', 'ы', 'Ы'): py5.save_frame("shadertoy.png") py5.run_sketch() Питон в ответ пускает пузыри! 139
140
Проект Simple circles and gradients Лихо крутятся круги! Шейдер здесь: https://www.shadertoy.com/view/llySW3 Оригинальный код: void mainImage( out vec4 fragColor, in vec2 fragCoord ) { vec2 ratio; ratio.x = iResolution.x / iResolution.y; ratio.y = 1.0; vec2 uv = fragCoord.xy / iResolution.xy; uv -= 0.5; // center coordinates uv *= ratio; // calculate circles float distance1 = sin(length(uv - vec2(-cos(iTime * 5.0) * 0.05, -sin(iTime * 5.0) * 0.05)) * (30.0 + abs(sin(iTime) * 100.0))); float distance2 = sin(length(uv - vec2(+cos(iTime * 5.0) * 0.05, +sin(iTime * 5.0) * 0.05)) * 100.0); fragColor.rgb = vec3(distance1 * distance2); fragColor.a = 1.0; // calculate rotating color grading float angleRad = iTime; vec2 sc; sc.x = cos(angleRad); sc.y = sin(angleRad); mat2 m = mat2(sc.y, sc.x, -sc.x, sc.y); uv = m * uv; vec4 color1 = vec4(1, 0.4, 0.0, 1); vec4 color2 = vec4(0.1, 0.8, 0.9, 1); fragColor += mix(color1, color2, uv.y); } Попал в переделку: 141
uniform vec2 resolution; uniform float time; void main(){ vec2 ratio; ratio.x = resolution.x / resolution.y; ratio.y = 1.0; vec2 uv = gl_FragCoord.xy / resolution.xy; uv -= 0.5; uv *= ratio; float distance1 = sin(length(uv - vec2(-cos(time * 5.0) * 0.05, -sin(time * 5.0) * 0.05)) * (30.0 + abs(sin(time) * 100.0))); float distance2 = sin(length(uv - vec2(+cos(time * 5.0) * 0.05, +sin(time * 5.0) * 0.05)) * 100.0); gl_FragColor.rgb = vec3(distance1 * distance2); gl_FragColor.a = 1.0; float angleRad = time; vec2 sc; sc.x = cos(angleRad); sc.y = sin(angleRad); mat2 m = mat2(sc.y, sc.x, -sc.x, sc.y); uv = m * uv; vec4 color1 = vec4(1, 0.4, 0.0, 1); vec4 color2 = vec4(0.1, 0.8, 0.9, 1); gl_FragColor += mix(color1, color2, uv.y); } Сопутствующий питонячий код: #!/usr/bin/env python3 """ Пробел — пауза/продолжение анимации R — перезапустить время с нуля S — сохранить скриншот """ import os os.environ['JAVA_HOME'] = r'C:\Program Files\Java\jdk-26.0.1' 142
import py5 from pathlib import Path WIN_W = 1200 WIN_H = 780 shader t paused = None = 0.0 = False def setup(): global shader py5.size(WIN_W, WIN_H, py5.P2D) py5.frame_rate(60) py5.window_title("Simple circles and gradients") py5.rect_mode(py5.CORNER) path = Path(py5.sketch_path()) / "shaders" / "simple_circles_and_gradients.glsl" shader = py5.load_shader(str(path)) def draw(): global t if not paused: # секунды, как iTime на Shadertoy t += 1.0 / 60.0 shader.set("resolution", float(WIN_W), float(WIN_H)) shader.set("time", t) py5.shader(shader) py5.rect(0, 0, WIN_W, WIN_H) py5.reset_shader() # Подпись py5.fill(255, 255, 255, 140) py5.no_stroke() py5.text_size(12) py5.text_align(py5.LEFT, py5.TOP) status = "⏸ пауза" if paused else f"t = {t:.1f} с" py5.text(status, 8, 8) def key_pressed(): global paused, t k = py5.key 143
if k == ' ': paused = not paused elif k in ('r', 'R', 'к', 'К'): t = 0.0 elif k in ('s', 'S', 'ы', 'Ы'): py5.save_frame("shadertoy.png") py5.run_sketch() 144
Проект Voronoi Wave 145
А не прокатиться ли нам на вороных? Шейдер Voronoi Wave здесь: https://www.shadertoy.com/view/3lfyDB Оригинальный код: const int POINTS = 140; // Also gives 14 point rows (point rows are determined like N / 10, from bottom to up) void voronoi(vec2 uv, inout vec3 col) { vec3 voronoi = vec3(0.0); float time = (iTime + 3000.0); // 3k time offset gives better wave pattern float bestDistance = 999.0; float lastBestDistance = bestDistance; // Used for Bloom & Outline for (int i = 0; i < POINTS; i++) // Is there a proper GPU implementation of voronoi out somewhere? { float fi = float(i); vec2 p = vec2(mod(fi, 1.0) * 0.1 + sin(fi), -0.05 + 0.15 * float(i / 10) + cos(fi + time * cos(uv.x * 0.025))); float d = distance(uv, p); if (d < bestDistance) { lastBestDistance = bestDistance; bestDistance = d; // Two colored gradients for voronoi color variation voronoi.x = p.x; voronoi.yz = vec2(p.x * 0.4 + p.y, p.y) * vec2(0.9, 0.87); } } col *= 0.64 + 0.29 * voronoi; // Mix voronoi effect and default shadertoy gradient col += smoothstep(0.99, 1.05, 1.0 - abs(bestDistance - lastBestDistance)) * 0.9; // Outline col += smoothstep(0.95, 1.05, 1.0 - abs(bestDistance - lastBestDistance)) * 0.2 * col; // Outline fade border col += (voronoi) * 0.1 * smoothstep(0.5, 1.0, 1.0 - abs(bestDistance lastBestDistance)); // Bloom } 146
void mainImage( out vec4 fragColor, in vec2 fragCoord ) { // Normalized pixel coordinates (from 0 to 1) vec2 uv = fragCoord/iResolution.xy; // Time varying pixel color vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4)); // Effect looks nice on this uv scaling voronoi(uv * 2.0 - 1.0, col); // Output to screen fragColor = vec4(col,1.0); } Поковали вороного: uniform vec2 resolution; uniform float time; const int POINTS = 140; void voronoi(vec2 uv, inout vec3 col) { vec3 voronoi = vec3(0.0); float time = (time + 3000.0); float bestDistance = 999.0; float lastBestDistance = bestDistance; for (int i = 0; i < POINTS; i++) { float fi = float(i); vec2 p = vec2(mod(fi, 1.0) * 0.1 + sin(fi), -0.05 + 0.15 * float(i / 10) + cos(fi + time * cos(uv.x * 0.025))); float d = distance(uv, p); if (d < bestDistance) { lastBestDistance = bestDistance; bestDistance = d; voronoi.x = p.x; voronoi.yz = vec2(p.x * 0.4 + p.y, p.y) * vec2(0.9, 0.87); } } 147
col *= 0.64 + 0.29 * voronoi; col += smoothstep(0.99, 1.05, 1.0 - abs(bestDistance - lastBestDistance)) * 0.9; col += smoothstep(0.95, 1.05, 1.0 - abs(bestDistance - lastBestDistance)) * 0.2 * col; col += (voronoi) * 0.1 * smoothstep(0.5, 1.0, 1.0 - abs(bestDistance lastBestDistance)); } void main(){ vec2 uv = gl_FragCoord.xy/resolution.xy; vec3 col = 0.5 + 0.5*cos(time+uv.xyx+vec3(0,2,4)); voronoi(uv * 2.0 - 1.0, col); gl_FragColor = vec4(col,1.0); } И передали Питону: #!/usr/bin/env python3 """ Пробел — пауза/продолжение анимации R — перезапустить время с нуля S — сохранить скриншот """ import os os.environ['JAVA_HOME'] = r'C:\Program Files\Java\jdk-26.0.1' import py5 from pathlib import Path WIN_W = 780 WIN_H = 780 shader t paused = None = 0.0 = False def setup(): global shader py5.size(WIN_W, WIN_H, py5.P2D) 148
py5.frame_rate(60) py5.window_title("Voronoi Wave") py5.rect_mode(py5.CORNER) path = Path(py5.sketch_path()) / "shaders" / "VoronoiWave.glsl" shader = py5.load_shader(str(path)) def draw(): global t if not paused: # секунды, как iTime на Shadertoy t += 1.0 / 60.0 shader.set("resolution", float(WIN_W), float(WIN_H)) shader.set("time", t) py5.shader(shader) py5.rect(0, 0, WIN_W, WIN_H) py5.reset_shader() # Подпись py5.fill(255, 255, 255, 140) py5.no_stroke() py5.text_size(12) py5.text_align(py5.LEFT, py5.TOP) status = "⏸ пауза" if paused else f"t = {t:.1f} с" py5.text(status, 8, 8) def key_pressed(): global paused, t k = py5.key if k == ' ': paused = not paused elif k in ('r', 'R', 'к', 'К'): t = 0.0 elif k in ('s', 'S', 'ы', 'Ы'): py5.save_frame("shadertoy.png") py5.run_sketch() 149
150
Проект Light Circles 151
А не покрутить ли нам обруч вокруг себя? Шейдер Light Circles здесь: https://www.shadertoy.com/view/sfKGzV Оригинальный код: vec3 palette(float t, vec3 p[4]) { return p[0] + p[1] * cos(6.28318 * (p[2] * t + p[3])); } // Usage vec3 myPalette[4] = vec3[4]( vec3(0.5, 0.5, 0.5), vec3(0.5, 0.5, 0.5), vec3(1.0, 1.0, 1.0), vec3(0., 0.33, 0.67) ); mat2 rotate2D(float angle){ float s = sin(angle); float c = cos(angle); return mat2(c, -s, s, c); } void mainImage( out vec4 fragColor, in vec2 fragCoord ) { // Normalized pixel coordinates (from 0 to 1) vec2 uv = (fragCoord * 2. - iResolution.xy) / iResolution.y; vec2 uv0 = uv; uv = rotate2D(cos(iTime*.1)*1.67) * uv; uv.x = uv.x + cos(iTime*.07)*2.; uv.y = uv.y + sin(iTime*.03)*2.; vec3 finalColor = vec3(0.); for (float i = 0.0; i < 3.0; i++){ uv = fract(uv * 1.7) - .5; vec2 p = vec2(cos(iTime*1.2),sin(iTime*.8)); float d = length(uv); //d *= exp(-length(uv0)); vec3 col = palette(length(uv0 - p) + iTime*.4 + i*.4, myPalette); d = sin(d * 8. + cos(iTime*(1.4+i))) / 8.; d = abs(d); 152
d = pow(.01 / d, 1.2); d *= (abs(cos(uv0.x + cos(iTime*.68))) + .1 + abs(sin(uv.y) + sin(iTime*.35))); finalColor += col*d; } // Output to screen fragColor = vec4(finalColor, 1.0); } Перекрутили: uniform vec2 resolution; uniform float time; vec3 palette(float t, vec3 p[4]) { return p[0] + p[1] * cos(6.28318 * (p[2] * t + p[3])); } vec3 myPalette[4] = vec3[4]( vec3(0.5, 0.5, 0.5), vec3(0.5, 0.5, 0.5), vec3(1.0, 1.0, 1.0), vec3(0., 0.33, 0.67) ) mat2 rotate2D(float angle){ float s = sin(angle); float c = cos(angle); return mat2(c, -s, s, c); } void main(){ vec2 uv = (gl_FragCoord.xy * 2. - resolution.xy) / resolution.y; vec2 uv0 = uv; uv = rotate2D(cos(time*.1)*1.67) * uv; uv.x = uv.x + cos(time*.07)*2.; uv.y = uv.y + sin(time*.03)*2.; vec3 finalColor = vec3(0.); for (float i = 0.0; i < 3.0; i++){ uv = fract(uv * 1.7) - .5; vec2 p = vec2(cos(time*1.2),sin(time*.8)); float d = length(uv); 153
vec3 col = palette(length(uv0 - p) + time*.4 + i*.4, myPalette); d = sin(d * 8. + cos(time*(1.4+i))) / 8.; d = abs(d); d = pow(.01 / d, 1.2); d *= (abs(cos(uv0.x + cos(time*.68))) + .1 + abs(sin(uv.y) + sin(time*.35))); finalColor += col*d; } gl_FragColor = vec4(finalColor, 1.0); } И передали Питону: #!/usr/bin/env python3 """ Пробел — пауза/продолжение анимации R — перезапустить время с нуля S — сохранить скриншот """ import os os.environ['JAVA_HOME'] = r'C:\Program Files\Java\jdk-26.0.1' import py5 from pathlib import Path WIN_W = 770 WIN_H = 770 shader t paused = None = 0.0 = False def setup(): global shader py5.size(WIN_W, WIN_H, py5.P2D) py5.frame_rate(60) py5.window_title("Light Circles") py5.rect_mode(py5.CORNER) path = Path(py5.sketch_path()) / "shaders" / "LightCircles.glsl" 154
shader = py5.load_shader(str(path)) def draw(): global t if not paused: # секунды, как iTime на Shadertoy t += 1.0 / 60.0 shader.set("resolution", float(WIN_W), float(WIN_H)) shader.set("time", t) py5.shader(shader) py5.rect(0, 0, WIN_W, WIN_H) py5.reset_shader() # Подпись py5.fill(255, 255, 255, 140) py5.no_stroke() py5.text_size(12) py5.text_align(py5.LEFT, py5.TOP) status = "⏸ пауза" if paused else f"t = {t:.1f} с" py5.text(status, 8, 8) def key_pressed(): global paused, t k = py5.key if k == ' ': paused = not paused elif k in ('r', 'R', 'к', 'К'): t = 0.0 elif k in ('s', 'S', 'ы', 'Ы'): py5.save_frame("shadertoy.png") py5.run_sketch() 155
156
Проект Iterations - inversion 2 157
Шейдер Iterations - inversion 2 здесь: https://www.shadertoy.com/view/4t3SzN Оригинальный код: // // // // // // // // // // Copyright Inigo Quilez, 2016 - https://iquilezles.org/ I am the sole copyright owner of this Work. You cannot host, display, distribute or share this Work in any form, including physical and digital. You cannot use this Work in any commercial or non-commercial product, website or project. You cannot sell this Work and you cannot mint an NFTs of it. I share this Work for educational purposes, and you can link to it, through an URL, proper attribution and unmodified screenshot, as part of your educational material. If these conditions are too restrictive please contact me and we'll definitely work it out. // based on https://www.shadertoy.com/view/XdXGDS #define AA 2 // // // // // // // // // // // // Other "Iterations" shaders: "trigonometric" "trigonometric 2" "circles" "coral" "guts" "inversion" "inversion 2" "shiny" "worms" "stripes" : : : : : : : : : : https://www.shadertoy.com/view/Mdl3RH https://www.shadertoy.com/view/Wss3zB https://www.shadertoy.com/view/MdVGWR https://www.shadertoy.com/view/4sXGDN https://www.shadertoy.com/view/MssGW4 https://www.shadertoy.com/view/XdXGDS https://www.shadertoy.com/view/4t3SzN https://www.shadertoy.com/view/MslXz8 https://www.shadertoy.com/view/ldl3W4 https://www.shadertoy.com/view/wlsfRn vec3 shape( in vec2 uv ) { float time = iTime*0.05 + 47.0; vec2 z = -1.0 + 2.0*uv; z *= 1.5; vec3 col = vec3(1.0); for( int j=0; j<48; j++ ) { float s = float(j)/16.0; float f = 0.2*(0.5 + 1.0*fract(sin(s*20.0))); vec2 c = 0.5*vec2( cos(f*time+17.0*s),sin(f*time+19.0*s) ); z -= c; 158
float zr = length( z ); float ar = atan( z.y, z.x ) + zr*0.6; z = vec2( cos(ar), sin(ar) )/zr; z += c; // color col -= 0.5*exp( -10.0*dot(z,z) )* (0.25+0.4*sin( vec3(1.6,0.8,0.5) )); } return col; } 5.5 + 1.5*s + void mainImage( out vec4 fragColor, in vec2 fragCoord ) { float e = 1.0/iResolution.x; vec3 tot = vec3(0.0); for( int m=0; m<AA; m++ ) for( int n=0; n<AA; n++ ) { vec2 uv = (fragCoord+vec2(m,n)/float(AA))/iResolution.xy; vec3 col = shape( uv ); float f = dot(col,vec3(0.333)); vec3 nor = normalize( vec3( dot(shape(uv+vec2(e,0.0)),vec3(0.333))-f, dot(shape(uv+vec2(0.0,e)),vec3(0.333))-f, e ) ); col += 0.2*vec3(1.0,0.9,0.5)*dot(nor,vec3(0.8,0.4,0.2));; col += 0.3*nor.z; tot += col; } tot /= float(AA*AA); tot = pow( clamp(tot,0.0,1.0), vec3(0.8,1.1,1.3) ); vec2 uv = fragCoord/iResolution.xy; tot *= 0.4 + 0.6*pow( 16.0*uv.x*uv.y*(1.0-uv.x)*(1.0-uv.y), 0.1 ); fragColor = vec4( tot, 1.0 ); } Наши итерации: uniform vec2 center; uniform vec2 resolution; uniform float time; #define AA 2 159
vec3 shape( in vec2 uv ) { float time = time*0.05 + 47.0; vec2 z = -1.0 + 2.0*uv; z *= 1.5; vec3 col = vec3(1.0); for( int j=0; j<48; j++ ) { float s = float(j)/16.0; float f = 0.2*(0.5 + 1.0*fract(sin(s*20.0))); vec2 c = 0.5*vec2( cos(f*time+17.0*s),sin(f*time+19.0*s) ); z -= c; float zr = length( z ); float ar = atan( z.y, z.x ) + zr*0.6; z = vec2( cos(ar), sin(ar) )/zr; z += c; col -= 0.5*exp( -10.0*dot(z,z) )* (0.25+0.4*sin( 5.5 + 1.5*s + vec3(1.6,0.8,0.5) )); } return col; } void main(){ float e = 1.0/resolution.x; vec3 tot = vec3(0.0); for( int m=0; m<AA; m++ ) for( int n=0; n<AA; n++ ) { vec2 uv = (gl_FragCoord.xy+vec2(m,n)/float(AA))/resolution.xy; vec3 col = shape( uv ); float f = dot(col,vec3(0.333)); vec3 nor = normalize( vec3( dot(shape(uv+vec2(e,0.0)),vec3(0.333))-f, dot(shape(uv+vec2(0.0,e)),vec3(0.333))-f, e ) ); col += 0.2*vec3(1.0,0.9,0.5)*dot(nor,vec3(0.8,0.4,0.2));; col += 0.3*nor.z; tot += col; } tot /= float(AA*AA); tot = pow( clamp(tot,0.0,1.0), vec3(0.8,1.1,1.3) ); 160
vec2 uv = gl_FragCoord.xy/resolution.xy; tot *= 0.4 + 0.6*pow( 16.0*uv.x*uv.y*(1.0-uv.x)*(1.0-uv.y), 0.1 ); gl_FragColor = vec4( tot, 1.0 ); } Код на Питоне: #!/usr/bin/env python3 """ Пробел — пауза/продолжение анимации R — перезапустить время с нуля S — сохранить скриншот """ import os os.environ['JAVA_HOME'] = r'C:\Program Files\Java\jdk-26.0.1' import py5 from pathlib import Path WIN_W = 770 WIN_H = 770 shader t paused = None = 0.0 = False def setup(): global shader py5.size(WIN_W, WIN_H, py5.P2D) py5.frame_rate(60) py5.window_title("Iterations - inversion 2") py5.rect_mode(py5.CORNER) path = Path(py5.sketch_path()) / "shaders" / "Iterations-inversion2.glsl" shader = py5.load_shader(str(path)) def draw(): global t if not paused: # секунды, как iTime на Shadertoy 161
t += 1.0 / 60.0 shader.set("resolution", float(WIN_W), float(WIN_H)) shader.set("time", t) py5.shader(shader) py5.rect(0, 0, WIN_W, WIN_H) py5.reset_shader() # Подпись py5.fill(255, 255, 255, 140) py5.no_stroke() py5.text_size(12) py5.text_align(py5.LEFT, py5.TOP) status = "⏸ пауза" if paused else f"t = {t:.1f} с" py5.text(status, 8, 8) def key_pressed(): global paused, t k = py5.key if k == ' ': paused = not paused elif k in ('r', 'R', 'к', 'К'): t = 0.0 elif k in ('s', 'S', 'ы', 'Ы'): py5.save_frame("shadertoy.png") py5.run_sketch() Большое Красное Пятно Юпитера отдыхает! 162
163
Проект Iterations - inversion 164
Шейдер Iterations - inversion здесь: https://www.shadertoy.com/view/XdXGDS Оригинальный код: // // // // // // // // // // Copyright Inigo Quilez, 2013 - https://iquilezles.org/ I am the sole copyright owner of this Work. You cannot host, display, distribute or share this Work in any form, including physical and digital. You cannot use this Work in any commercial or non-commercial product, website or project. You cannot sell this Work and you cannot mint an NFTs of it. I share this Work for educational purposes, and you can link to it, through an URL, proper attribution and unmodified screenshot, as part of your educational material. If these conditions are too restrictive please contact me and we'll definitely work it out. // // // // // // // // // // // // Other "Iterations" shaders: "trigonometric" "trigonometric 2" "circles" "coral" "guts" "inversion" "inversion 2" "shiny" "worms" "stripes" : : : : : : : : : : https://www.shadertoy.com/view/Mdl3RH https://www.shadertoy.com/view/Wss3zB https://www.shadertoy.com/view/MdVGWR https://www.shadertoy.com/view/4sXGDN https://www.shadertoy.com/view/MssGW4 https://www.shadertoy.com/view/XdXGDS https://www.shadertoy.com/view/4t3SzN https://www.shadertoy.com/view/MslXz8 https://www.shadertoy.com/view/ldl3W4 https://www.shadertoy.com/view/wlsfRn void mainImage( out vec4 fragColor, in vec2 fragCoord ) { vec2 uv = fragCoord.xy/iResolution.xy; // shape (16 points) float time = iTime + 47.0; vec2 z = -1.0 + 2.0*uv; vec3 col = vec3(1.0); for( int j=0; j<16; j++ ) { // deform float s = float(j)/16.0; float f = 0.2*(0.5 + 1.0*fract(sin(s*113.1)*43758.5453123)); vec2 c = 0.5*vec2( cos(f*time+17.0*s),sin(f*time+19.0*s) ); z -= c; 165
float zr = length( z ); float ar = atan( z.y, z.x ) + zr*0.6; z = vec2( cos(ar), sin(ar) )/zr; z += c; z += 0.05*sin(2.0*z.x); // color col -= 0.7*exp( -8.0*dot(z,z) )* (0.5+0.5*sin( 4.2*s + vec3(1.6,0.9,0.3) )); } col *= 0.75 + 0.25*clamp(length(z-uv)*0.6,0.0,1.0); // 3d effect float h = dot(col,vec3(0.333)); vec3 nor = normalize( vec3( dFdx(h), dFdy(h), 1.0/iResolution.x ) ); col -= 0.05*vec3(1.0,0.9,0.5)*dot(nor,vec3(0.8,0.4,0.2));; col += 0.25*(1.0-0.8*col)*nor.z*nor.z; // 2d postpro col *= 1.12; col = pow( clamp(col,0.0,1.0), vec3(0.8) ); col *= 0.8 + 0.2*pow( 16.0*uv.x*uv.y*(1.0-uv.x)*(1.0-uv.y), 0.1 ); fragColor = vec4( col, 1.0 ); } Итерируем по-нашему! uniform vec2 resolution; uniform float time; void main(){ vec2 uv = gl_FragCoord.xy/resolution.xy; float time = time + 47.0; vec2 z = -1.0 + 2.0*uv; vec3 col = vec3(1.0); for( int j=0; j<16; j++ ) { float s = float(j)/16.0; float f = 0.2*(0.5 + 1.0*fract(sin(s*113.1)*43758.5453123)); vec2 c = 0.5*vec2( cos(f*time+17.0*s),sin(f*time+19.0*s) ); z -= c; float zr = length( z ); float ar = atan( z.y, z.x ) + zr*0.6; 166
z = vec2( cos(ar), sin(ar) )/zr; z += c; z += 0.05*sin(2.0*z.x); col -= 0.7*exp( -8.0*dot(z,z) )* (0.5+0.5*sin( 4.2*s + vec3(1.6,0.9,0.3) )); } col *= 0.75 + 0.25*clamp(length(z-uv)*0.6,0.0,1.0); float h = dot(col,vec3(0.333)); vec3 nor = normalize( vec3( dFdx(h), dFdy(h), 1.0/resolution.x ) ); col -= 0.05*vec3(1.0,0.9,0.5)*dot(nor,vec3(0.8,0.4,0.2));; col += 0.25*(1.0-0.8*col)*nor.z*nor.z; col *= 1.12; col = pow( clamp(col,0.0,1.0), vec3(0.8) ); col *= 0.8 + 0.2*pow( 16.0*uv.x*uv.y*(1.0-uv.x)*(1.0-uv.y), 0.1 ); gl_FragColor = vec4( col, 1.0 ); } Код на Питоне: #!/usr/bin/env python3 """ Пробел — пауза/продолжение анимации R — перезапустить время с нуля S — сохранить скриншот """ import os os.environ['JAVA_HOME'] = r'C:\Program Files\Java\jdk-26.0.1' import py5 from pathlib import Path WIN_W = 770 WIN_H = 770 shader t paused = None = 0.0 = False def setup(): global shader py5.size(WIN_W, WIN_H, py5.P2D) py5.frame_rate(60) 167
py5.window_title("Iterations - inversion") py5.rect_mode(py5.CORNER) path = Path(py5.sketch_path()) / "shaders" / "Iterations-inversion.glsl" shader = py5.load_shader(str(path)) def draw(): global t if not paused: # секунды, как iTime на Shadertoy t += 1.0 / 60.0 shader.set("resolution", float(WIN_W), float(WIN_H)) shader.set("time", t) py5.shader(shader) py5.rect(0, 0, WIN_W, WIN_H) py5.reset_shader() # Подпись py5.fill(255, 255, 255, 140) py5.no_stroke() py5.text_size(12) py5.text_align(py5.LEFT, py5.TOP) status = "⏸ пауза" if paused else f"t = {t:.1f} с" py5.text(status, 8, 8) def key_pressed(): global paused, t k = py5.key if k == ' ': paused = not paused elif k in ('r', 'R', 'к', 'К'): t = 0.0 elif k in ('s', 'S', 'ы', 'Ы'): py5.save_frame("shadertoy.png") py5.run_sketch() С изрядной долей фантазии, которая нам присуща, вполне можно разглядеть в круговороте пикселей неопознанную чёрную дыру, которая поглощает невинных звёзд шоу-бизнеса. 168
169
Проект Warping - procedural 2 Шейдер Warping - procedural 2 здесь: https://www.shadertoy.com/view/lsl3RH Оригинальный код: // // // // // // // // Tutorial: https://iquilezles.org/articles/warp More warping examples: https://www.shadertoy.com/view/4s23zz https://www.shadertoy.com/view/lsl3RH https://www.shadertoy.com/view/XsfSD4 https://www.shadertoy.com/view/MdSXzz https://www.shadertoy.com/view/3XlfWH //==================================================================== const mat2 m = mat2( 0.80, 0.60, -0.60, 0.80 ); float noise( in vec2 p ) { return sin(p.x)*sin(p.y); } float fbm4( vec2 p ) { float f = 0.0; f += 0.5000*noise( f += 0.2500*noise( f += 0.1250*noise( f += 0.0625*noise( return f/0.9375; } p p p p ); p = m*p*2.02; ); p = m*p*2.03; ); p = m*p*2.01; ); float fbm6( vec2 p ) { float f = 0.0; f += 0.500000*(0.5+0.5*noise( f += 0.250000*(0.5+0.5*noise( f += 0.125000*(0.5+0.5*noise( f += 0.062500*(0.5+0.5*noise( f += 0.031250*(0.5+0.5*noise( f += 0.015625*(0.5+0.5*noise( return f/0.96875; } p p p p p p )); )); )); )); )); )); p p p p p = = = = = m*p*2.02; m*p*2.03; m*p*2.01; m*p*2.04; m*p*2.01; 170
vec2 fbm4_2( vec2 p ) { return vec2(fbm4(p), fbm4(p+vec2(7.8))); } vec2 fbm6_2( vec2 p ) { return vec2(fbm6(p+vec2(16.8)), fbm6(p+vec2(11.5))); } //==================================================================== float func( vec2 q, out vec4 ron ) { q += 0.03*sin( vec2(0.27,0.23)*iTime + length(q)*vec2(4.1,4.3)); vec2 o = fbm4_2( 0.9*q ); o += 0.04*sin( vec2(0.12,0.14)*iTime + length(o)); vec2 n = fbm6_2( 3.0*o ); ron = vec4( o, n ); float f = 0.5 + 0.5*fbm4( 1.8*q + 6.0*n ); return mix( f, f*f*f*3.5, f*abs(n.x) ); } void mainImage( out vec4 fragColor, in vec2 fragCoord ) { vec2 p = (2.0*fragCoord-iResolution.xy)/iResolution.y; float e = 2.0/iResolution.y; vec4 on = vec4(0.0); float f = func(p, on); vec3 col = vec3(0.0); col = mix( vec3(0.2,0.1,0.4), vec3(0.3,0.05,0.05), f ); col = mix( col, vec3(0.9,0.9,0.9), dot(on.zw,on.zw) ); col = mix( col, vec3(0.4,0.3,0.3), 0.2 + 0.5*on.y*on.y ); col = mix( col, vec3(0.0,0.2,0.4), step(1.2,1.3,abs(on.z)+abs(on.w)) ); col = clamp( col*f*2.0, 0.0, 1.0 ); 0.5*smooth- #if 0 // gpu derivatives - bad quality, but fast vec3 nor = normalize( vec3( dFdx(f)*iResolution.x, 6.0, dFdy(f)*iResolution.y ) ); #else // manual derivatives - better quality, but slower vec4 kk; vec3 nor = normalize( vec3( func(p+vec2(e,0.0),kk)-f, 2.0*e, func(p+vec2(0.0,e),kk)-f ) ); #endif 171
vec3 lig = normalize( vec3( 0.9, 0.2, -0.4 ) ); float dif = clamp( 0.3+0.7*dot( nor, lig ), 0.0, 1.0 ); vec3 lin = vec3(0.70,0.90,0.95)*(nor.y*0.5+0.5) + vec3(0.15,0.10,0.05)*dif; col *= 1.2*lin; col = 1.0 - col; col = 1.1*col*col; fragColor = vec4( col, 1.0 ); } Наше дело: uniform vec2 resolution; uniform float time; const mat2 m = mat2( 0.80, 0.60, -0.60, 0.80 ); float noise( in vec2 p ) { return sin(p.x)*sin(p.y); } float fbm4( vec2 p ) { float f = 0.0; f += 0.5000*noise( f += 0.2500*noise( f += 0.1250*noise( f += 0.0625*noise( return f/0.9375; } p p p p ); p = m*p*2.02; ); p = m*p*2.03; ); p = m*p*2.01; ); float fbm6( vec2 p ) { float f = 0.0; f += 0.500000*(0.5+0.5*noise( f += 0.250000*(0.5+0.5*noise( f += 0.125000*(0.5+0.5*noise( f += 0.062500*(0.5+0.5*noise( f += 0.031250*(0.5+0.5*noise( f += 0.015625*(0.5+0.5*noise( return f/0.96875; } p p p p p p )); )); )); )); )); )); p p p p p = = = = = m*p*2.02; m*p*2.03; m*p*2.01; m*p*2.04; m*p*2.01; 172
vec2 fbm4_2( vec2 p ) { return vec2(fbm4(p), fbm4(p+vec2(7.8))); } vec2 fbm6_2( vec2 p ) { return vec2(fbm6(p+vec2(16.8)), fbm6(p+vec2(11.5))); } float func( vec2 q, out vec4 ron ) { q += 0.03*sin( vec2(0.27,0.23)*time + length(q)*vec2(4.1,4.3)); vec2 o = fbm4_2( 0.9*q ); o += 0.04*sin( vec2(0.12,0.14)*time + length(o)); vec2 n = fbm6_2( 3.0*o ); ron = vec4( o, n ); float f = 0.5 + 0.5*fbm4( 1.8*q + 6.0*n ); return mix( f, f*f*f*3.5, f*abs(n.x) ); } void main(){ vec2 p = (2.0*gl_FragCoord.xy-resolution.xy)/resolution.y; float e = 2.0/resolution.y; vec4 on = vec4(0.0); float f = func(p, on); vec3 col = vec3(0.0); col = mix( vec3(0.2,0.1,0.4), vec3(0.3,0.05,0.05), f ); col = mix( col, vec3(0.9,0.9,0.9), dot(on.zw,on.zw) ); col = mix( col, vec3(0.4,0.3,0.3), 0.2 + 0.5*on.y*on.y ); col = mix( col, vec3(0.0,0.2,0.4), 0.5*smoothstep(1.2,1.3,abs(on.z)+abs(on.w)) ); col = clamp( col*f*2.0, 0.0, 1.0 ); #if 0 vec3 nor = normalize( vec3( dFdx(f)*resolution.x, 6.0, dFdy(f)*resolution.y 173
) ); #else vec4 kk; vec3 nor = normalize( vec3( func(p+vec2(e,0.0),kk)-f, 2.0*e, func(p+vec2(0.0,e),kk)-f ) ); #endif vec3 lig = normalize( vec3( 0.9, 0.2, -0.4 ) ); float dif = clamp( 0.3+0.7*dot( nor, lig ), 0.0, 1.0 ); vec3 lin = vec3(0.70,0.90,0.95)*(nor.y*0.5+0.5) + vec3(0.15,0.10,0.05)*dif; col *= 1.2*lin; col = 1.0 - col; col = 1.1*col*col; gl_FragColor = vec4( col, 1.0 ); } Код на Питоне: #!/usr/bin/env python3 """ Пробел — пауза/продолжение анимации R — перезапустить время с нуля S — сохранить скриншот """ import os os.environ['JAVA_HOME'] = r'C:\Program Files\Java\jdk-26.0.1' import py5 from pathlib import Path WIN_W = 770 WIN_H = 770 shader t paused = None = 0.0 = False def setup(): global shader py5.size(WIN_W, WIN_H, py5.P2D) 174
py5.frame_rate(60) py5.window_title("Warping - procedural 2") py5.rect_mode(py5.CORNER) path = Path(py5.sketch_path()) / "shaders" / "Warping-procedural2.glsl" shader = py5.load_shader(str(path)) def draw(): global t if not paused: # секунды, как iTime на Shadertoy t += 1.0 / 60.0 shader.set("resolution", float(WIN_W), float(WIN_H)) shader.set("time", t) py5.shader(shader) py5.rect(0, 0, WIN_W, WIN_H) py5.reset_shader() # Подпись py5.fill(255, 255, 255, 140) py5.no_stroke() py5.text_size(12) py5.text_align(py5.LEFT, py5.TOP) status = "⏸ пауза" if paused else f"t = {t:.1f} с" py5.text(status, 8, 8) def key_pressed(): global paused, t k = py5.key if k == ' ': paused = not paused elif k in ('r', 'R', 'к', 'К'): t = 0.0 elif k in ('s', 'S', 'ы', 'Ы'): py5.save_frame("shadertoy.png") py5.run_sketch() Отличные пиксельные процедуры! 175
176
Проект Warping - procedural 5 177
Шейдер Warping - procedural 5 здесь: https://www.shadertoy.com/view/3XlfWH Оригинальный код: // // // // // // // // // // // // Copyright Inigo Quilez, 2025 - https://iquilezles.org/ I am the sole copyright owner of this Work. You cannot host, display, distribute or share this Work neither as is or altered, in any form including physical and digital. You cannot use this Work in any commercial or non-commercial product, website or project. You cannot sell this Work and you cannot mint an NFTs of it. You cannot use this Work to train AI models. I share this Work for educational purposes, you can link to it as an URL, proper attribution and unmodified screenshot, as part of your educational material. If these conditions are too restrictive please contact me. // // // // // // // // // // // Tutorial: https://iquilezles.org/articles/warp More warping examples: https://www.shadertoy.com/view/4s23zz https://www.shadertoy.com/view/lsl3RH https://www.shadertoy.com/view/XsfSD4 https://www.shadertoy.com/view/MdSXzz https://www.shadertoy.com/view/3XlfWH float hash21( in vec2 p ) { // replace this by something better p = 50.0*fract( p*0.3183099 + vec2(0.71,0.113)); return fract( p.x*p.y*(p.x+p.y) ); } vec2 hash22( in vec2 p ) { return vec2(hash21(p.xy+vec2(0.0,0.0)), hash21(p.yx+vec2(0.7,0.5))); } float noise( in vec2 x ) { 178
vec2 i = floor(x); vec2 f = fract(x); f = f*f*(3.0-2.0*f); float a = hash21(i+vec2(0,0)); float b = hash21(i+vec2(1,0)); float c = hash21(i+vec2(0,1)); float d = hash21(i+vec2(1,1)); return -1.0+2.0*mix(mix(a,b,f.x),mix(c,d,f.x),f.y); } float voronoi( in vec2 p ) { vec2 i = floor(p); vec2 f = fract(p); float d = 10.0; for( int n=-1; n<=1; n++ ) for( int m=-1; m<=1; m++ ) { vec2 b = vec2(m, n); vec2 r = b - f + hash22(i+b); d = min(d,dot(r,r)); } return d; } float fbmNoise( in vec2 p, in int oct ) { const mat2 m = mat2( 0.8, 0.6, -0.6, 0.8 ); float f = 0.0; float s = 0.5; float t = 0.0; for( int i=0; i<oct; i++ ) { f += s*noise( p ); t += s; p = m*p*2.01; s *= 0.5; } return f/t; } float fbmVoronoi( in vec2 p ) { float f = 1.0; float s = 1.0; for( int i=0; i<8; i++ ) { 179
float v = voronoi(p); f = min(f,v*s); p *= 2.0; s *= 1.4; } return 3.0*f; } vec2 fbm2Noise( in vec2 p, in int o ) { return vec2(fbmNoise(p.xy+vec2(0.0,0.0),o), fbmNoise(p.yx+vec2(0.7,1.3),o)); } //==================================================================== // distortion vec2 dis( in vec2 p, in float t ) { // accelerate t += 0.3*sin(t); // scroll p.x -= 0.2*t; // wirl const float a = 0.7; p += a*0.5000*sin(p.yx*1.4+0.0+t); p += a*0.2500*sin(p.yx*2.3+1.0+t); p += a*0.1250*sin(p.yx*4.2+2.0+t); p += a*0.0625*sin(p.yx*8.1+3.0+t); // turbulence p += 0.4*fbm2Noise( 0.5*p-t*vec2(0.9,0.18), 2 ); return p; } void mainImage( out vec4 fragColor, in vec2 fragCoord ) { vec2 p = (2.0*fragCoord-iResolution.xy)/iResolution.y; // distortion (and its velocity) const float dt = 0.01; vec2 q = dis(p,iTime); vec2 pq = dis(p,iTime-dt); float vel = length(q-pq)/dt; 180
// wave float f = q.y - 0.5*sin(1.57*q.x); // circles f -= 0.5*vel*vel*(0.5-fbmVoronoi(0.5*q)); // colorize f = 0.5 + 1.5*fbmNoise( vec2(2.5*f,0.0), 10 ); vec3 col = mix(vec3(0.0,0.25,0.6),vec3(1.0),f); // vignetting col *= 1.0 - 0.1*dot(p,p); fragColor = vec4( col, 1.0 ); } Наше переделкино: uniform vec2 resolution; uniform float time; float hash21( in vec2 p ) { p = 50.0*fract( p*0.3183099 + vec2(0.71,0.113)); return fract( p.x*p.y*(p.x+p.y) ); } vec2 hash22( in vec2 p ) { return vec2(hash21(p.xy+vec2(0.0,0.0)), hash21(p.yx+vec2(0.7,0.5))); } float noise( in vec2 x ) { vec2 i = floor(x); vec2 f = fract(x); f = f*f*(3.0-2.0*f); float a = hash21(i+vec2(0,0)); float b = hash21(i+vec2(1,0)); float c = hash21(i+vec2(0,1)); float d = hash21(i+vec2(1,1)); 181
return -1.0+2.0*mix(mix(a,b,f.x),mix(c,d,f.x),f.y); } float voronoi( in vec2 p ) { vec2 i = floor(p); vec2 f = fract(p); float d = 10.0; for( int n=-1; n<=1; n++ ) for( int m=-1; m<=1; m++ ) { vec2 b = vec2(m, n); vec2 r = b - f + hash22(i+b); d = min(d,dot(r,r)); } return d; } float fbmNoise( in vec2 p, in int oct ) { const mat2 m = mat2( 0.8, 0.6, -0.6, 0.8 ); float f = 0.0; float s = 0.5; float t = 0.0; for( int i=0; i<oct; i++ ) { f += s*noise( p ); t += s; p = m*p*2.01; s *= 0.5; } return f/t; } float fbmVoronoi( in vec2 p ) { float f = 1.0; float s = 1.0; for( int i=0; i<8; i++ ) { float v = voronoi(p); f = min(f,v*s); p *= 2.0; s *= 1.4; } return 3.0*f; } 182
vec2 fbm2Noise( in vec2 p, in int o ) { return vec2(fbmNoise(p.xy+vec2(0.0,0.0),o), fbmNoise(p.yx+vec2(0.7,1.3),o)); } vec2 dis( in vec2 p, in float t ) { t += 0.3*sin(t); p.x -= 0.2*t; const float a = 0.7; p += a*0.5000*sin(p.yx*1.4+0.0+t); p += a*0.2500*sin(p.yx*2.3+1.0+t); p += a*0.1250*sin(p.yx*4.2+2.0+t); p += a*0.0625*sin(p.yx*8.1+3.0+t); p += 0.4*fbm2Noise( 0.5*p-t*vec2(0.9,0.18), 2 ); return p; } void main(){ vec2 p = (2.0*gl_FragCoord.xy-resolution.xy)/resolution.y; const vec2 vec2 float float q = pq = vel = dt = 0.01; dis(p,time); dis(p,time-dt); length(q-pq)/dt; float f = q.y - 0.5*sin(1.57*q.x); f -= 0.5*vel*vel*(0.5-fbmVoronoi(0.5*q)); f = 0.5 + 1.5*fbmNoise( vec2(2.5*f,0.0), 10 ); vec3 col = mix(vec3(0.0,0.25,0.6),vec3(1.0),f); 183
col *= 1.0 - 0.1*dot(p,p); gl_FragColor = vec4( col, 1.0 ); } Код на Питоне: #!/usr/bin/env python3 """ Пробел — пауза/продолжение анимации R — перезапустить время с нуля S — сохранить скриншот """ import os os.environ['JAVA_HOME'] = r'C:\Program Files\Java\jdk-26.0.1' import py5 from pathlib import Path WIN_W = 770 WIN_H = 770 shader t paused = None = 0.0 = False def setup(): global shader py5.size(WIN_W, WIN_H, py5.P2D) py5.frame_rate(60) py5.window_title("Warping - procedural 5") py5.rect_mode(py5.CORNER) path = Path(py5.sketch_path()) / "shaders" / "Warping-procedural5.glsl" shader = py5.load_shader(str(path)) def draw(): global t if not paused: # секунды, как iTime на Shadertoy t += 1.0 / 60.0 184
shader.set("resolution", float(WIN_W), float(WIN_H)) shader.set("time", t) py5.shader(shader) py5.rect(0, 0, WIN_W, WIN_H) py5.reset_shader() # Подпись py5.fill(255, 255, 255, 140) py5.no_stroke() py5.text_size(12) py5.text_align(py5.LEFT, py5.TOP) status = "⏸ пауза" if paused else f"t = {t:.1f} с" py5.text(status, 8, 8) def key_pressed(): global paused, t k = py5.key if k == ' ': paused = not paused elif k in ('r', 'R', 'к', 'К'): t = 0.0 elif k in ('s', 'S', 'ы', 'Ы'): py5.save_frame("shadertoy.png") py5.run_sketch() Отличные водные процедуры! 185
186
Как это всё функционирует? 187
Парадокс, но в Питоне всё функционирует без дополнительных средств типа LINQ в Си-шарпе. Хорошо ли это или просто здорово, мы и попытаемся разобраться на поучительных примерах. Чтобы далеко не ходить, мы возьмём проекты из этой книги: Все проекты в ней написаны на паскале, но функциональное программирование в целом следует LINQ. Первая задача называется Трёхзначное число и легко решается с помощью метода расширения для последовательностей Where. 188
Трёхзначное число В книге 600 задач на сообразительность, на странице 112 напечатана задача 41: Трёхзначное число Если от трёхзначного числа отнять 7, то оно разделится на 7; если отнять от него 8, то оно разделится на 8; если отнять от него 9, то оно разделится на 9. Какое это число? // решаем задачу: Range(100,999).Where(n -> ((n - 7) mod 7 = 0) and ((n - 8) mod 8 = 0) and ((n - 9) mod 9 = 0)) .Println; Искомое число – 504. Программа на Питоне отличается только в деталях: # ПРОВЕРЯЕМ УСЛОВИЕ: def check_number(n): return (n - 7) % 7 == 0 and (n - 8) % 8 == 0 and (n - 9) % 9 == 0 # решаем задачу: result = filter(check_number, range(100, 1000)) # печатаем ответ: print(f"Число = {list(result)[0]}") Функция check_number проверяет условие, как это сделано в методе Where. Функция filter применяет заданную функцию ко всем элементам итерируемого объекта (последовательность, коллекция) в указанном диапазоне. 189
filter(function, iterable) Функция range возвращает трёхзначные числа в диапазоне 100..999. Таким образом, функция check_number проверяет числа согласно условию и возвращает True, если условие выполняется. Все числа, успешно прошедшие проверку, добавляются к результату. Переменная result имеет тип object, который мы конвертируем в список и печатаем первый элемент. Ответ мы получим, естественно, тот же самый. Для укрепления и закрепления знания решаем вторую задачу: Пятизначное число Задача 440 из книги Математическая шкатулка [Нагибин88]: Мне было задано пятизначное число. К этому числу нужно было прибавить 200 000 и сумму умножить на 3. Вместо этого я приписал к заданному мне числу в конце его справа цифру 2 и получил правильный результат. Какое число было мне задано? // решаем задачу: Range(10000, 99999) .Where(i -> (i + 200000) * 3 = (i * 10 + 2)) .Println; Решаем задачу на Питоне, как и в предыдущем проекте, но в функции check_number записываем новое условие, а также передаём в функцию filter пятизначный диапазон: # ПРОВЕРЯЕМ УСЛОВИЕ: def check_number(i): 190
return (i + 200000) * 3 == (i * 10 + 2) # решаем задачу: result = filter(check_number, range(10000, 100000)) # печатаем ответ: print('Ответ: ', *result) Ответ на задачу: Ответ: 85714 При желании или без оного вы можете решить задачу и с генератором списка: # генератор списка: result = [i for i in range(10000, 100000) if check_number(i)] Здесь и тут логика более кудрявая, но число питоническая. Нужно привыкать… А тут подоспела и третья задача. Шестизначный перенос Задача 557 из книги Математическая шкатулка [Нагибин88], страница 94: Первая слева цифра шестизначного числа – 1. Если её перенести с первого места в конец числа, сохранив порядок остальных цифр, то вновь полученное число будет втрое больше первоначального. Восстановите первоначальное число. 191
// первая цифра числа: var start: integer -> integer := x -> x div 100000; // 5-значное число из последних 5 цифр числа x: var end5: integer -> integer := x -> x mod 100000; // новое 6-значное число: var newnum: integer -> integer := x -> end5(x) * 10 + start(x); var res:= Range(MIN6, MAX6) .Where(n -> newnum(n) = 3 * n) .Println; Ежели не придумывать новое решение, то программа на Питоне тут же готова: # константы: MIN6 = 100000 MAX6 = 999999 # ВОЗВРАЩАЕМ ПЕРВУЮ ЦИФРУ 6-ЗНАЧНОГО ЧИСЛА def start(x): return x // 100000 # ВОЗВРАЩАЕМ ПЯТИЗНАЧНОЕ ЧИСЛО из ПЯТИ ПОСЛЕДНИХ ЦИФР def end5(x): return x % 100000 # СОБИРАЕМ НОВОЕ 6-ЗНАЧНОЕ ЧИСЛО def newnum(x): return end5(x) * 10 + start(x) # ПРОВЕРЯЕМ УСЛОВИЕ: def check_number(n): return newnum(n) == 3 * n # решаем задачу: result = filter(check_number, range(MIN6, MAX6 + 1)) # печатаем ответ: print('Ответ: ', *result) 192
Решение верное и правильное: Ответ: 142857 285714 Идём дальше проторённой дорогой: Восстановите потерянную цифру Задача 7 из книги Удивительный мир чисел [КА86], страница 85: Числа вида Мр = 2р -1, где р - простое число, называются числами Мерсенна. При некоторых значениях р Мр - простое число. Так, первые одиннадцать простых чисел Мерсенна получаются при значениях р = 2, 3, 5, 7, 13, 17, 19, 31, 61, 89, 107. Двенадцатое простое число Мерсенна равно М127 = 2127 - 1. В его десятичной записи одна цифра стёрлась. Мы пока заменили её буквой х. Получилась такая запись: 170 141 183 460 469 231 хЗ1 687 303 715 884 105 727. Восстановите цифру, замещенную буквой х, если известно, что М127 + 3 делится на 13. Задача решается очень просто, если вспомнить признак делимости чисел на 13. Цифра, обозначенная буквой x, может принимать значения от 0 до 9. Мы их получим в методе Range: Range(0, 9) Для каждого значения х мы находим арифметическую сумму трёхзначных чисел – как и предписывает нам признак делимости на 13: 193
var summa: integer -> integer := x -> 170 - 141 + 183 - 460 + 469 - 231 + x * 100 + 31 - 687 + 303 - 715 + 884 - 105 + 727 + 3; К получившейся сумме нужно добавить тройку. Если при этом функция summa возвращает число, которое делится на 13, то задача решена: Where(n -> summa(n) mod 13 = 0) // решаем задачу: var x:= Range(0, 9) .Where(n -> summa(n) mod 13 = 0) .Println; Println; // печатаем искомое число: Console.WriteLine('Искомое число равно 170 141 183 460 469 231 {0}31 687 303 715 884 105 727', x.First); Применяем к Питону ловкость рук – и никакого мошенства: # константа: CONSTANT_SUM = 170 - 141 + 183 - 460 + 469 - 231 + 31 - 687 + 303 - 715 + 884 105 + 727 + 3 # ВОЗВРАЩАЕМ СУММУ def summa(x): return CONSTANT_SUM + x * 100 # # ПРОВЕРЯЕМ УСЛОВИЕ: def check_number(n): return summa(n) % 13 == 0 # решаем задачу: result = list(filter(check_number, range(0, 10))) # печатаем ответ: for x in result: print(f'Искомое число равно: 170 141 183 460 469 231 {x}31 687 303 715 884 105 727') 194
Икс равен семи: Искомое число равно: 170 141 183 460 469 231 731 687 303 715 884 105 727 Всезнающая статистика Задача 14 (16) из книги Удивительный мир чисел [КА86], страницы 86-87: Попал как-то мне в руки обрывок прошлогодней газеты. Моё внимание привлекло чернильное пятно, закрывшее последние три цифры шестизначного числа (Рис. 2.8). По сохранившемуся кусочку текста я вспомнил: это была заметка, в которой сообщалось, что к концу минувшего года население нашего города возросло до этого числа. В заметке говорилось также о том, что это шестизначное число уникально среди шестизначных: оно делится на 2, 3, 4, 6, 7, 8 и 9. Ого! Не правда ли? Чтобы восстановить все цифры этого числа, нет нужды обращаться в реставрационную лабораторию. Собственная сообразительность подскажет вам математический метод быстрого решения этой задачи. 195
const // мин. и макс. 6-значные числа: MIN6 = 566000; MAX6 = 566999; // решаем задачу: var res:= Range(MIN6, MAX6) .Where(num -> (num mod (num mod (num mod (num mod (num mod (num mod (num mod .Println; 2 3 4 6 7 8 9 = = = = = = = 0) and 0) and 0) and 0) and 0) and 0) and 0)) Для тех, кто уловил принцип решения задач с методом Where, тому не страшен серый волк: # константы: MIN6 = 566000 MAX6 = 566999 # ПРОВЕРЯЕМ УСЛОВИЕ: def check_number(num): return (num % 2 == 0 and num % 3 == 0 and num % 4 == 0 and num % 6 == 0 and num % 7 == 0 and num % 8 == 0 and num % 9 == 0) # решаем задачу: result = filter(check_number, range(MIN6, MAX6 + 1)) # печатаем ответ: print('Ответ: ', *result) Ответ единственно правильный: Ответ: 566496 196
Снимите маску с одной цифры 197
Задача 6 из книги Удивительный мир чисел [КА86], страница 84: В записи знаменитого «шахматного» числа М64= 1y446 744 073 709 551 615 на его вторую цифру накинута маска у. Сняв маску, расшифруйте значение у, зная, что достаточно увеличить заданное число на 3 единицы, как оно становится кратным числу 19. Примечание. По легенде именно такое число пшеничных зёрен следовало выдать в награду изобретателю шахмат, попросившему положить всего одно зерно на первую клетку шахматной доски, а на каждую следующую клетку вдвое большее число зёрен, чем на предыдущую. var num: decimal-> decimal := y -> (10 + y) * 1000000000000000000 + 446744073709551615 + 3; // решаем задачу: var y := Range(0, 9) .Where(n -> num(n) mod 19 = 0) .Println; Println; // печатаем искомое число: Console.WriteLine('Искомое число равно 1{0} 446 744 073 709 551 615', y.First); Перепись паскалевского кода на питоний закончена в срок: # константы: CONSTANT_PART = 446744073709551615 + 3 MULTIPLIER = 1000000000000000000 # ВЫЧИСЛЯЕТ И ВОЗВРАЩАЕТ ЧИСЛО def num(y): return (10 + y) * MULTIPLIER + CONSTANT_PART # ПРОВЕРЯЕМ УСЛОВИЕ: 198
def check_number(n): return num(n) % 19 == 0 # решаем задачу: result = list(filter(check_number, range(0, 10))) # печатаем ответ: for y in result: print(f'Искомое число равно 1{y} 446 744 073 709 551 615') Быстро находим, что y = 8, а искомое число равно 18 446 744 073 709 551 615 Число 117 649 Задача 3.6 из книги Удивительный мир чисел [КА86], страница 44: Число 117 649 существует одновременно в трёх качествах. Оно: • квадратное • кубическое • кратное семи: 117 649 = 3432 = 493 = 7k, k ∈ N. Более того, на отрезке от единицы до миллиона оно единственное с таким свойством. Докажите! const MIN = 7; MAX = 1000000000; 199
var res := Range(MIN, MAX, 7) .Where(num -> num.IsQuadrat and num.IsCube) .Println(); Println(); // печатаем ответ: Println; foreach var num: int64 in res do begin var s := 'num = ' + num.ToString(); Console.WriteLine(s); s := 'Квадратный корень = ' + Round(Power(num, 1.0 / 2.0)); Console.WriteLine(s); s := 'Кубический корень = ' + Round(Power(num, 1.0 / 3.0)); Console.WriteLine(s); Console.WriteLine(); end; Питону предстоит большой перебор: import math # константы: MIN = 7 MAX = 1000000000 # ПРОВЕРЯЕМ, ЯВЛЯЕТСЯ ЛИ ЧИСЛО ПОЛНЫМ КВАДРАТОМ def is_quadrat(num): root = int(math.sqrt(num)) return root * root == num # ПРОВЕРЯЕМ, ЯВЛЯЕТСЯ ЛИ ЧИСЛО ПОЛНЫМ КУБОМ def is_cube(num): root = round(num**(1/3)) return root * root * root == num # ПРОВЕРЯЕМ УСЛОВИЕ: def is_both_quadrat_and_cube(num): 200
return is_quadrat(num) and is_cube(num) # решаем задачу: result = list(filter(is_both_quadrat_and_cube, range(MIN, MAX + 1, 7))) # печатаем ответ: for num in result: print(f'num = {num}') print(f'Квадратный корень = {round(num**(1/2))}') print(f'Кубический корень = {round(num**(1/3))}') print() Здесь мы используем модуль math, чтобы извлекать квадратные корни, хотя можно обойтись и без него. Первое число с указанными в задаче свойствами, - 117 649, а второе – 7 529 536 – значительно больше 1 миллиона. А всего среди первого миллиарда чисел только 4 кратны 7 и являются полными квадратами и кубами: num = 117649 Квадратный корень = 343 Кубический корень = 49 num = 7529536 Квадратный корень = 2744 Кубический корень = 196 num = 85766121 Квадратный корень = 9261 Кубический корень = 441 num = 481890304 Квадратный корень = 21952 Кубический корень = 784 Если не требовать, чтобы числа были кратны семи, то можно найти ещё несколько любопытных чисел: 201
# решаем задачу: #result = list(filter(is_both_quadrat_and_cube, range(MIN, MAX + 1, 7))) result = list(filter(is_both_quadrat_and_cube, range(MIN, MAX + 1))) . . . num = 34012224 Квадратный корень = 5832 Кубический корень = 324 num = 47045881 Квадратный корень = 6859 Кубический корень = 361 num = 64000000 Квадратный корень = 8000 Кубический корень = 400 num = 85766121 Квадратный корень = 9261 Кубический корень = 441 Или в более наглядной форме: • • • 33752 = 2253 = 11390625 49132 = 2893 = 24137560 И так далее. 202
И «хвост», и «грива» 203
Задача 8 из книги Удивительный мир чисел [КА86], страница 72: Если есть четырёхзначные числа, первые две и последние две цифры каждого из которых совпадают соответственно с первыми двумя и последними двумя цифрами квадрата и куба искомого числа, то какое из них наименьшее и какое - наибольшее? Числа, оканчивающиеся единицей и двумя нулями, исключаем. В зашифрованном виде: xyzt2= xy...zt и xyzt3= xy...zt. В правой и левой частях этих равенств буквой х зашифрована одна и та же цифра, буквой у - также, буквой z - также и буквой t - также, но эти цифры могут быть и одинаковыми. function First2(num: int64) : int64; begin while (num >= 100) do num := num div 10; Result:= num; end; var First2Quadrat: int64 -> int64 := num -> First2(num*num); var First2Cube: int64 -> int64 := num -> First2(num*num*num); var Last2: integer -> integer := num -> num mod 100; var Last2Quadrat: integer -> integer := num -> num * num mod 100; var Last2Cube: int64 -> int64 := num -> num * num * num mod 100; Range(1000,9999).Where(n -> (First2(n) = First2Quadrat(n)) and (First2(n) = First2Cube(n)) and (Last2(n) = Last2Quadrat(n)) and (Last2(n) = Last2Cube(n))) .Println; И-го-го-готово! 204
# ВОЗВРАЩАЕТ ПЕРВЫЕ ДВЕ ЦИФРЫ def first2(num): while num >= 100: num //= 10 return num # ВОЗВРАЩАЮТ ПЕРВЫЕ ДВЕ ЦИФРЫ ИЗ КВАДРАТА И КУБА ЧИСЛА def first2_quadrat(num): return first2(num * num) def first2_cube(num): return first2(num * num * num) # ВОЗВРАЩАЕТ ПОСЛЕДНИЕ ДВЕ ЦИФРЫ def last2(num): return num % 100 def last2_quadrat(num): return (num * num) % 100 def last2_cube(num): return (num * num * num) % 100 # ПРОВЕРЯЕМ УСЛОВИЕ: def check_number(n): return (first2(n) == first2_quadrat(n) and first2(n) == first2_cube(n) and last2(n) == last2_quadrat(n) and last2(n) == last2_cube(n)) # решаем задачу: result = filter(check_number, range(1000, 10000)) # печатаем ответ: print(*result) Первые два числа нарушают условия задачи, поэтому остаются 2 последних числа. Первое из них – 1025 – наименьшее из возможных, а второе – 9976 – наибольшее: 1000 1001 1025 9976 205
Ж-Ж-Ж! 206
Задача 3 из книги Удивительный мир чисел [КА86], страница 71: // решаем задачу: var res:= Range(2,9) .Where(n -> (n-1)*(n-1)*(n-1)*(n-1)*(n-1) = n * 1000 + n * 100 + n * 10 + n - 1) .Println; Жужжим задачу по-питоньи: # ПРОВЕРЯЕМ УСЛОВИЕ: def check_number(n): return (n - 1)**5 == n * 1000 + n * 100 + n * 10 + n - 1 # решаем задачу: result = filter(check_number, range(2, 10)) # печатаем ответ: print(*result) Ж = 7, а полное решение такое: (7-1)5 = 7776 207
Девять в квадрате 208
Задача 1 из книги Удивительный мир чисел [КА86], страница 70: Найдите шестизначное число, зашифрованное в ребусе: ДЕВЯТЬ2=§§§§§§ДЕВЯТЬ // решаем задачу: var f: int64 -> boolean := n -> n * n mod 1000000 = n; var res := Range(102345, 987654) .Where(n -> f(n) = true) .Println(); Println(); // печатаем ответ: foreach var num: int64 in res do begin var s := 'num = ' + num.ToString; s += ' num2 = ' + (num * num).ToString(); Console.WriteLine(s); end; Беззаботно решаем задачу на Питоне: # ПРОВЕРЯЕМ УСЛОВИЕ: def f(n): return (n * n) % 1000000 == n # решаем задачу: result = list(filter(f, range(102345, 987655))) # печатаем ответ: print(*result) for num in result: s = f'num = {num} num² = {num * num}' print(s) Ответственное решение готово: 209
109376 890625 num = 109376 num² = 11963109376 num = 890625 num² = 793212890625 Пара чисел: 3149 и 3151 Задача 3.2 из книги Удивительный мир чисел [КА86], страница 43: Десятичная запись куба каждого из чисел 3149 и 3151 начинается двумя его первыми цифрами, а оканчивается двумя его последними цифрами: 31493 = 31 226 116 949 31513 = 31 285 651 951 Есть ещё два последовательных четырёхзначных числа, обладающих таким же свойством. Какие это числа? function First2(num: int64): int64; begin while (num >= 100) do num := num div 10; Result := num; end; var First2Cube: int64-> int64 := num -> First2(num * num * num); var Last2: integer-> integer := num -> num mod 100; var Last2Cube: int64-> int64 := num -> num * num * num mod 100; // решаем задачу: var nums := Range(1000, 9999) 210
.Where(n -> ((First2(n) = First2Cube(n)) and (Last2(n) = Last2Cube(n)))) .Println; // печатаем ответ: Println; foreach var num: int64 in nums do begin var s := 'num = ' + num.ToString; s += ' num3 = ' + (num * num * num).ToString(); Println(s); end; Переписываем на Питон и решаем с генератором: def First2(num: int) -> int: while num >= 100: num //= 10 return num First2Cube = lambda num: First2(num ** 3) Last2 = lambda num: num % 100 Last2Cube = lambda num: (num ** 3) % 100 # решаем задачу: nums = [n for n in range(1000, 10000) if First2(n) == First2Cube(n) and Last2(n) == Last2Cube(n)] # печатаем ответ: print(*nums) print() for num in nums: s = f'num = {num} num3 = {num ** 3}' print(s) И с функцией filter: def First2(num: int) -> int: while num >= 100: 211
num //= 10 return num First2Cube = lambda num: First2(num ** 3) Last2 = lambda num: num % 100 Last2Cube = lambda num: (num ** 3) % 100 # ПРОВЕРЯЕМ УСЛОВИЕ: def check_number(n): return First2(n) == First2Cube(n) and Last2(n) == Last2Cube(n) # решаем задачу: nums = list(filter(check_number, range(1000, 10000))) # печатаем ответ: print(*nums) print() for num in nums: s = f'num = {num} num3 = {num ** 3}' print(s) Вот полный список чисел, выполняющих условие задачи: 1000 1001 1024 1025 3149 3151 3200 3201 9975 9976 9999 num num num num num num num num num num num = = = = = = = = = = = 1000 1001 1024 1025 3149 3151 3200 3201 9975 9976 9999 num3 num3 num3 num3 num3 num3 num3 num3 num3 num3 num3 = = = = = = = = = = = 1000000000 1003003001 1073741824 1076890625 31226116949 31285651951 32768000000 32798729601 992518734375 992817266176 999700029999 212
В книге указаны только числа 3200 и 3201, но есть и другие пары чисел: 1000 и 1001 1024 и 1025 9975 и 9976 Трёхзначное число filter Задача 3 из книги Удивительный мир чисел [КА86], страница 63: Найдите трёхзначное число, обладающее следующими свойствами: • число десятков на 4 меньше числа единиц, но на 4 больше числа сотен; • если цифры этого числа разместить в обратном порядке, то новое полученное число будет на 792 больше искомого. // решаем задачу --> // число единиц: var e: integer-> integer := n -> n mod 10; // число десятков: var d: integer-> integer := n -> n div 10 mod 10; // число сотен: var s: integer-> integer := n -> n div 100 mod 10; var res:= Range(100, 999) .Where(n -> d(n) = e(n) - 4) .Where(n -> d(n) = s(n) + 4) .Println; // печатаем ответ: Println; var str := 'Число = ' + res.First; Console.WriteLine(str); Console.WriteLine(); Питонизируем код: 213
# e # d # s число единиц: = lambda n: n % 10 число десятков: = lambda n: n // 10 % 10 число сотен: = lambda n: n // 100 % 10 # ПРОВЕРЯЕМ УСЛОВИЕ: def check_num(n): return d(n) == e(n) - 4 == s(n) + 4 # решаем задачу: res = filter(check_num, range(100, 1000)) # печатаем ответ: print('Число =',*res) print() А ответ тут как тут: Число = 159 Таких чисел только два Задача 1 из книги Удивительный мир чисел [КА86], страница 63: Есть только два двузначных числа, каждое из которых равно неполному квадрату разности своих цифр. Найдите эти числа. Чтобы облегчить решение, подскажем, что одно число на 11 больше другого. 214
// решаем задачу --> // число единиц: var e: integer-> integer := n -> n mod 10; // число десятков: var d: integer-> integer := n -> n div 10 mod 10; var res:= Range(10, 99) .Where(n -> e(n) * e(n) + d(n) * d(n) - e(n) * d(n) .Println; = n) // печатаем ответ: Println; foreach var n in res do begin var str := 'Число = ' + n; Console.WriteLine(str); Console.WriteLine(); end; Питон не подкачал: # e # d число единиц: = lambda n: n % 10 число десятков: = lambda n: n // 10 % 10 # ПРОВЕРЯЕМ УСЛОВИЕ: def check_num(n): return e(n)*e(n) + d(n)*d(n) - e(n)*d(n) == n # решаем задачу: res = list(filter(check_num, range(10, 100))) print(*res) print() # печатаем ответ: for n in res: print(f'Число = {n}') print() 215
Задача решена: 37 48 Число = 37 Число = 48 Ещё два числа Задача 2 из книги Удивительный мир чисел [КА86], страница 63: Сходным свойством обладают ещё два двузначных числа: каждое равно неполному квадрату суммы своих цифр. Найдите эти числа, зная, что одно число на 50 больше другого. // число единиц: var e: integer-> integer := n -> n mod 10; // число десятков: var d: integer-> integer := n -> n div 10 mod 10; var res:= Range(10, 99) .Where(n -> e(n) * e(n) + d(n) * d(n) + e(n) * d(n) .Println; = n) // печатаем ответ: Println; foreach var n in res do begin var str := 'Число = ' + n; Console.WriteLine(str); end; Console.WriteLine(); 216
Эта задача решается точно так же, как предыдущая. Только в функции check_num нужно исправить один знак: # e # d число единиц: = lambda n: n % 10 число десятков: = lambda n: n // 10 % 10 # ПРОВЕРЯЕМ УСЛОВИЕ: def check_num(n): return e(n)*e(n) + d(n)*d(n) + e(n)*d(n) == n # решаем задачу: res = list(filter(check_num, range(10, 100))) print(*res) print() # печатаем ответ: for n in res: print(f'Число = {n}') print() Правда, чисел оказалось не два, а три: 13 63 91 Число = 13 Число = 63 Число = 91 Итак, метод расширения LINQ можно заменить функцией filter, которой следует передать функцию для проверки условия и диапазон чисел для проверки. 217
Любопытное свойство чисел Задача 15 (16 – номер задачи в издании 1996 года) из книги Удивительный мир чисел [КА86], страница 102: Возьмите какое-либо б-значное число, делящееся на 7, например 325 836. Перенесите последнюю цифру в начало записи числа. Образуется новое число 632 583. Оно также делится на 7. Докажите самостоятельно, что таким свойством обладает любое 6-значное число, делящееся на 7. Рекомендация. Представьте заданное число так: 7k = = 10а+ 6 (1). Тогда новое число примет вид 100 0006 +а (2). Используя (1), докажите делимость (2) на число 7. // последняя цифра числа: var endn: integer-> integer := n -> n mod 10; // 5-значное число из первых 5 цифр числа num: var start5: integer-> integer := n -> n div 10; // новое 6-значное число: var newnum: integer-> integer := n -> endn(n) * 100000 + start5(n); var res := Range(999999, 100000) .Where(n -> n mod 7 = 0) .Where(n -> newnum(n) mod 7 <> 0) .Count; // все числа проверены: if (res = 0) then Console.WriteLine('Утверждение верное') else Console.WriteLine('Утверждение неверное'); Метод расширения Count без параметров возвращает число элементов в последовательности. В Питоне это может сделать функция len: 218
# последняя цифра числа: endn = lambda n: n % 10 # 5-значное число из первых 5 цифр числа num: start5 = lambda n: n // 10 # новое 6-значное число: newnum = lambda n: endn(n) * 100000 + start5(n) # ПРОВЕРЯЕМ УСЛОВИЕ: def check_num(n): return n % 7 == 0 and newnum(n) % 7 != 0 # решаем задачу: nums = range(100000, 1000000) res = len(list(filter(check_num, nums))) # все числа проверены: if res == 0: print('Утверждение верное') else: print('Утверждение неверное') Как и следовало ожидать: Утверждение верное Как определил ошибку Чохбилмиш? Задача 23 (25) из книги Удивительный мир чисел [КА86], страница 57: Чохбилмиш предложил каждому из двух учеников задумать какое-либо шестизначное число и переставить первую цифру в конец записи числа. Одному 219
сказал: «Найди сумму получившихся чисел». Другому сказал: «Найди разность». Ученики выполнили действия и написали: 913 485 и 167 860. Чохбилмиш не знал, какие числа были задуманы учениками, но сразу определил: «Вы оба ошиблись». Как рассуждал Чохбилмиш? // последняя цифра числа: var endn: integer-> integer := n -> n mod 10; // 5-значное число из первых 5 цифр числа num: var start5: integer-> integer := n -> n div 10; // новое 6-значное число: var newnum: integer-> integer := n -> endn(n) * 100000 + start5(n); // сумма: var sum: integer-> integer := n -> n + newnum(n); // разность: var razn: integer-> integer := n -> n - newnum(n); var res := Range(100000, 999999) .Where(n -> (sum(n) mod 11 <> 0) or (razn(n) mod 9 <> 0)) .Count; // все числа проверены: if (res = 0) then Console.WriteLine('Утверждения верные') else Console.WriteLine('Утверждения неверные'); Console.WriteLine(); Аккуратно и бережно переписываем паскалевскую программу на Питон: 220
# последняя цифра числа: endn = lambda n: n % 10 # 5-значное число из первых 5 цифр числа num: start5 = lambda n: n // 10 # новое 6-значное число: newnum = lambda n: endn(n) * 100000 + start5(n) # сумма: summa = lambda n: n + newnum(n) # разность razn = lambda n: n - newnum(n) # ПРОВЕРЯЕМ УСЛОВИЕ: def check_num(n): return (summa(n) % 11 != 0) or (razn(n) % 9 != 0) # решаем задачу res = len(list(filter(check_num, range(100000, 1000000)))) # все числа проверены if res == 0: print('Утверждения верные') else: print('Утверждения неверные') print() Оба утверждения верные: Утверждения верные 221
Гаусс Задача 5 из книги Математическая шкатулка [Нагибин88], страница 15: Рассказывают, что в начальной школе, где учился мальчик Карл Гаусс, ставший потом знаменитым математиком, учитель, чтобы занять класс на продолжительное время самостоятельной работой, дал детям такое задание - вычислить сумму всех натуральных чисел от 1 до 100. Но маленький Гаусс это задание моментально выполнил. Попробуй и ты быстро выполнить это задание. // решаем задачу: var summa := Range(1,100).Sum; Console.WriteLine('Сумма ряда равна {0}', summa); Метод расширения Sum без параметров возвращает сумму элементов последовательности. В Питоне его заменяет функция с тем же названием: summa = sum(range(1, 101)) print(f'Сумма ряда равна {summa}') Гауссиана закончилась быстро и успешно: Сумма ряда равна 5050 Задача 7.1 из книги Математическая шкатулка: Как быстро вычислить сумму 1 + 3+ 5+7 + 9 + ... + 997 +999? 222
Добавьте новую строку: #summa = sum(range(1, 101)) summa = sum(range(1, 1000, 2)) print(f'Сумма ряда равна {summa}') Ответ всегда готов: Сумма ряда равна 250000 Задача 838 из книги Математическая шкатулка: Вычислите: 5 + 10 + 15+20 + 25 + ... + 100. Решаем: #summa = sum(range(1, 101)) #summa = sum(range(1, 1000, 2)) summa = sum(range(5, 101, 5)) print(f'Сумма ряда равна {summa}') Готово! Сумма ряда равна 1050 «Избранные» числа Задача 12 из книги Удивительный мир чисел [КА86], страница 65: 223
Есть 80 четырёхзначных и 800 пятизначных чисел, не оканчивающихся нулем, и таких, что если от любого из них вычтем 999 в случае четырёхзначного числа и 9999 в случае пятизначного числа, то всякий раз получим обращённое число, т. е. записанное теми же цифрами, но в обратном порядке. Какие это числа? Найдите способ быстрого вычисления суммы этих чисел (без привлечения компьютера). const MIN = 1001; MAX = 9999; MINUS = 999; // решаем задачу: var res:= Range(MIN, MAX) .Where(num -> num mod 10 <> 0) .Where(num -> num - MINUS = num.ReverseNum) .Println; Console.WriteLine('Всего чисел: ' + res.Count); Console.WriteLine('Сумма чисел = ' + res.Sum); // печатаем ответ: Console.WriteLine(); foreach var num in res do begin var s := 'num = ' + num.ToString(); s += ' > ' + num.ReverseNum.ToString(); Console.WriteLine(s); end; Console.WriteLine(); Питонизируем паскаль: # константы: MIN = 1001 MAX = 9999 MINUS = 999 224
# ВОЗВРАЩАЕМ ПЕРЕВЁРНУТОЕ ЧИСЛО: reverse_num = lambda n: int(str(n)[::-1]) # ПРОВЕРЯЕМ УСЛОВИЕ: def check_num(n): return n % 10 != 0 and n - MINUS == reverse_num(n) # решаем задачу: res = list(filter(check_num, range(MIN, MAX + 1))) # печатаем ответ: print(*res) print() print('Всего чисел:', len(res)) print('Сумма чисел =', sum(res)) print() for num in res: print(f'num = {num} > {reverse_num(num)}') print() Для переворота числа в функции reverse_num мы конвертируем его в строку, строку переворачиваем [::-1] и снова превращаем в целое число. Функции len и sum в Питоне заменяют методы расширения Count и Sum в LINQ. Всего чисел: 80 Сумма чисел = 479960 num num num . . num num = = = . = = 2001 > 1002 2111 > 1112 2221 > 1222 9668 > 8669 9778 > 8779 225
num = 9888 > 8889 num = 9998 > 8999 Для поиска 5-значных чисел изменяем значения констант: # # # # константы: MIN = 1001 MAX = 9999 MINUS = 999 MIN = 10001; MAX = 99999; MINUS = 9999; const MIN = 10001; MAX = 99999; MINUS = 9999; 5-значных чисел ровно в 10 раз больше, чем 4-значных: Всего чисел: 800 Сумма чисел = 47999600 num num num num . . num num num num num = = = = . = = = = = 20001 20101 20201 20301 > > > > 10002 10102 10202 10302 99598 99698 99798 99898 99998 > > > > > 89599 89699 89799 89899 89999 226
Проект Эйлер. Задача 1 Задача номер 1 (Problem 1) называется Multiples of 3 and 5: If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below 1000. Найдите сумму всех натуральных чисел, которые: • меньше 1000 • делятся без остатка на 3 или на 5 В книге на паскале такой задачи нет, поэтому добавляем от себя: # генерируем последовательность чисел: numbers = range(1, 1000) # ПРОВЕРЯЕМ УСЛОВИЕ: def check(n): return n % 3 == 0 or n % 5 == 0 # решаем задачу: summa = sum(filter(check, numbers)) # печатаем ответ: print(f'Сумма = {summa}') Но ответ всё равно верный: Сумма = 233168 227
Назойливый остаток 228
В книге Бориса Кордемского и Аскера Ахадова Удивительный мир чисел [КА86] вы найдёте немало интересных задач, в том числе и на делимость. На странице 86 авторы предлагают решить задачу Назойливый остаток: Некоторые числа, кратные числу 7, при делении на 2, на 3, на 4, на 5 и на 6 дают остаток 1. Найдите наименьшее из таких чисел. Эта же задача напечатана в книге Фёдора Нагибина и Евгения Канина Математическая шкатулка [Нагибин88], задача 42, страницы 18-19, но в более занимательной форме: Колхозница привезла на рынок для продажи корзину яиц. Продавала она их по одной и той же цене. После продажи яиц колхозница пожелала проверить, верно ли она получала деньги. Но вот беда: она забыла, сколько у неё было яиц. Вспомнила она только, что когда перекладывала яйца по 2, то оставалось одно яйцо; одно яйцо оставалось также при перекладывании яиц по 3, по 4, по 5, по 6. Когда же она перекладывала яйца по 7, то не оставалось ни одного. Помоги колхознице сообразить, сколько у неё было яиц. Во Франции бытует подобная задач (Математический фольклор, Задача Д17): Женщина несла на базар две корзины яиц. Прохожий случайно толкнул её, корзинки упали и яйца разбились. Виновник извинился, предложил возместить ущерб и спросил: - Сколько яиц было в корзинах? - Не помню точно, - ответила женщина, - но знаю, когда вынимала их по 2, по 3, по 4, по 5 или по 6 яиц, то в корзине оставалось по 1 яйцу, а когда вынимала по 7, то не оставалось ни одного. Сколько яиц было в корзинах? 229
Популярна яичная тема и в Болгарии (Математический фольклор, Задача 87): Женщина несла на базар две корзины яиц. Её нечаянно толкнул парень, корзины упали, а яйца разбились. Парень, чтобы заплатить, спросил, сколько было всего яиц. - Я их не считала, но когда складывала в корзины по 2, по 3, по 4, по 5, по 6, то всякий раз оставалось по 1 яйцу, а когда складывала по 7 - не оставалось ни одного. Сколько яиц было в корзинах? // решаем задачу: var res:= 1.Step() .Where(n -> (n (n (n (n (n (n .First; mod mod mod mod mod mod 7 2 3 4 5 6 = = = = = = 0) and 1) and 1) and 1) and 1) and 1)) // печатаем ответ: Console.WriteLine('Искомое число равно ' + res); Println(NewLine); У Питона нет метода расширения Step, поэтому пишем аналогичную функцию: # ГЕНЕРИРУЕМ ПОСЛЕДОВАТЕЛЬНОСТЬ def step(num = 1): while True: yield num num += 1 # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(num): return (num % 7 == 0 and 230
num num num num num % % % % % 2 3 4 5 6 == == == == == 1 and 1 and 1 and 1 and 1) Функция next возвращает следующий элемент последовательности. В данном случае – первый: # решаем задачу: res = next(filter(check_condition, step())) # печатаем ответ: print(f'Искомое число равно {res}\n') Запускаем программу – и все яйца сосчитаны: Искомое число равно 301 Давайте найдём другие числа, которые имеют назойливый остаток: Range(1, MAX).Where(n -> (n mod (n mod (n mod (n mod (n mod (n mod .Println(NewLine); 7 2 3 4 5 6 = = = = = = 0) and 1) and 1) and 1) and 1) and 1)) Здесь мы должны разумно ограничить последовательность: # макс. число: MAX = 10000 + 1; 231
# ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(num): return (num % 7 == 0 and num % 2 == 1 and num % 3 == 1 and num % 4 == 1 and num % 5 == 1 and num % 6 == 1) # решаем задачу: res = list(filter(check_condition, range(MAX))) # печатаем ответ: print(*res, sep='\n', end='\n') Среди первой десятки тысяч натуральных чисел довольно много подходящих под условие задачи: 301 721 1141 1561 1981 2401 2821 3241 3661 4081 4501 4921 5341 5761 6181 6601 7021 7441 7861 8281 8701 232
9121 9541 9961 Шестизначное число Задача 4 из книги Удивительный мир чисел [КА86], страница 44: Ученику понадобилось написать наибольшее из шестизначных чисел, кратных 11 и чтобы цифра 6 была первой слева. Как надо действовать ученику для быстрого выполнения задания, если признаков делимости на 11 он ещё не знает? Сообщим, что искомое число обладает забавной особенностью: если каждую его цифру повернуть на 180° в плоскости бумаги, оставляя её на прежнем месте, то образовавшееся число окажется дважды кратным 11 (делится на 11 и частное также делится на 11). Выявите ещё одну особенность чисел: найденного и с повёрнутыми цифрами. // решаем задачу: var res:= Range(699999, 600000, -1) .Where(n -> n mod 11 = 0) .First; var s := 'Наибольшее число равно ' + res.ToString; Console.WriteLine(s); Console.WriteLine(); С нашим багажом знаний задача решается однозначно: # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(n): return n % 11 == 0 233
# генерируем последовательность в обратном порядке: sequence = range(699999, 599999, -1) # решаем задачу: res = next(filter(check_condition, sequence)) # печатаем ответ: print(f'Наибольшее число равно {res}') print() А вот и ответ: Наибольшее число равно 699996 По условию задачи, переворачиваем все цифры найденного числа: 699996 → 966669 Легко проверить, что перевёрнутое число делится на 121: А ещё одной особенностью этих чисел является их палиндромичность – они одинаково читаются и в прямом, и в обратном направлении. 234
И такие есть числа 235
Задача 4-1 из книги Удивительный мир чисел [КА86], страница 63: Какое двузначное число в 19 раз больше числа его единиц? var res:= Range(10,99).Where(n -> n = n mod 10*19).Println.First; var s := 'Число = ' + res.ToString; Console.WriteLine(s); Задача решается легко и непринуждённо: # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(n): return n == (n % 10) * 19 # решаем задачу: res = next(filter(check_condition, range(10, 100))) # печатаем ответ: print(f'Число = {res}') Задача имеет единственное решение: Число = 95 236
Три велосипедиста 237
Задача 16 из книги Удивительный мир чисел [КА86], страница 55: Три велосипедиста начали с общего старта движение по круговой дорожке. Первый делает полный круг за 21 мин, второй - за 35 мин, а третий - за 15 мин. Через сколько минут они ещё раз окажутся вместе в начальном пункте? // решаем задачу: var t := 1.Step() .Where(n -> (n mod 21 = 0) and (n mod 35 = 0) and (n mod 15 = 0)) .First; // печатаем ответ: Println; Console.WriteLine('Велосипедисты встретятся через {0} мин.', t); Решаем задачу аналогично назойливому остатку: # ГЕНЕРИРУЕМ ПОСЛЕДОВАТЕЛЬНОСТЬ def step(num = 1): while True: yield num num += 1 # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(num): return (num % 21 == 0 and num % 35 == 0 and num % 15 == 0) # решаем задачу: res = next(filter(check_condition, step())) # печатаем ответ: print(f'Велосипедисты встретятся через {res} мин.') 238
Время встречи изменить нельзя: Велосипедисты встретятся через 105 мин. Пятизначный куб Задача 556 из книги Математическая шкатулка [Нагибин88]: Задумано пятизначное число, являющееся кубом натурального числа. Восстановите задуманное число, если известно, что оно должно делиться на 3 и последняя цифра его 6. // минимальное число: var minn:= Trunc(Power(10000, 1/3)); // максимальное число: var maxn:= Trunc(Power(99999, 1/3)); // решаем задачу: var res:= Range(minn, maxn) .Where(n -> (n*n*n mod 3 = 0) and (n*n*n mod 10 = 6)) .ElementAt(0); // печатаем ответ: Println( res, 'в кубе = ', (res*res*res)); Console.WriteLine(); Метод расширения ElementAt(0) заменяем той же функцией next: import math # выычисляем минимальное и максимальное значения: 239
minn = math.ceil(10000**(1/3)) maxn = math.ceil(math.cbrt(99999)) # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(n): cube = n**3 return (cube % 3 == 0) and (cube % 10 == 6) # генерируем последовательность: sequence = range(minn, maxn + 1) # решаем задачу: res = next(filter(check_condition, sequence)) # печатаем ответ: print(f'{res} в кубе = {res**3}') print() Было задумано число 46 656: 36 в кубе = 46656 Здесь условие можно существенно упростить. Последняя цифра куба равна 6 только тогда, когда последняя цифра самого числа равна 6. Поэтому среди чисел 22..46 достаточно проверить 26, 36, 46. Из них только 36 делится на 3, откуда сразу получаем ответ. Репетитор Этот познавательный процесс хорошо описан Антоном Павловичем Чеховым в рассказе Репетитор. Теперь по арифметике... Берите доску. Какая следующая задача? 240
Петя плюёт на доску и стирает рукавом. Учитель берёт задачник и диктует: - «Купец купил 138 арш. чёрного и синего сукна за 540 руб. Спрашивается, сколько аршин купил он того и другого, если синее стоило 5 руб. за аршин, а чёрное 3 руб.?» - И без алгебры решить можно, - говорит Удодов, протягивая руку к счётам и вздыхая. - Вот, извольте видеть... Он щёлкает на счётах, и у него получается 75 и 63, что и нужно было. const // общая длина сукна в аршинах: LENGTH: integer = 138; // общая стоимость сукна в рублях: COST: integer = 540; // макс. длина синего сукна: var maxBlue := COST div 5; // решаем задачу: var blue := Range(0, maxBlue) .Where(n -> n * 5 + (LENGTH - n) * 3 = COST) .ElementAt(0); Writeln('Синего сукна (аршин): ', blue); Writeln('Чёрного сукна (аршин): ' + (LENGTH - blue)); Console.WriteLine(); Перекраиваем паскалевский код: # общая длина сукна в аршинах: LENGTH = 138 # общая стоимость сукна в рублях: COST = 540 241
# макс. длина синего сукна: max_blue = COST // 5 # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(num): return num * 5 + (LENGTH - num) * 3 == COST # решаем задачу: blue = next(filter(check_condition, range(max_blue))) print('Синего сукна (аршин): ', blue) print('Чёрного сукна (аршин):', LENGTH - blue) Запускаем программу и получаем ответ: Синего сукна (аршин): 63 Чёрного сукна (аршин): 75 Американское наследство Задача Д39 из книги Математический фольклор: Отец оставил сыновьям Чарлзу и Роберту 100 долларов. Если одну треть части Чарлза вычесть из одной четверти части Роберта, то останется 11 долларов. Сколько долларов получил каждый из братьев? // решаем задачу: var Charles := Range(0, 100) .Where(n -> (100 - n) div 4 - n div 3 = 11) .ElementAt(0); Writeln(' Чарлз получил: ', Charles); 242
Writeln(' Роберт получил: ' + (100 - Charles)); Console.WriteLine(); Вот как Питон поделил наследство: # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(n): return (100 - n) // 4 - n // 3 == 11 # решаем задачу: Charles = next(filter(check_condition, range(0, 100+1))) # печатаем ответ: print('Чарлз получил: ', Charles) print('Роберт получил:', 100 - Charles) Не придерёшься и не подкопаешься: Чарлз получил: 24 Роберт получил: 76 Четыре числа Задача 341 из книги Математическая шкатулка [Нагибин88]: Произведение четырёх последовательных натуральных чисел равно 3024. Найдите эти числа. // решаем задачу: 243
var m := 1; var n := m.Step() .Where(i -> i * (i + 1) * (i + 2) * (i + 3) = 3024) .ElementAt(0); // печатаем ответ: Println(n, (n + 1), (n + 2), (n + 3)); Console.WriteLine(); Здесь мы опять встречаемся с назойливым остатком в несколько ином обличии: # ГЕНЕРИРУЕМ ПОСЛЕДОВАТЕЛЬНОСТЬ def step(num = 1): while True: yield num num += 1 # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(n): return n * (n + 1) * (n + 2) * (n + 3) == 3024 # решаем задачу: n = next(filter(check_condition, step())) # печатаем ответ: print(n, (n + 1), (n + 2), (n + 3)) В произведении участвуют числа 6, 7, 8 и 9: 6 7 8 9 244
Разность кубов 245
Задача 561 из книги Математическая шкатулка [Нагибин88]: Разность кубов двух последовательных натуральных чисел равна 331. Восстановите эти числа. // решаем задачу: var m:= 1; var res:= m.Step() .Where(n -> (n+1)*(n+1)*(n+1) - n*n*n = 331) .ElementAt(0); // печатаем ответ: Println( res, 'в кубе = ', (res*res*res)); res += 1; Println(res, 'в кубе = ', (res*res*res)); Console.WriteLine(); Действуем по назойливому шаблону: # ГЕНЕРИРУЕМ ПОСЛЕДОВАТЕЛЬНОСТЬ def step(num = 1): while True: yield num num += 1 # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(n): return (n + 1) ** 3 - n ** 3 == 331 # решаем задачу: res = next(filter(check_condition, step())) # печатаем ответ: print(res, 'в кубе = ', res ** 3) res += 1 print(res, 'в кубе = ', res ** 3); print() 246
Простой ответ на простую задачу: 10 в кубе = 11 в кубе = 1000 1331 Двузначное число Задача 563 из книги Математическая шкатулка [Нагибин88]: Найдите двузначное положительное число, равное произведению суммы и разности его цифр. // функции для вычисления разности и суммы цифр // заданного числа: var sub: integer -> integer := x -> Abs(x div 10 - x mod 10); var sum: integer -> integer := x -> x div 10 + x mod 10; // находим число: var res:= Range(10,99) .Where(n -> sum(n)*sub(n) = n) .Println .ElementAt(0); // печатаем ответ: Console.WriteLine('{0} = {1} * {2}', res, sum(res), sub(res)); А в этом проекте мы обошлись даже без назойливости: # ВОЗВРАЩАЕМ АБСОЛЮТНУЮ РАЗНОСТЬ ЦИФР ЧИСЛА def sub(x): return abs(x // 10 - x % 10) # ВОЗВРАЩАЕМ СУММУ ЦИФР ЧИСЛА def sum_digits(x): return x // 10 + x % 10 247
# ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(n): return sum_digits(n) * sub(n) == n # решаем задачу: sequence = range(10, 100) res = next(filter(check_condition, sequence)) # печатаем ответ: print(f'{res} = {sum_digits(res)} * {sub(res)}') Решение единственное: двузначное число равно 48. Сумма его цифр - 12, а разность – 4: 48 = 12 * 4 Квадратный куб Задача 584 из книги Математическая шкатулка [Нагибин88]: Какое трёхзначное число равно кубу цифр его единиц, а также квадрату числа, составленного из второй и первой цифр? var ed: integer -> integer := x -> x mod 10; var cube: integer -> integer := x -> x * x * x; var n21: integer -> integer := x -> (x div 10 mod 10) * 10 + x div 100; var quad: integer -> integer := x -> x * x; var res:= Range(100,999) .Where(n -> (cube(ed(n)) = n) and (quad(n21(n)) = n)) .Println .ElementAt(0); // печатаем ответ: 248
Console.WriteLine(); Console.WriteLine('Число = {0}', res); Console.WriteLine('{0} в кубе = {1}', ed(res), res); Console.WriteLine('{0} в квадрате = {1}', n21(res), res); Изрядно поднаторев в решении такого рода задач, мы тут же накодили новую программу: # ВОЗВРАЩАЕТ ПОСЛЕДНЮЮ ЦИФРУ ЧИСЛА def ed(x): return x % 10 # ВОЗВРАЩАЕТ КУБ ЧИСЛА def cube(x): return x * x * x # ВОЗВРАЩАЕТ ЧИСЛО ИЗ ДЕСЯТКОВ И СОТЕН def n21(x): return (x // 10 % 10) * 10 + x // 100 # ВОЗВРАЩАЕТ КВАДРАТ ЧИСЛА def quad(x): return x * x # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(n): return cube(ed(n)) == n and quad(n21(n)) == n # решаем задачу: sequence = range(100, 1000) res = next(filter(check_condition, sequence)) # печатаем ответ: print() print(f'Число = {res}') print(f'{ed(res)} в кубе = {res}') print(f'{n21(res)} в квадрате = {res}') Задача имеет единственное решение: 249
729 = 93 = 272 Число = 729 9 в кубе = 729 27 в квадрате = 729 Зачёркнутая цифра Задача 600 из книги Математическая шкатулка [Нагибин88]: В трёхзначном числе зачеркнули цифру сотен, затем полученное двузначное число умножили на 7 и получили вновь исходное трёхзначное число. Какое это число? // решаем задачу: var res:= Range(100,999) .Where(n -> n mod 100 * 7 = n) .Println .ElementAt(0); // печатаем ответ: Console.WriteLine(); Console.WriteLine('Число {0} = {1} * 7', res, res mod 100); Console.WriteLine(); Задача решается элементарно просто: # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(n): return n % 100 * 7 == n 250
# решаем задачу: sequence = range(100, 1000) res = next(filter(check_condition, sequence)) # печатаем ответ: print(f'Число {res} = {res % 100} * 7') print() И вот оно, наше произведение! Число 350 = 50 * 7 Зачёркнутая цифра 2 Задача 587 из книги Математическая шкатулка [Нагибин88]: В трёхзначном числе зачеркнули среднюю цифру. Полученное двузначное число оказалось в 6 раз меньше исходного трёхзначного. Какое такое трёхзначное число. // двузначное число: var n2: integer -> integer := x -> x div 100 * 10 + x mod 10; var res:= Range(100,999) .Where(n -> n = n2(n) * 6) .Println .ElementAt(0); // печатаем ответ: Console.WriteLine(); Console.WriteLine('Число {0} = {1} * 6', res, n2(res)); Console.WriteLine(); 251
Переделываем с уже однажды переделанного: # ВОЗВРАЩАЕМ ДВУЗНАЧНОЕ ЧИСЛО def n2(x): return x // 100 * 10 + x % 10 # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(n): return n == n2(n) * 6 # решаем задачу: sequence = range(100, 1000) res = next(filter(check_condition, sequence)) # печатаем ответ: print(f'Число {res} = {n2(res)} * 6') print() Вот и вся задача: Число 108 = 18 * 6 Индийские обезьяны 2 Задача Д12 из книги Математический фольклор: Квадрат восьмой части стада обезьян играют в роще, а остальные 12 – на горке. Сколько обезьян в стаде? var m := 0; var f: integer -> boolean := Print(' Обезьян было: '); n -> n - (n div 8) * (n div 8) = 12; 252
m.Step(8) .Where(f) .Take(2) .Println; Console.WriteLine(); Задача несложная для решения, но нам нужно взять не одно, а два решения. Метод расширения Take возвращает заданное число значений и останавливает дальнейшую генерацию последовательности. В Питоне мы можем использовать для этих целей функцию islice из модуля itertools. Также мы должны усовершенствовать генератор последовательности step, чтобы генерировать числа с заданным шагом: import itertools # ГЕНЕРИРУЕМ ПОСЛЕДОВАТЕЛЬНОСТЬ def step(num = 1, step = 1): while True: yield num num += step # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(n): return n - (n // 8) * (n // 8) == 12 # решаем задачу: sequence = step(0, 8) res = itertools.islice(filter(check_condition, sequence), 2) # печатаем ответ: print(f'Обезьян было:', *res) print() Два ответа на одну задачу: Обезьян было: 16 48 253
Четырёхзначное число 254
Задача 620 из книги Математическая шкатулка [Нагибин88]: Найдите нечётное четырёхзначное число, две средние цифры которого образуют число в пять раз больше числа тысяч и втрое больше числа единиц этого числа. // две средние цифры: var n23: integer -> integer := x -> x div 10 mod 100; // число тысяч: var n1: integer -> integer := x -> x div 1000; // число единиц: var n4: integer -> integer := x -> x mod 10; var res:= Range(1001,9999,2) .Where(n -> (n23(n) = n1(n) * 5) and (n23(n) = n4(n) * 3)) .Println .ElementAt(0); // печатаем ответ: Console.WriteLine(); Console.WriteLine('Число = {0}', res); Console.WriteLine(); Переделка кода прошла быстро и без жертв: # ВОЗВРАЩАЕТ ДВЕ СРЕДНИЕ ЦИФРЫ ЧИСЛА def n23(x): return (x // 10) % 100 # ВОЗВРАЩАЕТ ЧИСЛО ТЫСЯЧ В ЧИСЛЕ def n1(x): return x // 1000 # ВОЗВРАЩАЕТ ЧИСЛО ЕДИНИЦ В ЧИСЛЕ def n4(x): return x % 10 # ПРОВЕРЯЕМ УСЛОВИЕ: 255
def check_condition(n): return n23(n) == n1(n) * 5 and n23(n) == n4(n) * 3 # решаем задачу: sequence = range(1001, 10000, 2) res = next(filter(check_condition, sequence)) # печатаем ответ: print() print(f'Число = {res}') print() Ответ готов: Число = 3155 Жизнь Демохара Задача Д9 из книги Математический фольклор: Демохар четверть жизни прожил мальчиком, пятую часть – юношей, одну треть – зрелым мужчиной и 13 лет пожилым. Сколько лет прожил Демохар? // решаем задачу var v := 0.0; var f: double -> boolean := Print(' Демохар прожил: '); v.Step(1) .Where(f) .Take(1) .Println; n -> n / 4 + n / 5 + n / 3 + 13 = n; 256
Console.WriteLine(); Метод расширения Take(1) возвращает нас вспять – к функции next, но мы – исключительно для тренировки и подражания – снова используем функцию islice: import itertools # ГЕНЕРИРУЕМ ПОСЛЕДОВАТЕЛЬНОСТЬ def step(num = 1, step = 1): while True: yield num num += step # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(n): return n / 4 + n / 5 + n / 3 + 13 == n # решаем задачу: sequence = step(0) res = itertools.islice(filter(check_condition, sequence), 1) # печатаем ответ: print(f'Демохар прожил: ', *res) print() Демохар прожил 60 лет: Демохар прожил: 60 257
Индийское число 258
Задача Д7 из книги Математический фольклор: Если некоторое число умножить на 5, от произведения отнять его треть, остаток разделить на 10 и к полученному числу прибавить последовательно одну треть, одну вторую и одну четвёртую первоначального числа, то получится 68. Какое это число? // решаем задачу var m := 1; var f: integer -> boolean := n -> (n * 5 - n * 5 div 3) div 10 + n div 3 + n div 2 + n div 4 = 68; Print(' Число равно: '); m.Step(1) .Where(f) .Take(1) .Println; Console.WriteLine(); История с методом расширения Take(1) неумолимо повторяется: import itertools # ГЕНЕРИРУЕМ ПОСЛЕДОВАТЕЛЬНОСТЬ def step(num = 1, step = 1): while True: yield num num += step # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(n): return (n * 5 - n * 5 // 3) // 10 + n // 3 + n // 2 + n // 4 == 68 # решаем задачу: sequence = step(1) res = itertools.islice(filter(check_condition, sequence), 1) 259
# печатаем ответ: print(f'Число равно:', *res) print() Искомое число должно делиться нацело на 2, 3 и 4, поэтому мы могли бы значительно сократить перебор, но в данном случае это не требуется. Число равно: 48 Диофантово число Задача АЭ14 из книги Увлекательная математика: По двум данным числам 200 и 5 найти третье число, которое, если его умножить на одно из них, даёт полный квадрат, а если его умножить на другое число, даёт квадратный корень из этого квадрата. // решаем задачу var m := 1; var f: integer -> boolean := n -> (n * 200).IsQuadrat and ((n * 200).Sqrt = 5 * n); Print(' Число = '); m.Step(1) .Where(f) .Take(1) .Println; Console.WriteLine(); Функцию is_quadrat мы уже написали раньше (найдите её!). Других проблем нет вообще! 260
import itertools, math # ПРОВЕРЯЕМ, ЯВЛЯЕТСЯ ЛИ ЧИСЛО ПОЛНЫМ КВАДРАТОМ def is_quadrat(num): root = int(math.sqrt(num)) return root * root == num # ГЕНЕРИРУЕМ ПОСЛЕДОВАТЕЛЬНОСТЬ def step(num = 1, step = 1): while True: yield num num += step # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(n): return is_quadrat(n * 200) and math.sqrt(n * 200) == 5 * n # решаем задачу: sequence = step(1) res = itertools.islice(filter(check_condition, sequence), 1) # печатаем ответ: print(f'Число =', *res) print() Искомое число равно 8: Число = 8 Годится также число 0, но оно явно не древнегреческое. 261
Пифагорейское число 262
Задача Д4 из книги Математический фольклор: Знаменитого Пифагора спросили: - Сколько учеников посещают твою школу и слушают твои беседы? Пифагор ответил: - Половина моих учеников изучает математику, четверть – музыку, седьмая часть проводит время в молчаливом размышлении, остальную часть составляют 3 ученика. Сколько учеников было у Пифагора? Эта задача напечатана и в книге Увлекательная математика. Античные этюды. Задача 5. Пифагор Самосский (ок. 508-501 гг. до н.э.): Поликрат (известный из баллады Шиллера тиран с острова Самос) однажды спросил на пиру у Пифагора, сколько у того учеников. - Охотно скажу тебе, о Поликрат, - отвечал Пифагор. - Половина моих учеников изучает прекрасную математику, четверть исследует тайны вечной природы, седьмая часть молча упражняет силу духа, храня в сердце учение. Добавь ещё к ним трёх юношей, из которых Теон превосходит прочих своими способностями. Сколько учеников было у Пифагора? var m := 1.0; var f: double -> boolean := n -> n / 2 + n / 4 + n / 7 + 3 = n; Print(' Учеников было: '); m.Step(1) .Where(f) .Take(1) .Println; Console.WriteLine(); 263
Очень простая, незадачливая задача: import itertools # ГЕНЕРИРУЕМ ПОСЛЕДОВАТЕЛЬНОСТЬ def step(num = 1, step = 1): while True: yield num num += step # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(n): return n / 2 + n / 4 + n / 7 + 3 == n # решаем задачу: sequence = step(1) res = itertools.islice(filter(check_condition, sequence), 1) # печатаем ответ: print(f'Учеников было:', *res) print() У Пифагора было 28 учеников: Учеников было: 28 Индийские квадраты Задача из журнала Наука и жизнь 1963-03-38-4: Найти число, которое от прибавления 5 или отнятия 11 обращается в полный квадрат. 264
// решаем задачу var m := 11; var f: integer -> boolean := n -> (n + 5).IsQuadrat and (n - 11).IsQuadrat; Print(' Число = '); m.Step(1) .Where(f) .Take(2) .Println; Console.WriteLine(); А вот тут нам нужно взять 2 числа: import itertools, math # ПРОВЕРЯЕМ, ЯВЛЯЕТСЯ ЛИ ЧИСЛО ПОЛНЫМ КВАДРАТОМ def is_quadrat(num): root = int(math.sqrt(num)) return root * root == num # ГЕНЕРИРУЕМ ПОСЛЕДОВАТЕЛЬНОСТЬ def step(num = 1, step = 1): while True: yield num num += step # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(n): return is_quadrat(n + 5) and is_quadrat(n - 11) # решаем задачу: sequence = step(11) res = itertools.islice(filter(check_condition, sequence), 2) # печатаем ответ: print(f'Числа:', *res) print() 265
Вот эти 2 числа, удовлетворяющие условиям задачи: Числа: 11 20 При n = 11 мы получаем квадраты 16 и 0, а при n = 20 – 25 и 9. Половина и треть Задача 489 из книги Математическая шкатулка [Нагибин88]: Запишите наименьшее натуральное число, половина и треть которого были бы соответственно квадратом и кубом некоторых натуральных чисел. var m:= 6; var res:= m.Step(6) .Where(n-> (n div 2).IsQuadrat and (n div 3).IsCube) .Take(7); // печатаем ответ: Println; foreach var n in res do begin Println(n, Sqrt(n div 2), Power((n div 3), 1.0 / 3.0)); end; Console.WriteLine(); Ищите функцию is_cube там же, где is_quadrat. Исключительно для интереса любопытства вычисляем 7 чисел, годных под условие задачи: import itertools, math 266
# ПРОВЕРЯЕМ, ЯВЛЯЕТСЯ ЛИ ЧИСЛО ПОЛНЫМ КВАДРАТОМ def is_quadrat(num): root = int(math.sqrt(num)) return root * root == num # ПРОВЕРЯЕМ, ЯВЛЯЕТСЯ ЛИ ЧИСЛО ПОЛНЫМ КУБОМ def is_cube(num): root = round(num**(1/3)) return root * root * root == num # ГЕНЕРИРУЕМ ПОСЛЕДОВАТЕЛЬНОСТЬ def step(num = 1, step = 1): while True: yield num num += step # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(n): return is_quadrat(n / 2) and is_cube(n / 3) # решаем задачу: sequence = step(6) res = itertools.islice(filter(check_condition, sequence), 7) # печатаем ответ: for n in res: print(n, int(math.sqrt(n / 2)), int(math.cbrt(n / 3))) Задача – совершенно компьютерная, поэтому он справляется с ней в считанные мгновения. Наименьшее число – 648: 648 18 6 41472 144 24 472392 486 54 2654208 1152 96 10125000 2250 150 30233088 3888 216 76236552 6174 294 267
Наименьшее число В журнале Наука и жизнь, №2 за 1968 год, на странице 57 напечатана такая задача: Очень простая задача на сообразительность: // решаем задачу var n := 1; var sq := n.Step .SkipWhile(n -> (n (n (n (n (n .Take(1) .Println; mod mod mod mod mod 2 3 4 5 6 <> <> <> <> <> 1) or 2) or 3) or 4) or 5)) Чтобы сохранить преемственность с кодом на паскале, заменяем функцию filter функцией dropwhile из модуля itertools, который действует подобно методу расширения SkipWhile из LINQ, то есть пропускает элементы 268
последовательности, удовлетворяющие заданному условию, а затем возвращает оставшиеся элементы, но нам достаточно первого такого элемента: import itertools # ГЕНЕРИРУЕМ ПОСЛЕДОВАТЕЛЬНОСТЬ def step(num = 1, step = 1): while True: yield num num += step # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(n): return (n % 2 != 1 or n % 3 != 2 or n % 4 != 3 or n % 5 != 4 or n % 6 != 5) # решаем задачу: sequence = step(1) res = itertools.islice(itertools.dropwhile(check_condition, sequence), 1) # печатаем ответ: print(f'Наименьшее число =', *res) print() Искомое число оказалось небольшим: Наименьшее число = 59 269
Индийские пчёлы 270
А эту задачу придумали в Древней Индии: Пчёлы числом, равным квадратному корню из половины их числа во всём рое, сели на куст жасмина, 8/9 пчёл полетели назад к рою. И только одна пчела из того же роя кружилась над цветком лотоса, привлечённая жужжанием подруги, неосторожно угодившей в ловушку сладко благоухающего цветка. Сколько всего пчёл было в рое? var b := 0; b.Step(18) .SkipWhile(d -> (d div 2).Sqrt + d * 8 div 9 + 2 <> d) .Take(1) .Println; Пока пчёлы нас не покусали, быстренько решаем задачу: import itertools, math # ГЕНЕРИРУЕМ ПОСЛЕДОВАТЕЛЬНОСТЬ def step(num = 1, step = 1): while True: yield num num += step # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(n): return math.sqrt(n / 2) + n * 8 / 9 + 2 != n # решаем задачу: sequence = step(0,18) res = itertools.islice(itertools.dropwhile(check_condition, sequence), 1) # печатаем ответ: print(f'Пчёл было:', *res) print() 271
Пчёл в рое было 72: Пчёл было: 72 Дробный ряд Задача 9-1 из книги Математическая шкатулка [Нагибин88], страница 16: Найдите простой приём вычислений и воспользуйтесь им для вычисления суммы: // решаем задачу: var summa:= Range(1,9).Println .Select(n -> 1.0/(n*(n+1))) .Println .Sum; // печатаем ответ: Console.WriteLine('Сумма ряда равна {0}', summa); Задача решается так же, как предыдущая, но дополнительно мы находим сумму элементов: # ФУНКЦИЯ ПРЕОБРАЗОВАНИЯ def transform(n): return 1.0/(n*(n+1)) # решаем задачу: 272
sequence = range(1, 10) res = sum(map(transform, sequence)) # печатаем ответ: print(‘Сумма ряда равна:’, res) print() Сумма равна 0,9: Сумма ряда равна: 0.9 Ещё один дробный ряд Задача 9-2 из книги Математическая шкатулка [Нагибин88], страница 16: Найдите простой приём вычислений и воспользуйтесь им для вычисления суммы: // решаем задачу: var summa:= Range(10,99).Println .Select(n -> 1.0/(n*(n+1))) .Println .Sum; // печатаем ответ: Console.WriteLine('Сумма ряда равна {0}', summa); 273
Эта задача отличается от предыдущей только диапазоном чисел в последовательности: # ФУНКЦИЯ ПРЕОБРАЗОВАНИЯ def transform(n): return 1.0/(n*(n+1)) # решаем задачу: sequence = range(10, 100) res = sum(map(transform, sequence)) # печатаем ответ: print('Сумма ряда равна:', res) print() Сумма ряда равна: 0.09 Трёхзначное число 2 Задача 597 из книги Математическая шкатулка [Нагибин88], страницы 97-98: Сколько слагаемых суммы 1 + 2 + 3 + 4 + 5 + … надо взять, чтобы получить трёхзначное число, состоящее из одинаковых цифр? // число сотен: var s: integer -> integer := x -> x div 100; // число десятков: var d: integer -> integer := x -> x div 10 mod 10; // число единиц: var e: integer -> integer := x -> x mod 10; // сумма арифметической прогрессии: 274
var summa: integer -> integer := n -> (1 + n) * n div 2; // решаем задачу: var res := 1.Step() .Select(n -> (n,summa(n))) .SkipWhile(n -> n[1] < 100) .Where(n -> (e(n[1]) = d(n[1])) and (e(n[1]) = s(n[1]))) .ElementAt(0); // печатаем ответ: Console.WriteLine('Число слагаемых равно {0}', res[0]); Console.WriteLine('Трёхзначное число равно {0}', res[1]); Console.WriteLine(); Эта программа оказалась сложнее предыдущих! Мы используем функцию map, чтобы применить функцию transform к элементам последовательности. Она генерирует кортежи из текущего индекса и суммы арифметической прогрессии. Функция dropwhile пропускает все элементы, пока сумма меньше 100. Функция filter отбирает элементы, удовлетворяющих условиям. Функция next извлекает первый элемент, который удовлетворяет условиям после всех операций фильтрации. import itertools # число сотен def s(x): return x // 100 # число десятков def d(x): return (x // 10) % 10 # число единиц def e(x): return x % 10 275
# сумма арифметической прогрессии def summa(n): return (1 + n) * n // 2 # ГЕНЕРИРУЕМ ПОСЛЕДОВАТЕЛЬНОСТЬ def generate_sequence(n = 1): while True: yield n n += 1 # ФУНКЦИЯ ПРЕОБРАЗОВАНИЯ def transform(n): return (n, summa(n)) # ПРОВЕРЯЕМ УСЛОВИЯ: def check1(x): return x[1] < 100 def check2(x): return e(x[1]) == d(x[1]) == s(x[1]) # последовательность: sequence = map(transform, generate_sequence()) # решаем задачу: res = next(itertools.dropwhile(check1, filter(check2, sequence))) # печатаем ответ: print(f'Число слагаемых равно {res[0]}') print(f'Трёхзначное число равно {res[1]}') print() Задача имеет единственное решение: Число слагаемых равно 36 Трёхзначное число равно 666 276
Индийские обезьяны 277
Очень старая индийская задача про обезьян. Подобные задачи были известны в Индии ещё в XII веке (Математический фольклор. Задача Д10). Сколько обезьян в стаде, если квадрат одной пятой их без 3 скрылось в пещере, а одна залезла на дерево? // решаем задачу var m := 15; Print(' Обезьян было: '); m.Step(5) .SkipWhile(n -> (n div 5 - 3).Sqr + 1 <> n) .Take(1) .Println; Здесь мы должны начать последовательность с числа 15 и продолжать её с шагом 5: import itertools # ГЕНЕРИРУЕМ ПОСЛЕДОВАТЕЛЬНОСТЬ def step(num = 1, step = 1): while True: yield num num += step # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(n): num = (n / 5 - 3) ** 2 + 1 return num != n # решаем задачу: sequence = step(15,5) res = itertools.islice(itertools.dropwhile(check_condition, sequence), 1) # печатаем ответ: print(f'Обезьян было:', *res) print() В стаде было 50 обезьян: 278
Обезьян было: 50 Наименьшее число 2 Задача 1.35 из книги Савина Занимательные математические задачи: Найдите наименьшее число, которое начинается с 1993 и делится на все числа от 1 до 9. var num:= 1993; var res:= 1.Step() .Select(n -> Trunc(num * Power(10, Trunc(Log10(n)) + 1) + n)) .Where(n -> n mod 5 = 0) .Where(n -> n mod 7 = 0) .Where(n -> n mod 8 = 0) .Where(n -> n mod 9 = 0) .Take(1) .Println; Println; Console.WriteLine('Наименьшее число = ' + res.First); Console.WriteLine('Частное = ' + res.First div 5 div 7 div 8 div 9); Console.WriteLine(); Задача тоже непростая, но нам – по зубам! Функция map применяет функцию expression к каждому элементу из сгенерированной последовательности generate_sequence, которая просто возвращает натуральные числа. Функция expression(n) вычисляет значение согласно формуле, где n - текущий элемент последовательности. 279
Функция filter отбирает элементы, удовлетворяющих условиям делимости на 5, 7, 8 и 9. Функция next извлекает первое значение, которое проходит через все условия фильтрации. import math # начало числа: num = 1993 num = 2026 # ГЕНЕРИРУЕМ ПОСЛЕДОВАТЕЛЬНОСТЬ def generate_sequence(n = 1): while True: yield n n += 1 # ВОЗВРАЩАЕМ ЗНАЧЕНИЕ ВЫРАЖЕНИЯ def expression(n): return int(num * 10**(int(math.log10(n)) + 1) + n) # ПРОВЕРЯЕМ УСЛОВИЕ def check(n): return n % 5 == 0 and n % 7 == 0 and n % 8 == 0 and n % 9 == 0 # последовательность: sequence = map(expression, generate_sequence()) # решаем задачу: filtered_sequence = filter(check, sequence) res = next(filtered_sequence) # печатаем ответ: print(f'Наименьшее число = {res}') print(f'Частное = {res // 5 // 7 // 8 // 9}') print() 280
Наименьшее число = 1993320 Частное = 791 Наименьшее число = 20263320 Частное = 8041 Метод SelectMany var text : array of string := ('Белеет парус одинокой', 'В тумане моря голубом.', '— Что ищет он в стране далекой?', 'Что кинул он в краю родном?', 'Играют волны, ветер свищет,', 'И мачта гнется и скрыпит;', 'Увы! — он счастия не ищет', 'И не от счастия бежит! —', 'Под ним струя светлей лазури,', 'Над ним луч солнца золотой: —', 'А он, мятежный, просит бури,', 'Как будто в бурях есть покой!'); var words := text .SelectMany(s -> s.Split() .Select(s2 -> '<' + s2 + '>')); words.Println; Алгоритм может быть таким: • • • • Берём каждую строку текста. Разбиваем её на слова. Каждое слово заключаем в символы < и >. При печати объединяем все слова в одну строку. Мы заменили метод расширения SelectMany вложенным генератором: 281
text = [ 'Белеет парус одинокой', 'В тумане моря голубом.', '— Что ищет он в стране далекой?', 'Что кинул он в краю родном?', 'Играют волны, ветер свищет,', 'И мачта гнется и скрыпит;', 'Увы! — он счастия не ищет', 'И не от счастия бежит! —', 'Под ним струя светлей лазури,', 'Над ним луч солнца золотой: —', 'А он, мятежный, просит бури,', 'Как будто в бурях есть покой!' ] # генерируем строку из отдельных слов текста: words = ( f'<{word}>' for line in text for word in line.split() ) # печатаем слова: print(*words) #for word in words: # print(word) И вот что у нас получилось: <Белеет> <парус> <одинокой> <В> <тумане> <моря> <голубом.> <—> <Что> <ищет> <он> <в> <стране> <далекой?> <Что> <кинул> <он> <в> <краю> <родном?> <Играют> <волны,> <ветер> <свищет,> <И> <мачта> <гнется> <и> <скрыпит;> <Увы!> <—> <он> <счастия> <не> <ищет> <И> <не> <от> <счастия> <бежит!> <—> <Под> <ним> <струя> <светлей> <лазури,> <Над> <ним> <луч> <солнца> <золотой:> <—> <А> <он,> <мятежный,> <просит> <бури,> <Как> <будто> <в> <бурях> <есть> <покой!> При желании вы можете напечатать каждое слово в отдельной строке, если раскомментируете цикл for. 282
Дедушка и внучка 283
Задача 10 (10.1) из книги Удивительный мир чисел [КА86], страница 52: Сколько дедушке лет, столько месяцев внучке. Дедушке с внучкой вместе 91 год. Сколько лет дедушке и сколько внучке? var res:= 0.Step() .SkipWhile(x -> x + 12 * x <> 91 * 12) .First; Console.WriteLine('Возраст внучки в годах = {0}', res div 12); Console.WriteLine('Возраст дедушки в годах = {0}', 91 - res div 12); Задача решается очень просто: import itertools # ГЕНЕРИРУЕМ ПОСЛЕДОВАТЕЛЬНОСТЬ def step(num = 1, step = 1): while True: yield num num += step # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(n): return n + 12 * n != 91 * 12 # решаем задачу: res = next(itertools.dropwhile(check_condition, step())) # печатаем ответ: n = res print('Возраст внучки в годах = ', res // 12) print('Возраст дедушки в годах =', 91 - res // 12) print() 284
А внучка совсем ещё зелёная – как ёлочка: Возраст внучки в годах = 7 Возраст дедушки в годах = 84 Трёхзначное число 3 (2 в паскале) Задача 585 из книги Математическая шкатулка [Нагибин88]: Если первую цифру трёхзначного числа увеличить на n, а вторую и третью цифру уменьшить на n, то получится число в n раз больше исходного. Найдите n и исходное число. // решаем задачу --> // первая цифра: var n1: integer -> integer := x -> x div 100; // вторая цифра: var n2: integer -> integer := x -> x div 10 mod 10; // третья цифра: var n3: integer -> integer := x -> x mod 10; var res:= Range(100,999) .SelectMany(y -> Range(1,9), (x,y) -> (x,y))//.Println .Where(n -> (n1(n[0]) + n[1]) * 100 + (n2(n[0]) - n[1]) * 10 + (n3(n[0]) - n[1]) = n[0] * n[1]) .Println .ElementAt(0); // печатаем ответ: Console.WriteLine(); Console.WriteLine('Число = {0} n = {1}', res[0], res[1]); Console.WriteLine(); 285
Решаем задачу так же, как предыдущую, то есть с генератором: # первая цифра def n1(x): return x // 100 # вторая цифра def n2(x): return x // 10 % 10 # третья цифра def n3(x): return x % 10 # решаем задачу: res = ( (x, y) for x in range(100, 1000) for y in range(1, 10) if ((n1(x) + y) * 100 + (n2(x) - y) * 10 + (n3(x) - y)) == x * y ) # печатаем ответ: answer = next(res) print(f'Число = {answer[0]} n = {answer[1]}') А вот и ответ на эту замысловатую задачу: Число = 178 n = 2 286
На одно делится, на другое нет 287
Задача 2 из книги Удивительный мир чисел [КА86], страница 84: Найдите наименьшее число, которое делится на 77, а при делении на 74 даёт в остатке 48. // решаем задачу: var num := 1.Step() .SkipWhile(n -> 77 * n mod 74 <> 48) .First; //печатаем искомое число: Console.WriteLine('Искомое число равно {0}', num * 77); Console.WriteLine(); Задача решается – как предыдущие: import itertools # ГЕНЕРИРУЕМ ПОСЛЕДОВАТЕЛЬНОСТЬ def step(num = 1, step = 1): while True: yield num num += step # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(n): return 77 * n % 74 != 48 # решаем задачу: res = next(itertools.dropwhile(check_condition, step())) # печатаем ответ: print('Искомое число равно', res * 77) print() И вот мы уже в ответе: 288
Искомое число равно 1232 Плюс-минус Задача 7-2 из книги Математическая шкатулка [Нагибин88], страница 15: Как быстро вычислить: 99 - 97 + 95 - 93 + 91 - 89 + ... + 7 - 5 + 3 – 1? // решаем задачу: var seq1 := Range(99, 1, -2).Println; var seq2 := Range(1,50).Select(x -> x.IsOdd ? 1 : -1).Println; var summa := seq1.Zip(seq2, (x,y)->x*y).Println.Sum; // печатаем ответ: Console.WriteLine(); Console.WriteLine('Сумма ряда равна {0}', summa); Console.WriteLine(); Метод расширения Zip попарно объединяет два последовательных ряда. В Питоне есть аналогичная встроенная функция zip. В этой программе функция zip(seq1, seq2) превращает две последовательности: 99 97 95 93 ... 1 -1 1 -1 ... в пары (99, 1), (97, -1), (95, 1), (93, -1), ... 289
а затем генератор вычисляет произведения этих пар x * y: # последовательности: seq1 = range(99, 0, -2) seq2 = (1 if x % 2 else -1 for x in range(1, 51)) # генерируем попарные произведения: products = (x * y for x, y in zip(seq1, seq2)) # вычисляемм сумму ряда: summa = sum(products) # печатаем ответ: print(f'Сумма ряда равна {summa}') Сумма ряда найдена: Сумма ряда равна 50 Минус-плюс Задача 482-8 из книги Математическая шкатулка [Нагибин88], страница 88: Найдите простой приём вычисления: 1002 – 992 + 982 – 972 + 962 – 952 + ... + 42 – 32 + 22 – 12 // решаем задачу: var seq1 := Range(100, 1, -1).Select(x -> x*x); var seq2 := Range(1,100).Select(x -> x.IsOdd ? 1 : -1); var summa := seq1.Zip(seq2, (x,y)->x*y).Sum; // печатаем ответ: 290
Console.WriteLine('Сумма ряда равна {0}', summa); Console.WriteLine(); Задача решается, как предыдущая, но все числа возводятся в квадрат, что легко сделать в функции map: # ФУНКЦИЯ ПРЕОБРАЗОВАНИЯ def transform(n): return n ** 2 # последовательности: seq1 = map(transform, range(100, 0, -1)) seq2 = (1 if x % 2 else -1 for x in range(1, 101)) # генерируем попарные произведения: products = (x * y for x, y in zip(seq1, seq2)) # вычисляемм сумму ряда: summa = sum(products) # печатаем ответ: print(f'Сумма ряда равна {summa}') Ответ на эту задачу полностью совпадает с ответом, который мы получили, решая задачу Гаусса: Сумма ряда равна 5050 291
Кубическое число 292
Задача 1 из книги Удивительный мир чисел [КА86], страница 89: Найдите число, куб которого - данное число: 1) М = 996 703 628 669; 2) N = 1 011 443 374 872. // решаем задачу: var num : int64 := 996703628669; //var num : int64 := 1011443374872; var cube: int64 -> boolean:= i -> i * i * i < num; var res := 0.Step().SkipWhile(i -> cube(i) = true).First; // печатаем ответ: Console.WriteLine('Кубический корень из числа {0} = {1}', num, res); Бесхитростная задача в лоб: import itertools # кубы: num = 996703628669 num = 1011443374872 # ГЕНЕРИРУЕМ ПОСЛЕДОВАТЕЛЬНОСТЬ def step(num = 1, step = 1): while True: yield num num += step # ПРОВЕРЯЕМ УСЛОВИЕ: def check_condition(n): return n ** 3 < num # решаем задачу: res = next(itertools.dropwhile(check_condition, step(0))) 293
# печатаем ответ: print(f'Кубический корень из числа {num} = {res}') print() Точные значения искомых корней: Кубический корень из числа 996703628669 = 9989 Кубический корень из числа 1011443374872 = 10038 294
Кузнечики 295
В седьмом номере журнала Квантик за 2016 год, на странице 32 напечатана вот такая конкурсная задача: // решаем задачу: Range(1,25,2) .Select(n -> n + (25 - n) div 2 * 2) .Println(NewLine); Функция map в Питоне аналогична методу расширения Select в LINQ. Функция map применяет заданную функцию к каждому элементу в последовательности. В данном случае мы применяем функцию transform, которая возвращает вычисленное значение для каждого n в диапазоне. # ФУНКЦИЯ ПРЕОБРАЗОВАНИЯ def transform(n): return n + (25 - n) // 2 * 2 # решаем задачу: sequence = range(1, 25, 2) res = map(transform, sequence) # печатаем ответ: print(*res, sep='\n') print() Запускаем программу и смотрим на список: 296
25 25 25 25 25 25 25 25 25 25 25 25 297
Сколько мне лет? 298
Дядя Алёша вдвое старше меня, а цифры числа, выражающего мой возраст, равны сумме и разности цифр его возраста. Сколько мне лет? // цифры: var digits := Range(0, 10); // решаем задачу: foreach var t in digits.Cartesian(digits) do begin var a := t.Item1; var b := t.Item2; var i := a * 10 + b; // мой возраст - двузначное число: if (i < 10) then continue; var onkel := i * 2; var dig1 := onkel div 10; var dig2 := onkel mod 10; if (((dig1 + dig2 = a) and (Abs(dig1 - dig2) = b)) or ((dig1 + dig2 = b) and (Abs(dig1 - dig2) = a))) then begin Writeln('Мой возраст = ' + i); Writeln('Возраст дяди Алёши = ' + onkel); end end; Console.WriteLine(); Метод расширения Cartesian строит декартово произведение двух последовательностей. Здесь обе последовательности - 0..9, поэтому мы получим все пары цифр (a, b). В Питоне функция itertools.product делает то же самое. from itertools import product 299
# цифры: digits = range(10) # решаем задачу: for a, b in product(digits, digits): i = a * 10 + b # мой возраст — двузначное число: if i < 10: continue onkel = i * 2 dig1 = onkel // 10 dig2 = onkel % 10 if ((dig1 + dig2 == a and abs(dig1 - dig2) == b) or (dig1 + dig2 == b and abs(dig1 - dig2) == a)): # печатаем ответ: print('Мой возраст =', i) print('Возраст дяди Алёши =', onkel) print() Ответ в годах и летах: Мой возраст = 17 Возраст дяди Алёши = 34 300
Великолепная семёрка 301
В журнале Квантик №10 за 2015 год напечатана такая конкурсная задача: // цифры: var digits := Range(0, 9); var res := digits.Cartesian(digits); var cart3 := res.Cartesian(digits); //решаем задачу //число семёрок: var n7 := 0; foreach var t in cart3 do begin var a := t.Item1.Item1; var b := t.Item1.Item2; var c := t.Item2; var s := 0; s += a = 7 ? 1 : 0; s += b = 7 ? 1 : 0; s += c = 7 ? 1 : 0; n7 += s; end; Println('Всего семёрок: ' + n7); Console.WriteLine(); 302
Здесь метод расширения Cartesian используется дважды, чтобы получить все тройки цифр: (0,0,0), (0,0,1), ..., (9,9,9) То есть всего 103 = 1000 троек. Обратите внимание, что функция product заменила два метода расширения Cartesian: from itertools import product # цифры: digits = range(10) # число семёрок: n7 = 0 # решаем задачу: for a, b, c in product(digits, repeat=3): s = 0 s += 1 if a == 7 else 0 s += 1 if b == 7 else 0 s += 1 if c == 7 else 0 n7 += s # печатаем ответ: print(‘Всего семёрок:’, n7) print() Всё готово! Запускаем программу и тут же получаем точный и круглый ответ – в числах от 1 до 1000 семёрка встречается ровно 300 раз: Всего семёрок: 300 303
Вьетнамские буйволы 304
Очень старая вьетнамская задача: Для кормления 100 буйволов заготовили 100 охапок сена. Стоящий молодой буйвол съедает 5 охапок сена. Лежащий молодой буйвол съедает 3 охапки сена. Старые буйволы втроём съедают 1 охапку сена. Сколько молодых буйволов стоят, сколько лежат и сколько буйволов старых? // буйволы: var sqBuffaloS := Range(0, 100 div 5); var sqBuffaloSL := sqBuffaloS.Cartesian(Range(0, 100 div 3)); var cart3 := sqBuffaloSL.Cartesian(Range(0, 100 div 3)); // решаем задачу: foreach var t in cart3 do begin var buffaloS := t.Item1.Item1; var 305uffalo := t.Item1.Item2; var bO := t.Item2; var 305uffalo := bO * 3; if ((buffaloS + 305uffalo + 305uffalo = 100) and (buffaloS * 5 + 305uffalo * 3 + 305uffalo div 3 = 100)) then begin Println(‘Стоящих молодых буйволов : ‘ + buffaloS); Println('Лежащих молодых буйволов : ' + buffaloL); Println('Старых буйволов : ' + buffaloO); Println; end end; from itertools import product 305
# решаем задачу: for buffalo_s, buffalo_l, b_o in product( range(100 // 5 + 1), range(100 // 3 + 1), range(100 // 3 + 1)): buffalo_o = b_o * 3 # печатаем ответ: if (buffalo_s + buffalo_l + buffalo_o == 100 and buffalo_s * 5 + buffalo_l * 3 + buffalo_o // 3 == 100): print('Стоящих молодых буйволов :', buffalo_s) print('Лежащих молодых буйволов :', buffalo_l) print('Старых буйволов :', buffalo_o) print() Задача имеет 4 решения: Стоящих молодых буйволов : 0 Лежащих молодых буйволов : 25 Старых буйволов : 75 Стоящих молодых буйволов : 4 Лежащих молодых буйволов : 18 Старых буйволов : 78 Стоящих молодых буйволов : 8 Лежащих молодых буйволов : 11 Старых буйволов : 81 Стоящих молодых буйволов : 12 Лежащих молодых буйволов : 4 Старых буйволов : 84 306
Сотая цифра Задача 300 из книги Математическая шкатулка [Нагибин88], страница 45: Все натуральные числа, начиная с 1, записаны в порядке их возрастания: 1 2 3 4 5 6 7 8 9 10 11… Какая цифра в этой записи стоит на сотом месте? // решаем задачу: var res:= Range(1,100).JoinIntoString(‘’); // печатаем ответ: Console.WriteLine('На сотом месте стоит цифра: {0}', res[100]); Console.WriteLine(); Метод расширения JoinIntoString объединяет элементы последовательности в одну строку. В Питоне есть аналогичная функция join для строк: # решаем задачу: res = ''.join(str(x) for x in range(1, 101)) # печатаем ответ: print(f'На сотом месте стоит цифра: {res[100]}') На сотом месте стоит цифра: 5 Вдогонку и вдобавок вы можете проштудировать книгу Тотальный тренинг по ЯваСкрипту. Массивы и функциональное программирование. 307
308
Как перевернуть число? 309
Как мы убедились, строку перевернуть легко, а число – трудно. Но перевёрнутые числа часто используются в задачах, поэтому давайте разберёмся на примерах, как это можно сделать. Способ 1. Строковый: n = 12345 reversed_n = int(str(n)[::-1]) print(reversed_n) Превращаем число в строку. Переворачиваем её и превращаем в число. При этом функция int убирает ведущие нули. Если они появились. 54321 Способ 2. Числовой: n = 12345 rev = 0 while n > 0: # последняя : digit = n % 10 # добавляем её к перевёрнутому числу: rev = rev * 10 + digit # удаляем последниюю цифру исходного числа: n //= 10 print(rev) Это «честный» способ переворота чисел, которым мы уже пользовались при решении задач. 310
Способ 3. Числовой – для отрицательных чисел: Этот способ годится для переворачивания отрицательных чисел, если нужно сохранить знак. def reverse_number(n: int) -> int: # знак: sign = -1 if n < 0 else 1 # абсолютная величина числа: n_abs = abs(n) # переворачиваем число # строковым способом: reversed_n = int(str(n_abs)[::-1]) # возвращаем перевёрнутое число со знаком: return sign * reversed_n print(reverse_number(-1230)) -321 Способ 4. Числовой – рекурсивный: def reverse_recursive(n, acc=0): if n == 0: return acc return reverse_recursive(n // 10, acc * 10 + n % 10) print(reverse_recursive(12345)) 311
Время шуток 312
313
Сбитень – боксёр. 314
- Миша, у тебя горбы – как у верблюда! - Маша, просто я люблю холодную воду! 315
Хороший аппетит здоровью не вредит. 316
Хороший аппетит болезни победит! 317
Барин – это человек, у которого есть бар. 318
- Маша, я так люблю яичницу-глазунью! 319
Не плюй в того, у кого слюны больше! 320
Лучше всех зажигают пожарные! 321
Крупье – женщина с широкими бёдрами. 322
Кому одной жены много, а кому и двух не хватает. 323
Сварщик – специалист по сварам. 324
Ответы Кролики – это не болеки-лёлики! 10 кроликов. Головоломные скобки Задача 2 Задача 3 325
Задача 4 Задача 5 326
Задача 6 Задача 7 327
Задача 8 Задача 9 328
Задача 10 Головоломные палиндромы Задача 2 329
Задача 3 Задача 4 330
Задача 5 Задача 6 331
Задача 7 Задача 8 332
Задача 9 Задача 10 333
Фигуры из пиастров Числа, которые можно изобразить в виде треугольников, и называются треугольными. Как мы установили, первое треугольное число равно 1, второе – 3, третье – 6, и так далее. При этом к первому треугольному числу, единице, последовательно добавляются числа 2, 3, 4, 5,… Таким образом, n-е треугольное число равно сумме арифметической прогресии 1, 2, 3, 4, 5,… В ней начальный член равен 1, и разность также равна 1. Сумма первых n членов этой арифметической прогрессии равна (1 + n) * n/2, то есть: 1 𝑛(𝑛 + 1) 2 По этой формуле легко вычислить, что для построения треугольника, основание которого состоит из 10 пиастров, нужно 10*11/2 = 55 пиастров. Построить из пиастров четырёхугольники: 334
Пятиугольники: 335
И шестиугольники совсем нетрудно, если вы в ладах с циркулем и линейкой: 336
337
В общем случае числа, представленные в виде правильных многоугольников, называются многоугольными (полигональными): квадратными, пятиугольными, шестиугольными… Если вы внимательно посмотрите на рисунки, то заметите закономерности, по которым добавляются пиастры при увеличении стороны многоугольника. Например, для квадрата мы имеем такие числа: 1, 3, 5, 7, 9, … Это последовательные натуральные нечётные числа. Для пятиугольника последовательность такая: 1, 4, 7, 10, 13, … И наконец, последовательность для шестиугольников: 1, 5, 9, 13, 17,… Сможете ли вы теперь вывести формулу для нахождения n-го квадратного, пятиугольного и шестиугольного числа? Ответ смотрите на следующей странице. 338
Легко заметить, что все представленные последовательности – арифметические прогрессии, начинающиеся с 1. Разность квадратной прогрессии равна 2, пятиугольной – 3, шестиугольной – 4: (1 + (2n-1))*n/2 = n2 Естественно, такой вывод формулы обусловлен только общностью последовательностей. Для квадратных чисел вывести эту формулу можно сразу, возведя длину стороны в квадрат. Для пятиугольника: (1 + (3n – 2))*n/2 = (3n-1)n/2, или Для шестиугольника: 1 𝑛(3𝑛 − 1) 2 (1 + (4n-3))*n/2 = (4n-2)*n/2 = n(2n - 1) Аналогично вы сможете вывести формулу для любого многоугольного числа. 339
Литература [Python] Рубанцев Валерий Питонарий. Python: Первое знакомство 2026, - 726 с. 340
[Python] Рубанцев Валерий Питоний. Python: Лучший друг 2026, - 614 с. 341
[C#] Рубанцев Валерий Программирование на языке C#. Начальный уровень 2014, - 622 с. 342
[C#] Рубанцев Валерий Программирование на языке Си-шарп. Базовый уровень 2013, - 860 с. 343
[Котлин] Рубанцев Валерий Привет, Котлин! 2018, - 336 с. 344
[КА86] [КА96] Б.А. Кордемский, А.А.Ахадов Удивительный мир чисел (МАТЕМАТИЧЕСКИЕ ГОЛОВОЛОМКИ И ЗАДАЧИ ДЛЯ ЛЮБОЗНАТЕЛЬНЫХ) М.: Просвещение, 1986. – 144 с. М.: Просвещение, 1996. – 159 с 345
[Нагибин88] Нагибин Ф.Ф., Канин Е.С. Математическая шкатулка М.:Просвещение, 1988. – 160 с. [Фольклор] Ганчев, Чимев, Стоянов Математический фольклор Знание. Москва, 1985. – 208 с. 346
[СА95] Савин Анатолий Занимательные математические задачи АСТ, 1995. – 176 с. 347