/
Similar
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