Код, який боляче читати: як ми провели конкурс на найкращий спагеті-код

Привіт! Мене звати Дмитро Пилипенко, уже два роки я працюю Instructional Designer в освітній платформі robot_dreams, тепер — як Senior. Так-так, у нас теж є така градація, адже ми теж в ITшці.

Instructional Designer займається тим, що формує і оформлює навчальні програми разом із лекторами в такий спосіб, щоби програма була максимально ефективною та цікавою для студентів. У школах, де навчають саме IT-спеціальностей, технічний бекграунд буде плюсом. Так тандем лектор (досвід у своїй галузі) плюс ID (досвід у розробці програм і загальний технічний рівень) підсилюється, і програма отримує шанси бути більш якісною, ефективною та приносити практичний досвід.

Нещодавно на честь дня народження платформи ми провели конкурс на найкращий лайнокод. Зазвичай студенти вчаться писати код чітко, структуровано, із дотриманням стандартів. Та відсвяткувати річницю хотілося більш фаново й розслаблено, весело і з користю. Я брав участь у етапі відбору як представник від команди, а також кординував конкурс. Тож хочу поділитися, що цікавого зробили наші учасники.

Що таке лайнокод, або спагеті-код

Так називають погано структурований, заплутаний і нечитабельний код. Як правило, він написаний в один рядок, без переносів, розділень на функції чи цикли. Виглядає це приблизно так само, як сплутані між собою спагеті: розібрати й відокремити частини майже неможливо.

Початківці часто припускаються цієї помилки: можуть писати лайнокоди на старті своєї карʼєри. Із часом вони вчаться робити більш читабельні й зрозумілі іншим розробникам коди. Та ми навмисно пожартували над самими собою і використали цей термін у назві конкурсу. Що більше ти знаєш і вмієш, то легше зізнаватися в помилках минулого.

Код в один рядок

Головним завданням конкурсу було не просто написати спагеті-код, а й зробити так, щоб він працював. І це вже завдання із зірочкою.

На першому етапі відбору перевірялося виконання умов конкурсу, а саме:

  • один рядок коду — не два, не три, і вже точно не «if-else» на двох сторінках;
  • мова програмування — будь-яка: від Python до Brainfuck;
  • функціональність — код має працювати;
  • чесність — це має бути авторський код учасника, жодних хакерських трюків;
  • все в одному місці — у репозиторії;
  • документація — розвʼязок має містити на репозиторії документацію в довільному стилі, з якої зрозуміла ідея, постановка задачі, як скомпілювати/інтерпретувати і виконати код.

Чесно кажучи, навіть при невиконанні умов ми розглядали роботи, щоб не пропустити щось визначне та унікальне. Але такі учасники втрачали рейтинг, що мало значення при переході на наступний етап оцінювання.

Ми відзначали, чи власну ідею використав учасник, чи розв’язував одну із запропонованих нами задач:

  • перевірка на високосний рік;
  • унікальні елементи в масиві та їхнє сортування;
  • перевірка на паліндром;
  • власний генератор випадкових чисел;
  • стиснення тексту.

Після двох попередніх підетапів відбору ми дивилися саме в код. Після його відкриття в журі мало виникати бажжання не те, що звернути чи закрити його, а рубанути тумблер зі світлом і хоча б пʼять хвилин полежати в тиші й темноті. Оце основний критерій! 🤣

Але, хоч код і мав бути в один рядок (фактично в один рядок відформатований) і містити велику кількість заплутаних конструкцій, та все ж він мав працювати й розв’язувати поставлену задачу.

До фіналу потрапило десять робіт, за які голосували учасники нашого ком’юніті, а точніше всі охочі, після доєднання до нашого чат-боту. Коротенько оглянемо роботи призерів за результатами голосування.

Дисклеймер

Далі в статті будемо оглядати речі, які можуть викликати циклічне запитання «навіщо», а деякі з них — травмувати психіку ще психологічно не сформованих розробників. Будьте обережні й читайте далі відповідально!

Переможець (або Selfie-random)

📌 Посилання на репозиторій

Ну, звісно, оглянемо рішення нашого переможця.

Обрана задача із запропонованих: напишіть код, що генерує абсолютно випадкове число від 1 до 100... але без генератора випадкових чисел.

Код переможця в оригінальному форматуванні ви можете переглянути на репозиторії.
Давайте ризикнемо трішки його відформатувати:

__builtins__.__import__(«lorem ipsum»[-3] + «y» + «lorem ipsum»[-3]).stdout.write(
f«Shitty {'number' if '666'.isdigit() else 'string'}: "
f»{(lambda _: _())(lambda: (lambda sasori: (lambda f: (lambda hidan: hidan% 100 + 1)(
int(getattr(__import__('hashlib'), 'sha256')(
getattr(__import__('base{count}'.format(count=8 * 8)), 'b64encode')(
(
            getattr(getattr(__import__('cv2'), 'imencode')('.jpg', getattr(f, 'read')()[1])[1], 'tobytes')()
+ (
              str(len(getattr(__import__('psutil'), 'pids')()))
+ str(getattr(getattr(__import__('psutil'), 'virtual_memory')(), 'available'))
).encode()
      )
    )
  ).hexdigest()[:8], 16)
))(sasori) if getattr(sasori, 'isClosed'.replace('Closed', 'OPENED'.lower().title()))() 
else getattr((x for x in ()), 'throw')(IOError('Your camera is a shit!'))
))(getattr(__import__('cv2'), 'VideoCapture')(0))
)}\n»
)

Учасник не стандартно, а оригінально й динамічно імпортує модулі через генерацію рядка sys для того, щоб додати більше заплутаності:

__builtins__.__import__("lorem ipsum«[-3] + «y» + "lorem ipsum"[-3])

Налаштовуємося на правильні логічні хвилі конкурсу: фрагмент "lorem ipsum«[-3] дає літеру ’s’, і додавання ’y’ знову + «s» також через "lorem ipsum«[-3] дає ’sys’.

Далі використовуємо цікавий прийом на трушну перевірку if ’666’.isdigit() для того, щоб завжди використовувати «number».

Поки все логічно, скажете ви... 😅

Ініціалізуємо вкладену лямбда-функцію, яка обчислює значення для вставки у форматований рядок і взагалі робить магію (lambda _: _())(lambda:...)

Тут використовуємо всього потроху:

  • cv2 — для захоплення кадру з камери:
    • якщо камера доступна, виконує f.read()[1] для захоплення кадру з відеопотоку, cv2.imencode(’.jpg’, кадр)[1] кодує кадр як зображення JPEG, а.tobytes() перетворює його на байти. Якщо не доступна, отримаєте досить толерантний ерор меседж, який охарактеризує вашу камеру практично одним словом.
  • psutil — для отримання інформації про процеси й доступну пам’ять:
    • psutil.pids() повертає список усіх PID активних процесів.
    • psutil.virtual_memory().available дає обсяг доступної пам’яті. Обидва значення конвертуються в рядки й конкатенуються.
  • base64 — для кодування зображення:
    • base64.b64encode()- кодує зображення та системну інформацію в Base64.
  • hashlib — для створення SHA256 хешу:
    • hashlib.sha256(...) — створює SHA-256-хеш з отриманого Base64-рядка.

Потім беремо вісім символів отриманого хеш і кастуємо.

Ще цікавий момент: функція isOpened() (переписана через isClosed.replace(’Closed’, ’OPENED’)) перевіряє, чи підключено камеру. Якщо камера недоступна, маємо IOError.

Формула всього цього задуму в результаті виходить int((...), 16)% 100 + 1 і перетворює результат на число в межах від 1 до 100 згідно з умовою задачі конкурсу.

І, звісно, виводимо це все, хоч тут через стандартний вивід, а не сигналами системних звуків в абетці Морзе. Хоча це гарна ідея для такого конкурсу, і тут явно такого бракує 😅 Але ідея залучити апаратні модулі вашого комп’ютера теж цікава і прийшлася до душі всім, хто віддав голос.

Цікава історія одного стажера (із використанням C, Python, Shell)

📌 Посилання на репозиторій

Цікава історія й цікаве рішення. Загальне враження — творчий підхід. Учасник обрав один із варіантів завдань, але ж як це все оформив. Обрана задача із запропонованих — така ж, як і попередня: власний генератор випадкових чисел.

Історія розповідає нам про такого собі стажера Ваню, який отримав задачу на створення генератора випадкових чисел, що потрібно інтегрувати до продукту (так співпало із завданням конкурсу 😅) мовою програмування С. Але Ваня не шукає простих шляхів. Легенда говорить про те, що стажер ще й додумався все оформити TXT-файлами в різних місцях репозиторію, що явно доповнює код із погляду документації.

Найкраще опише задум стажера його ж нотатка:

... усі мої колеги розповідають, за скільки строк коду їм вдалося вирішити задачі. Я буду кращим за всіх! Я настільки крутий кодер, що можу написати найкраще рішення, і використати всього одну строчку коду!!!

Задача полягає в тому, щоб написати генератор випадкових чисел на.с, бо це низькорівнева мова програмування, і код на ній працює швидко.

Я, як мене вчили, провів ресерч, і дізнався, що для цієї задачі є готові рішення. Перше, яке мені вибилось — це сайт random.org. Я запропонував використати його, але мені сказали, що ми не можемо: їх АПІ з лімітами, і нам потрібне своє рішення. Тому використати random.org хоч і не жахлива ідея, але не варіант, враховуючи потреби компанії.

Але я знайшов, як викорситати random.org так, щоб не залежати від їх лімітів! Хмм, але я не знаюЮ як робити network request на.c, хоча знаю, як це зробити на пітоні.

Еврика! Можна інтегрувати пітон в.c.

Тоді треба спочатку написати пітонівську частину...

Чи доводилося вам інтегрувати Python в C? Я думаю, такі випадки дійсно зустрічаються в житті, але на цьому моменті точно стає цікаво, як же це буде.

Тож починаємо із Python:

from time import time
from concurrent.futures import ThreadPoolExecutor,as_completed # Треба зробити код набагато рандомніше, тому будемо паралелити для отримання найрандомнішого рандому
import requests

eval=exec # Евал така собі назва, хочу іншу. Більше того, для exec є краща назва
exec=ThreadPoolExecutor(5) # краща назва.

dict={} # так буде зрозуміліше

# в одну строчку, дефайн не зробити, наче, тому треба адаптуватися під мій виклик 
# NOTE: функція дуже тонко налаштована, змінювати значення заборонено!
# NOTE: якщо у вас дуже слабкий інтернет, вам заборонено(!!!) використовувати цей генератор, бо є вірогідність отримати OOM
str=«def fetch_url(*args): \
args=args[0];\
start_time,end_time=args[1](),args[2].get(args[0]).elapsed.total_seconds()+args[1]();\
return(end_time-start_time)*2718432483+543543» 

# Тут, сама логіка
# Ми надсилаємо Get запит на рандоморг, і записуємо за скільки часу ми отримали відповідь

# код чітко налаштований так, щоб усі функції дефайнились як ми хочемо, і відпрацьовували рівно 5(!) разів, після чого результати сумуються у фінальний результат
print(sum([list.result()for list in list(map(lambda _:exec.submit((lambda f:(eval(f,{},dict),dict[«fetch_url»])[1])(str),(«<a href="https://www.random.org">https://www.random.org</a>»,time,requests)),range(len(«55555»))))]))

Звісно, учасник додав коментарі, але ж погляньте на елегантність цього рішення.
Паралельно будемо виконувати це пʼять 5 разів на всяк випадок для покращення нашого рандому, точніше його результату:

eval=exec
exec=ThreadPoolExecutor(5)

Ну а самі перевизначення то окрема історія, захотілося людині 😅

Визначаємо функцію через рядок, а вже потім його виконаємо через exec. І самий сік нашої пайтон-частини:

print(sum([list.result()for list in list(map(lambda _:exec.submit((lambda f:(eval(f,{},dict),dict[«fetch_url»])[1])(str),(«<a href="https://www.random.org">https://www.random.org</a><a href="about:blank">»,time,requests</a>)),range(len(«55555»))))]))

Тут ми завуальовано визначаємо ітератор range(len("55555″), щоби потім використати його в map

map(lambda _:exec.submit((lambda f:(eval(f,{},dict),dict[«fetch_url»])[1])(str),(«<a href="https://www.random.org">https://www.random.org</a>»,time,requests)),range(len(«55555»)))

У рамках map ми визначаємо хитрим способом функцію, про яку говорили раніше, і яка в нас була просто рядком. І вносимо цю функцію до глобального словника. Далі виконуємо пʼять разів запити, кожен із результатів (час запиту як різницю таймстемпів початку й закінчення запиту) домножаємо на щось схоже на MAX_INT (але щось інше), додаємо магічне 543543 (мабуть, універсальний пароль засвітив стажер), суму виводимо просто print.

Отака простенька вступна частина Python-коду.

Рухаємось далі. Інтегруємо Python код в C:

int main() {
// я запитав у чатджіпіті, як можна це зробити, і мені порекомендували створити файл пітону. не знаю поки що далі, але він точно допоможе
// для читабельності, назву файл так, як він має запускатись
FILE *f=fopen(«python.exe», «w»);

fprintf(f, "%s», «from time import time;from concurrent.futures import ThreadPoolExecutor,as_completed;import requests;eval=exec;exec=ThreadPoolExecutor(5);dict={};str=\«def fetch_url(*args):args=args[0];start_time,end_time=args[1](),args[2].get(args[0]).elapsed.total_seconds()+args[1]();return(end_time-start_time)*2718432483+543543\";print(sum([list.result()for list in list(map(lambda _:exec.submit((lambda f:(eval(f,{},dict),dict[\«fetch_url\"])[1])(str),(\«<a href="https://www.random.org">https://www.random.org</a>\",time,requests)),range(len(\«55555\"))))]))");
fclose(f);
char command[256];

// наче так можна запустити команду…
snprintf(command, sizeof(command), «python3 python.exe»);
// попен, хе-хе
f = popen(command, «r»);
double _;
// тут записуємо значення з пітонівсьоко коду
fscanf(f, "%lf», &_);

  // а тут я подумав, що все таки, якось недостатньо виходить рандому, тому, було розроблена рандомізація рандомного числа
// логіка дуже проста — ми беремо адресу флоата в якому ми зберігаємо результат із пітону. потім переводимо її, ніби це андерса Інта. Потім, беремо Інт із цієї Адреси. Потім, проводимо XOR з інтовим time(NULL) (чому б і ні)
// потім, приводимо модульну операцію для того щоб звести результат до очікуваного ренджу від 1 до 100
// з якоїсь причини, значення можуть бути мінусовими. чому — я хз, але можна просто 100 додати, і так буде норміс
//а вже потім, проводимо останній модуло, і приводимо вже до реального ренджу 1 до 100
printf("%d\n», ((((int)(int*)&_ ^ (int)time(NULL))% 100) + 100)% 100 + 1);
}

Тут усе зрозуміло із коментарів учасника. Але все ж таки особливий підхід, коли ми розглянутий вище код на Python записуємо до файлу саме із С-коду:

FILE *f=fopen(«python.exe», «w»);

fprintf(f, "%s», «from time import time;from concurrent.futures import ThreadPoolExecutor,as_completed;import requests;eval=exec;exec=ThreadPoolExecutor(5);dict={};str=\«def fetch_url(*args):args=args[0];start_time,end_time=args[1](),args[2].get(args[0]).elapsed.total_seconds()+args[1]();return(end_time-start_time)*2718432483+543543\";print(sum([list.result()for list in list(map(lambda _:exec.submit((lambda f:(eval(f,{},dict),dict[\«fetch_url\"])[1])(str),(\«<a href="https://www.random.org">https://www.random.org</a><a href="about:blank">\",time,requests</a>)),range(len(\«55555\"))))]))");

fclose(f);

А потім із цього ж коду C ми виконуємо код на Python і за посередництвом Shell за кадром отримуємо нарешті наші дані, які для нас згенерував Python. Ну, начебто все знову логічно до цього моменту... Що ж далі?

А далі ще простіше. Але є новий гравець у цій історії — Shell 💪 Хоча насправді він зустрічався і раніше, але не так явно, як обгортка для двох попередніх складових.

Для чого нам баш, запитаєте ви? Для того, щоб зробити цей код однорядковим, оскільки include в один рядок із головною функцією компілятор не пропускає:

# Треба створити файл із сі кодом, який ми запустимо
# Називаємо gcc.exe, щоб усім було зрозуміло як його запускати
echo '#include <stdio.h>' > gcc.exe && \
echo '#include <stdlib.h>' >> gcc.exe && \
echo '#include <time.h>' >> gcc.exe && \
echo 'int main(){FILE *f=fopen(«python.exe», «w»);fprintf(f, "%s», «from time import time;from concurrent.futures import ThreadPoolExecutor,as_completed;import requests;eval=exec;exec=ThreadPoolExecutor(5);dict={};str=\«def fetch_url(*args):args=args[0];start_time,end_time=args[1](),args[2].get(args[0]).elapsed.total_seconds()+args[1]();return(end_time-start_time)*2718432483+543543\";print(sum([list.result()for list in list(map(lambda _:exec.submit((lambda f:(eval(f,{},dict),dict[\«fetch_url\"])[1])(str),(\«<a href="https://www.random.org">https://www.random.org</a>\",time,requests)),range(len(\«55555\"))))]))");fclose(f);char command[256];snprintf(command, sizeof(command), «python3 python.exe»);f = popen(command, «r»);double _;fscanf(f, "%lf», &_);printf("%d\n», ((((int)(int*)&_ ^ (int)time(NULL))% 100) + 100)% 100 + 1);}' >> gcc.exe && \
gcc -x c -o exe.exe gcc.exe && \
./exe.exe
# в кінці компілюємо, btw ці дебіли розробники gcc, без додаткових параметрів не дають.exe сприймати як.c…
# приводимо до exe.exe, щоб усім було зрозуміло що його треба запускати з exe
# ну й запускаємо

Таким чином у цьому рішенні поєдналося явно три технології. Однозначно цікавий підхід, який демонструє не тільки навички й широкий технічний погляд учасника, але і його творчі здібності.

Морзянка (або закодований код)

📌 Посилання на репозиторій

Розглянемо ще одне цікаве рішення з використанням абетки Морзе.

Код виглядає наступним чином, як наче трішки засніжило:

exec((lambda:''.join([{'.-': 'a', '-…': 'b', '-.': 'd', '.': 'e', '.-.': 'f', '.': 'i', '.-.': 'l', '--': 'm', '-.': 'n', '---': 'o', '.--.': 'p', '.-.': 'r', '…': 's', '-': 't', '.-': 'u', '-.--': 'y', '-----': '0', '.----': '1', '…-': '4', '-.-.--': '!', '-.--.': '(', '-.--.-': ')', '---…': ':', '-…-': '=', '.-.-.': '"','------':' ','…':'%'}[c]for c in '.--.-.. -. — -.--. -.--.-..- -- -… -..- ------ -.-- ---….-.-. -.--.….-.-...-. -.--. -.--…...- -…- -…- ----- ------.- -. -. ------ -.--….---- ----- -.-.-- -…- ----- -.--.- ---.-. -.--. -.--…...- ----- ----- -…- -…- ----- -.--.-.-.….-.-. -. ---.-.-. -.--.- -.--.. -. — -.--.. -.--..- — -.--.-.-. -. -.-. ------ -.--.-.-. ---… ------.-.-. -.--.- -.--.- -.--.- -.--.-'.split()]))())

Але якщо придивитися уважніше, то цей код уже не такий і складний.

У нас є словничок:

{'.-': 'a', '-…': 'b', '-.': 'd', '.': 'e', '.-.': 'f', '.': 'i', '.-.': 'l', '--': 'm', '-.': 'n', '---': 'o', '.--.': 'p', '.-.': 'r', '…': 's', '-': 't', '.-': 'u', '-.--': 'y', '-----': '0', '.----': '1', '…-': '4', '-.-.--': '!', '-.--.': '(', '-.--.-': ')', '---…': ':', '-…-': '=', '.-.-.': '"','------':' ','…':'%'}

І відповідно сам код абеткою Морзе, у яку додали два додаткових символи, оскільки азбука по дефолту їх не враховує — це ’------’ -> ’ ’ (пробіл) ’...’ -> ’%’ (відсоток):

'.--.-.. -. — -.--. -.--.-..- -- -… -..- ------ -.-- ---….-.-. -.--.….-.-...-. -.--. -.--…...- -…- -…- ----- ------.- -. -. ------ -.--….---- ----- -.-.-- -…- ----- -.--.- ---.-. -.--. -.--…...- ----- ----- -…- -…- ----- -.--.-.-.….-.-. -. ---.-.-. -.--.- -.--.. -. — -.--.. -.--..- — -.--.-.-. -. -.-. ------ -.--.-.-. ---… ------.-.-. -.--.- -.--.- -.--.- -.--.-'.split()

Весь цей задум приведе нас до доволі елегантного рішення, якщо перевести його в Python =-код:

print((lambda y:«yes» if (y% 4 == 0 and y% 10!= 0) or (y% 400 == 0) else «no»)(int(input(«enter year: "))))

Так, звісно, в Python це все виглядає не так лайново, як того вимагав конкурс. А от усе ж таки оформлене рішення цікаве, особливо для тих, хто не зустрічався раніше з абеткою Морзе. Щонайменше в програмуванні.

Навіщо це все

Насправді ми віримо, що лайнокод може принести користь для розвитку програміста та прокачати певні навички. Наприклад, лайнокод може стимулювати креативне мислення та здатність шукати рішення, які виходять за межі звичайного підходу.

Створення складного та неефективного коду дає змогу краще зрозуміти, як саме погана оптимізація або неправильна структура можуть вплинути на продуктивність програми. У процесі написання складного та нечитабельного коду програмісти часто стикаються з необхідністю ускладнювати прості речі. Це тренує око та мозок розпізнавати складне й робити його одразу простішим.

Можна навчитися цінувати компроміси між швидкістю розробки, читабельністю та ефективністю. Навіть якщо початковий код складний і важкочитабельний, важливим є процес його рефакторингу — перетворення в більш зрозумілий і ефективний.

Створення складного коду часто пов’язане з використанням нестандартних алгоритмів або конструкцій. Це може сприяти глибшому розумінню коду в майбутньому, бо хтозна із чим ще доведеться зустрітися в житті, з якими стажерами. Написання складного коду також зазвичай вимагає роботи з абстракціями та створення багатьох рівнів, що може допомогти в розвитку навичок роботи з більш складними концепціями та паттернами проєктування в майбутньому.

Навіщо ми провели конкурс на найкращий лайнокод? Здебільшого для розваги. Але, чесно кажучи, стикаючись із таким кодом, ти переосмислюєш деякі моменти й зосереджуєшся на тому, як все ж таки важливо не ускладнювати. Усе геніальне насправді просто!

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

👍ПодобаєтьсяСподобалось12
До обраногоВ обраному3
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

А це можете розібрати?

def ncm(x):
    a = x & -x
    b = x + a
    c = b ^ x
    c >>= 1 + a.bit_length()
    return b | c

Чудова побітова розминочка. Гарний челендж 😎
Візьмемо, наприклад, x=3 (так трішки цікавіше)

def ncm(x):
a = x & -x // побітове «і», тобто сумуємо скастовані до бітів значення. Цікавим є те, що 1 & −1 = 1, 2 & −2 = 2, а 3 & −3 != 3 — це тому, що для кастування відємного потрібно −3 << 32, відповідно маємо тоді в результаті не ...0011, а ...1101 і отримуємо 3 & −3 = 1
b = x + a // сумуємо, якщо мали 3 отримаємо 4
c = b ^ x // виняткова диз’юнкція (xor), у нашому випадку 100 ^ 011 = 111 (7)
c >>= 1 + a.bit_length() // зсуваємо біти значення с, у нашому випадку значення с зсуваємо на 2 біти і отримуємо 001 (1)
return b | c // повертаємо «або», в нашому випадку отримуємо 100 | 001 = 101 (5)

Дякую, приємні спогади 😌

Ну... а для чого цей код, це ж головне. Так, ncm(3) = 5, ncm(6) = 9, але який в цьому сенс?

Зазвичай, коли ти дивишся на спагетті код, то виникає та сама проблема: ти розмуєш, що робить окремий оператор, але незрозуміло, як вони працюють разом.

Значить, я дуже буквально сприйняв ваше запитання.

Ну як правило наші учасники вирішували якусь конкретну задачу, як я вже заначав у статті.
І не дивлячись на те, що код може бути абсурдним, заплутаним і не раціональним, однією із умов було вказати, що саме вони намагалися побороти, яку задачу вирішували.
У вашому випадку теж може бути генератор, але треба ще погратися із вхідним значенням 😌

тут схоже, що для даного числа х генерується таке найменше y > x, що кількість встановлених в 1 біт у-ка в точності дорівнює такій в х-са.

Є підозра що воно генерує нову перестановку бітів з числа що на вході. Якщо не помиляюся, кількість одиниць на вході і на виході виходить однаковою.

Так, більше того, буде згенеровано найменьше наступне число, але має ті ж саму кількість одиниць. Таким чином ми можемо перебрати усі комбінації (сполуки) C(n, k) досить швидким чином.

Але це зовсім незрозуміло, дивлячись на код.

Але це зовсім незрозуміло, дивлячись на код.

В данном случае «должно быть сразу понятно глядя на код» является совершенно необязательным условием и код не является «говнокодом»

Відразу таке питання... Проблема спагетті говнокоди, як на мене: (1) незрозуміло як працює; (2) легко помилитися при модифікації. Але з оговоркою, що якщо це можна уникнути.

Тут можна переписати, наприклад, так:

def ncm(xxxx_xO11_1OOO): # pylint: disable=invalid-name
    """
    Generate the next combination mask (NCM) for an integer.
    Uses Gosper's hack to get the next integer with the same ones count.
    See: TAoCP, Volume 4A, 7.1.3 exercise 20
         TAoCP, Volume 4A, 7.2.1.3
    """

    yyyy_y100_1OOO = - xxxx_xO11_1OOO 
    # -x == (~x) + 1
    # ~x               yyyy_y100_0111 (y = ~x)
    #                  + 1
    # -x               yyyy_y1OO_1OOO

    OOOO_OOOO_1OOO = (
        xxxx_xO11_1OOO
        &
        yyyy_y100_1OOO )
    #   OOOO_OOOO_1OOO 

    xxxx_x1OO_OOOO = (
        xxxx_xO11_1OOO
        +
        OOOO_OOOO_1OOO )
    #   xxxx_x1OO_OOOO 

    OOOO_O111_1OOO = (
        xxxx_x1OO_OOOO
        ^
        xxxx_xO11_1OOO )
    #   OOOO_O111_1OOO 

    OOOO_OOOO_OO11 = OOOO_O111_1OOO >> (1 + OOOO_OOOO_1OOO.bit_length())

    xxxx_x1OO_OO11 = (
        xxxx_x1OO_OOOO
        |
        OOOO_OOOO_OO11 )
    #   xxxx_x1OO_OO11 
    
    return xxxx_x1OO_OO11

може стати трохи зрозуміліше, що відбувається

Але у ChatGPT інше ставлення:

1. Проблеми з іменами змінних
Імена змінних (наприклад, xxxx_xO11_1OOO) не дають жодної інформації про їхній зміст або призначення. Вони виглядають як випадковий набір символів.
Це робить код складним для читання та підтримки. Хороші імена повинні відображати суть даних чи процесів.
Пропозиція: Використовуйте зрозумілі імена змінних, наприклад:
def next_combination_mask(mask): # Чітка назва функції і змінної
    # Зрозумілі імена для проміжних змінних
    least_significant_bit = -mask & mask
    increment = mask + least_significant_bit
    xor_diff = increment ^ mask
    right_correction = xor_diff >> (1 + least_significant_bit.bit_length())
    result = increment | right_correction
    return result

2. Коментарі
Коментарі дуже докладні, але описують базові операції, зрозумілі досвідченому програмісту, наприклад, що -x дорівнює (~x) + 1.
Натомість немає пояснення, що саме робить алгоритм.

3. Простота логіки
Хоча алгоритм реалізований коректно, кількість проміжних змінних занадто велика. Це збільшує обсяг коду без додаткової користі.
Деякі змінні, такі як OOOO_O111_1OOO, можуть бути об’єднані без втрати читабельності.

def next_combination_mask(mask):
    """
    Generate the next combination mask for an integer.
    """
    lsb = -mask & mask  # Least significant bit
    increment = mask + lsb
    return increment | ((increment ^ mask) >> (1 + lsb.bit_length()))
Тут можна переписати, наприклад, так:

Работать оно будет так же єффективно как и исходній код?
ві реально считаете что куча нулей и единиц — єто более лучшее именование перемеенной чем а б с и тд?

Работать оно будет так же єффективно как и исходній код?

Якщо писати на Сі, то оптимізатор порішає. Якщо на Python, то у цілому проценти перфомансу нас не дуже хвилюють.

ві реально считаете что куча нулей и единиц — єто более лучшее именование перемеенной чем а б с и тд?

Не знаю, чесно, чукча письменник, а не читач. Переба було генерувати сполуки в покерному калькуляторі, я підгледів алгоритм у тоненькому випуску Д. Кнута та перевів його мовою біт. А вже потім вийшов 4A повністю, звісно що я прочитав розділ про бітові трюки, та дізнався, що цей називається хаком Госпера та побачив відповідну вправу. Ну а потім вже рішення існує, то просто переносиш його з проекту на проект.

Коли ти пишеш сам такий код, то тобі дуже важливо знати, що відбувається на низькому рівні, які біти звідки йдуть та куди. Тому на аркуші у тебе  x = xxxx_x011_1000, a = 0000_0000_1000 і т. п. Перше враження, що придумати більш вдалу назві для змінної bнеможливо, тому що mask_with_highest_one_and_all_zeroes_right_from_that_one виглядає ще гірше.

Але, колись мені прийшла думка, що можливо можна просто закодувати те, що ти пишеш на листі аркуша, в назві самої змінної. Тоді може й код писати буде зручніше?

Якщо писати на Сі, то оптимізатор порішає. Якщо на Python, то у цілому проценти перфомансу нас не дуже хвилюють.

Яснопонятно
в принципе я тоже всегда говорил «просто купите сервер помощнее», но у меня всегда біла табличка «сраказм»

Не знаю, чесно, чукча письменник, а не читач.

\
Понятно. Т.е. мі заходим в тему «код, которій хрен прочтешь» и даем код, которій хрен прочтешь с мотивацией «я не читатель, я пейсатель, оно ме не надо»
Вам не кажется что тут есть какая-то логическая прореха?

в назві самої змінної.

Т.е.

increment

недостаточно понятное и меморабельное... ну ок

в принципе я тоже всегда говорил «просто купите сервер помощнее», но у меня всегда біла табличка «сраказм»
In [1]: %timeit ncm1(0b1010111000)
100 ns ± 2.49 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

In [2]: %timeit ncm2(0b1010111000)
105 ns ± 1.8 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

Тому чекати чи 5 хвилин, чи 5 хвилин та 15 секунд. І це без урахування того, що код, який викликає ncm буде ще розмивати різницю.

increment
недостаточно понятное и меморабельное... ну ок

То поясни мені, що воно означає? Як в анекдоті, прилад, 20, що 20? що прилад? increment для мене означає певне значення, яку буде додано до змінної, зазвичай у циклі. Наприклад, цикл з інкрементом 2. А тут increment означає, що це певне значення, яке утворено в результаті додавання. Ну я це і так бачу без назви increment. По-друге, це ніяк не описує що саме буде там зберігатися, який сенс в цій змінній. Також коли я бачу, що increment ксориться та ориться, це просто маячня. Тому це не що зрозуміла назва, це навіть ще гірше, бо ця назва заплутує.

Тому чекати чи 5 хвилин, чи 5 хвилин та 15 секунд.

15/(5*60)*100% = 5%
вы на каждой элементарнй операции собираетесь терять по 5%?

Тому це не що зрозуміла назва, це навіть ще гірше, бо ця назва заплутує.

Я тоже называю таблички типа [8A9A8645-B6F6-4B71-87B9-A7D686015466]
Нужно абстрагироваться, названия только запутівают и все равно не несут в себе описания храниміх сущностей или данніх
Ну и меня в принципе улібает доставлять головную боль девелоперам, когда они не способні бістро и сразу по имени отличить одну сущность от другой
Ну что єто такое — Header, Lines? Кому это интересно!
То ли дело 8A9A8645-B6F6-4B71-87B9-A7D686015466 и 8A9A8645-B6F6-4B71-87B9-A3D686015466!
Во первых хрен пойми это одна таблица или разные, а во вторых — хрен сразу пойм и где какая
Придает свежести разработке.

вы на каждой элементарнй операции собираетесь терять по 5%?

Якщо я вибрав Python, то у порівнянні з Сі на кожній елементарній операції я вже буду втрачати 1000%. Чому мені хвилюватися за 5%?

Я тоже называю таблички типа [8A9A8645-B6F6-4B71-87B9-A7D686015466]
Нужно абстрагироваться, названия только запутівают и все равно не несут в себе описания храниміх сущностей или данніх

Ні, мені набагато більше подобається таблицю з працівниками назвати equipment. А чому, зрозуміле та меморабельне ім’я, не гірше ніж employees.

Чому мені хвилюватися за 5%?

Чтобы не тратить на задачи в 5 раз больше времени и не покупать сервер помощнее?

Ні, мені набагато більше подобається таблицю з працівниками назвати equipment. А чому, зрозуміле та меморабельне ім’я, не гірше ніж employees

Ну, даже такие названия проще запомнить и отличить друг от друга, чем произвольный набор кракозябр
Скажем, если бы я называл таблицы Альфа, Брава, Чарли, Дельта и т.д. — это было бы не очень говоряще о содержании таблиц, но вполне удобно и различимо, также как i,j,k мало что говорит об назначении переменніх, но вполне удобно для их запоминания, различения и использования

То ли дело 8A9A8645-B6F6-4B71-87B9-A7D686015466 и 8A9A8645-B6F6-4B71-87B9-A3D686015466!

Щойно до мене прийшла думка, що ви геть не розумієте, що відбувається у коді, та чому саме обрані такі імена. Добре, спробую пояснити це детальніше.

Так, припустимо ми хочемо перебрати всі варіанти, як вибрати три цифри з п’яти. Їх має бути C(5, 3) = 5! / 3! (5-3)! = 5 * 4 * 3 * 2 * 1 / 1 * 2 * 3 * 1 * 2 = 5 * 4 / 2 = 5 * 2 = 10.

В принципі їх можна виписати у такому порядку:

0, 1, 2     00111     7
0, 1, 3     01011    11 
0, 2, 3     01101    13
1, 2, 3     01110    14
0, 1, 4     10011    17
0, 2, 4     10101    21
1, 2, 4     10110    22
0, 3, 4     11001    25
1, 3, 4     11010    26
2, 3, 4     11100    28
---
0, 1, 5    100011    35

Перший стовпець це комбінація цифр, другий це відповідна бітова маска, де біт з відповідним номером встановлений тільки якщо він присутній у сполуці, потім йде десятеричне число. Функція ncm це просто перехід до наступного такого числа ncm(17) = 21, ncm(21) = 22, ncm(22) = 25, ...

Останнім це же комбінація з шостої цифри, тобто ми можемо перейти у переборі від C(5, 3) до C(6, 3).

Але виникає, яким чином нам перейти до наступного числа, в якому встановлена та сама кількість біт, що і даному, тобто як написати ncm. Звісно, що просто робити інкремент та перевіряти кількість одиничних біт нам не хочеться, бо якщо почати з одного біту 1 -> 2-> 4 -> 8 -> 16  -> ...  то щоб перейти від  32768 до 65536 нам треба марно перебрати тридцять тисяч чисел.

Яка ж логіка ncm? В принципі доволі проста, припустимо у нас є число 1100111000. Там треба взяти саму праву послідовність одиниць 1100111000, Саму ліву одиницю послідовності зсунути вліво, а ті одиниці, що лишилися, вправо до упора: 1101000011.

Тому, змінна номер один, це xxxx_x011_1000, тут назва ілюструє число, де через x позначені біти, значення яких нам треба зберігти, та приклад числа. З нього нам треба отримати число xxxx_x100_0011_. Або запишемо одне під другим:

x = xxxx x011 1000
r = xxxx x100 0011

Далі, a = x & (-x) це загальновідомий з початкової школи трюк, який лишає лише самий правий встановлений біт (але для початківців я розписав також чому). Тому ми отримаємо

x = xxxx x011 1000
a = 0000 0000 1000
---
r = xxxx x100 0011

тепер, якщо розглянути b = x + a то ми бачимо, що ми можемо легко виконати цю дію в стовбчик, та отримати

x = xxxx x011 1000
a = 0000 0000 1000
b = xxxx x100 0000
---
r = xxxx x100 0011

так, нейромережа запропонувала для b назву increment, але я категорично не можу з цим погодитися, тому що змінна містить незмінні біті, та саму ліву одиниую самої правої послідовності одиниць, яка вже зсунута вліво на один як треба. Це ніяк не increment. А от моя мнемоніка xxxx_x1OO_OOOO як раз це ілюструє (незмінна частина, одиниця, нулі).

Далі, нам ще треба отримати якось дві одиниці справа, тут перше бажання це зробити xor для x та b, що обнулить усі x, та зробить послідовніть одиниць. Це також досить легко виконати в стовбчик:

x = xxxx x011 1000
a = 0000 0000 1000
b = xxxx x100 0000
c = 0000 0111 1000
---
r = xxxx x100 0011

Тут ми бачимо, що одиниці рівно на дві більше, ніж потрібно. Та їх треба зсунути вправо. a.bit_length() (індекс першого встановленого в одиницю біту) це три, а зсунути нам треба на чотири:

x = xxxx x011 1000
a = 0000 0000 1000
b = xxxx x100 0000
c = 0000 0111 1000
d = 0000 0000 0011
---
r = xxxx x100 0011

Ну все, тут ми бачимо, що r можна отримати поєднавши b та d.

Тому в моєму прикладі імені змінних не рандомні, не ґуїди, вони ілюструють, що буде містити змінна на прикладі досить загального приклада. Так простіше писати код, бо не треба зирити в аркуш папіру, які біти в якій змінній чому дорівнюють. Також можливо так простіше зрозуміти код, от мені це як раз було б цікаво дізнатися.

Щойно до мене прийшла думка, що ви геть не розумієте, що відбувається у коді, та чому саме обрані такі імена. Добре, спробую пояснити це детальніше.

Смотрите, если вам приходится писать огромную поясняющую простіню к коду на 3 строчки- есть немалая вероятность того что ві написали говнокод
Єто совершенно не обязательно так, разумеется, 95% битовіх триков нужно пояснять значительно более длительнім путём, нежели трик написан.

А нащо для чергових IT курсів проводити

конкурс на найкращий спагеті-код

?
Думаю там такий код буде по замовчуванню.

# в кінці компілюємо, btw ці дебіли розробники gcc, без додаткових параметрів не дають.exe сприймати як.c...

Яка милота. У вас там мабуть всі генії.

// з якоїсь причини, значення можуть бути мінусовими. чому — я хз, але можна просто 100 додати, і так буде норміс

Без коментарів

А нащо для чергових IT курсів проводити
?
Думаю там такий код буде по замовчуванню.

Конкурс проводився не тільки для учасників наших курсів, а й для усіх бажаючих. ми хотіли провести щось цікаве і незвичне, щось що випадає із звичного флоу життя розробника. Вихід за рамки задач, структури, строгості і абсолютно раціональних алгоритмів. Ну, і звісно, ж для розваги.

Так як, мабуть, і скрізь, в когось код якісний, а в когось аби був. Але навіть в наших студентів так відбуається далеко не завжди.
Якщо говорити про початкіців, багато хто дійсно спочатку кодить «щоб працювало», а хтось одразу прислуховується до лекторів і старається зробити читабельно і якісно, наскільки виходить відповідно до досвіду і можливостей. Тим більше, що часто лектори знижують оцінки за нечитабельний код 😅

Якщо ж говорити про досвічених студентів, то вони одразу пишуть на досвіді код, як правило. Навіть курс у нас є присвячений чистому коду і за іронією ми запросили долучитися лектора до жюрі цього курсу на одному із етапів 😀

Яка милота. У вас там мабуть всі генії.

Усі ситуації і персонажі є вигаданими, а збіги із реальним життям випадкові, це якщо коротко. 😅 Учасники вдавалися до креативності для того, щоб наповнити свої роботи якимось смислом (хоч інколи і дуже дивним, щоб відповідав тематиці конкурсу), додати емоційного забарвлення, передати думки вигаданих персонажів і чому вони так чинили в своєму коді (за що вони з ним так 🤣). Тому не сприймайте це занадто близько.

Без коментарів

Так-так, абсурдність, заплутаність і невизначеність теж в копілочку цього онкурсу 🤪

лайнокод це такий код, який ви без IDE з його літнером не прочитаєте ніяк.
не обов’язково це повинні бути «однострочки»

по роботі бачив багато лайна — найчастіше якшо в фірмі немає кодестайлінгу і умов до коду, то виходить лажа. якщо ше й розробникам в цілом по*** — будуються динозаври яких не розуміють ніхто окрім того хто його породив.
наступній крок це коли починають в такому лайні юзати goto так як межі копіпасту вичерпані і вже вони самі повністю не розуміють як воно прайюе

і вже вони самі повністю не розуміють як воно прайюе

.
Цей вислів актуальний для 99.9% відсотків сучасних проектів.

Если найду архив, то поделюсь оконной библиотекой для М
И это не говнокод, там так принято

На одному з проєктів мого минулого працедавця довелося якось фіксити цікаву багу, пов’язану із spagetti кодом. :)

Древній проєкт на PHP 4, в якості БД Oracle. Для певних вхідних даних поверталася якась дивна помилка, вже не пам’ятаю що конкретно там було, але чесно — воно ніяк не допомагало дізнатися, в чому насправді проблема.

А проблема була в тому, що в файлі типу `report.php`, котрий (як ми всі любимо) був HTML-ем із вкрапленнями PHP, авторка вирішила to go deeper.

І в PHP-шному коді, був виклик виконання SQL-запиту, а всередині цього SQL-запиту `CONCAT`-ами формувався HTML, котрий PHP потім вставляв в основний HTML.

І воно все працювало, допоки не трапилися такі вхідні дані, що на виході з Oracle формувався текстовий рядок із довжиною, що перевищує 8192 символи. :)

SQL-запиту, а всередині цього SQL-запиту `CONCAT`-ами формувався HTML, котрий PHP потім вставляв в основний HTML

Я виконував один GIS-проєкт. Програміст, якого я найняв, написав алгоритм пошуку найкоротшого шляху в графі на вбудованих процедурах Postgres. :)

Програміст, якого я найняв, написав алгоритм пошуку найкоротшого шляху в графі на вбудованих процедурах Postgres. :)

Стесняюсь спросит — а чо тут такого?

1. Тому що алгоритми на графах пишуть студенти, щоб вивчити дискретну математику. Професійні програмісти беруть готові бібліотеки. Його код ми викинули, замінили на NetworkX. Що зекономило на багато годин на розробці та налагодженню його процедур.
2. Він не зміг до алгоритму написати юніт-тести.
3. Він не зміг налаштувати налагоджувач, щоб виправити помилки в алгоритмі.

Я бачу в вас в профілі DBA-досвід. Буду вдячний, якщо підкажете, як налаштувати відладку вбудованих процедур в Postgres. Я не одужав.

Я бачу в вас в профілі DBA-досвід. Буду вдячний, якщо підкажете, як налаштувати відладку вбудованих процедур в Postgres. Я не одужав.

А еше, раз я програмист, я могу посудомойку починить и принтер заправить.
Попробовать вот студию (но я не уверен что в ней есть дебагер) www.devart.com/dbforge/postgresql
Воспользоваться старіми добріми «принтами» — раз ві знаете про библиотеки, то знаете и про такой дедовский способ отладки при отстуствии дебагера

Його код ми викинули, замінили на NetworkX

Точнее надо ставить ТЗ
ВІ изначално оговаривали условия? или сказал и «вот у нас есть посгтре, ті же у нас датабейз девелопер, иди считай графі»?

мі когда-то для себестоимости считали на т-скл матриці на 150+ переменніх, забавно біло

ВІ изначално оговаривали условия?

Я програміста найняв як Python-розробника. Та ми розробляли плагін до QGis який був на Python. Та й ми Postgres використовували лише для зберігання геопросторових даних (postgis). До того випадку я не знав, що можна на pgSQL таке написати. Але він мені показав статтю якогось китайця, який алгоритм Дейкстри написав на pgSQL. :)

Я витратив десь 8 годин, щоб підʼєднати дебагер до Postgres (наче pgAdmin це вміє), але я не одужав, не маю такого досвіду. Тому й сказав йому брати готові алгоритми на NetwokX та писати завдання за допомогою нього. Там хоть алгоритми вже перевірені, та є можливість покривати юніт-тестами та дебажити.

о того випадку я не знав,

Понятно

який алгоритм Дейкстри написав на pgSQL

У меня на тскл есть мд5 (когда еще были только ESP) и рисование множества мандельбротта (ну тут чисто для поржать)

я не одужав

Понятно

Там хоть алгоритми вже перевірені, та є можливість покривати юніт-тестами та дебажити.

Ну, тут спорить не буду, єто важно

Я програміста найняв як Python-розробника.

Ну, я біл програмистом под виндовс на делфи и скуле
Когда пришел на новую работу то первая таска біла под линухом, на сайбейзе и си с перлом.
меня єто не удивило ни разу.

Лайнокод — це коли ти відкриваєш код та за пʼять хвилин йдеш шукати нове місце роботи. :)

Ну це ближче до обфускованого коду, ніж до лайнокоду. Навіть змагання такі є: en.wikipedia.org/...​Obfuscated_C_Code_Contest

Так ви праві, є такий момент, це дуже схоже.

Підписатись на коментарі