Стендап Сьогодні

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

Підписатись на RSS · 📢 Канал в Telegram @stendap_sogodni

14.12.2024

(Не)Дев-Адвент 2024: "вихідний": заміна акумулятора

⚡ Заміна автомобільного акумулятора це одна з найцікавіших у своїй нудності задач. Робиться раз в 3-5-7 років. Практично не впливає на поведінку автомобіля. Втім, ігнорувати несправний акумулятор не вийде аж ніяк.

🥶 Акумулятори стихійно виходять з ладу, коли холодає. Для здоровʼя акумулятора гірше жар двигуна, ніж зимний холод. Але на холоді складніше крутити стартер двигуна, от і проявляється. (Власне, чим холодніше місцевість, тим пильніше треба обирати акумулятор.) Далі залишається прикурити від сусіда, та їхати за новим. (А допомогти сусідові — теж святе діло.)

📏 З одного боку, акумулятор — не та деталь, де має сенс обирати з альтернатив. А точніше, майже певно можна ставити те, що буде в наявності в сервісі, куди ти приїдеш. Але! З іншого боку, в автомобільних акумуляторах значно більше різновидів, ніж “пальчикові чи пальчикові тонкі”. Головне — це, звісно, розмір та місткість, які повинні збігатися з тим, що є. Але потім виявляється, що є й різні технології (SFA/EFB/AGM), і стандарти корпусів (євро/азія), і розташування полюсів. А ще окремі вимоги накладає наявність старт/стопу. Одним словом, обирати все ж доведеться, та замовити акумулятор навмання буде ризиковано.

👀 Так само ризиковано покладатися на документацію, замість того, щоб відкрити капот та подивитися, а ще й поміряти рулеткою. Для моєї CX-5 я буквально ніде в інтернеті не бачив специфікації акумулятора, яке збігається з тим, що мені поставили на заводі. Ну то й що — головне замінити сумісним по факту.

🤔 Про що це я… А, взагалі от: сьогодні я міняв акумулятор. А за день до того було інше. Я трохи зʼїхав з системи задач, а точніше, з виконання задач, тобто вони напливали в контекстах. Та вчора вирішив все ж зробити хоч щось, зарядив FVP по 43 задачах в контексті “комп” та обрав найпріоритетнішу: “підібрати новий акумулятор”. Та сьогодні, коли машина не завелася, відчував себе підготованим, а не розгубленим. Ось так FVP допомагає дійсно зробити головне, навіть без примусового призначення пріоритетів та сортування.


13.12.2024

Дев-адвент 13: воно працює!

Взагалі сьогодні встановив macOS 15.2, а там таке… Відкриваю увечері XCode, а він каже — не можу відкритися - “XCode is updating”. Перший раз таке бачу… Як виявилося, це значило, що нова версія вже завантажилася та тепер розпаковується, на що пішло, мабуть, з пів години мого золотого часу. Класика XCode.

Але. Зробив тут знакову річ, яку давно хотів. Оскільки я сон тегаю автоматично з даних Apple Health (це не обовʼязково, але дуже зручно), але проби під час сну генеруються, як завжди, випадково. А якщо проба припадає на час сну, то вона отримує тег “сон”.

Про що це я… Виходить. для сну можна перевірити точні виміри та приблизні, обчислені з випадкових проб! Та підтвердити (чи спростувати) надійність методу. Затамувавши подих, дописав код для отримання сирих даних з Apple Health та побудови графіку.

(Тут не так все просто, до речі, бо в Apple Health всі дані сидять в формі своїх “проб”. В проби є час початку та кінцю. Та головне, що проби Apple Health можуть перетинатися за часом. Наприклад, коли виміри сну приходять з двох гаджетів з різними системами, то вони утворять подвійні записи. Але коли вже це знаєш, то достатньо просто дедублювати: відсортувати все за часом початку, а потім стежити за останнім покритим часом та відтинати все, що раніше за нього.)

І що виходить: воно працює! Реальна кількість сну майже завжди в інтервалі впевненості. Та хоч шуму на “випадковому” графіку немало, загальний тренд зберігається (у випадку сну — тренду на стабільність.) Такий графік буде доступний всім користувачам для тегу “сон”, щоб власноруч переконатись в справжності методу.

Рухаємось далі.


12.12.2024

Дев-адвент 12: пріоритети на мапі

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

Наприклад: тег “комп” містить 50 годин, а “робота+комп” 40 годин. За базовим алгоритмом “робота” завжди буде в “компі”. Ще гірше, коли є ще “робота+телефон” без компа — тоді на загальній мапі ми побачимо роботу тільки по шматочках.

А от якщо “робота” отримує підвищений пріоритет, тоді в нас буде “робота” на 40 годин, всередині неї “комп” - 35 годин, “телефон” - 5 годин. А поруч з “роботою” будуть залишки “компа” - 10 годин, та залишки “телефона”.

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

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

Зазначу ще, що почав виводити на мапі число годин… воно, звісно, визначається з дуже широким інтервалом. Наприклад, 51 год сну це насправді плюс-мінус 12. Але читати “39..64 год” мені не наочно. (Хоча то на мапі; в інших місцях як-от графік тегу по тижнях я його показую.)


11.12.2024

Дев-адвент 11: покращення деревної мапи

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

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

✌️ Помітив, що деколи заходиш в тег, а всередині — точно такий саме за розміром. Тобто ці два теги завжди згадувалися разом — та ніякої вкладеності немає. Тепер, щоб було більш наочно, виявляю такі випадки та групую в один вузол дерева. (Як виявляю? Дивлюся, коли в “батька” та “дитини” однакова кількість згадок.)

🫥 Також помітив, що під час викладки дітей тегу я втрачаю випадки, коли “батько” згадувався наодинці. (Нагадаю, що “діти” в моїй моделі — це теги, які згадувалися разом з “батьком”, але менше за нього разів.) Тепер підсумовую згадки всіх дітей разом, та якщо згадок батька ще більше, то для решти додаю ще одну “дитину” для “батька окремо”.

🔤 Та почав наповнювати мапу текстовим змістом. Це наче й нескладно, але потрібно спочатку переконатися, що текст влізе. Для того є цікавий метод resolve(), який точно вимірює текст в даному графічному контексті. (В JS/CSS такого часто не вистачає!) Потім перевіряю: якщо тексту забагато, залишаю тільки емодзі. В будь-якому разі планую ще додати тултіпи з повним текстом до кожного тегу.


10.12.2024

Дев-адвент 10: редагування тегів... та AWS Lambda

Сьогодні не те щоб найцікавіші функції, яких в кожному проєкті вистачає. (Хоча гарно, що простий CRUD на Swift UI робиться без зайвих перешкод.) Тому розкажу про іншу цікаву знахідку.

На вихідних одна AWSLambda накрилася та почала замість 15 секунд тривати 15 хвилин, тобто вилітати за браком часу. Лямбда ця запускається за розкладом та перекачує дані з Kafka ще кудись. Ніяких підстав бути такою повільною в неї не було.

В Кафки особливий підхід до споживачів, а саме: щоб уникнути повторної обробки даних, споживачі в групі ділять між собою потік задач. (Саме для того потоки — теми - topic діляться на розділи - partition.) А це своєю чергою значить, що коли споживач уходить, або зʼявляється новий, Кафка негайно виконує перебалансування групи. А якщо споживач просто відвалився, то щоб переконатися в його відсутності потрібний ще й тайм-аут. (Тому важливо завжди виходити з групи ввічливо.)

(До речі, це значить, що лямбди — не краще поєднання з Кафкою; сталий сервіс однозначно природніше. Втім, в моєму випадку на цілий сервіс задач було мало.)

Що, гадаю вийшло: в якийсь момент чергова лямбда “спіткнулася” - можливо, в Кафці йшли роботи, які самі викликають перебалансування — та зависла. Після того її наздогнала наступна лямбда, та вони почали ділити між собою групу в Кафці. Далі почався каскадний ефект: поки старі лямбди запускалися наново, зʼявлялися ще й нові та викликали нове перебалансування, причому кожна з них мала необмежену кількість спроб, тому вони нікуди не зникали, та проблема тривала аж до ручного втручання.

Виявилося, що запуск лямбд за розкладом — не така тривіальна справа. 1)тайм-аут лямбди повинен бути менше за інтервал розкладу - це, думаю, очевидно. 2) в налаштуваннях рівночасності треба вказати 1 рівночасне виконання, щоб уникнути набігання. 3) в налаштуваннях надійності вказати 0 повторних спроб, бо лямбда все одно запуститься за наступним розкладом. 4) там же ж вказати мінімальний вік подій, бо знов-таки немає сенсу обробляти старий розклад, коли завжди буде новий.


09.12.2024

Дев-адвент 9: кольори та навігація дерева

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

🎨 Щоб там не було… Додав таки кольори. Чесно скажу, спочатку я хотів обирати колір з емодзі. Знайшов декілька бібліотек, які вміють це робити (хоч і на JavaScript). Внутрішній програміст так і рвався реалізувати такий нівроку розумний алгоритм. Проте я розглянув приклади в тестері, та зрозумів, що кольори з емодзі не будуть достатньо різноманітними. Тобто це цікавий спосіб додати краплю гармонічного кольору, але ніяк не для розрізнення тегів у звіті.

🎲 Тому повернувся до випадкових кольорів (звісно, з можливістю редагування.) Формула гармонічних випадкових кольорів в мене вже є. Виходить теж не ідеально, зате зусиль встократ менше.

🪜 А навігація за деревом працює так: за тицем в мапу спочатку знаходжу, в який регіон він відбувся (бо мапа — це Canvas.) Якщо це тег з вкладеністю, то зберігаю його в масив “шлях” та переобчислюю мапу, але вже для вкладених тегів. Без анімацій поки грубувато, але працює.

🖋️ Наступним кроком буде, певно, доповнення мапи текстовою інформацією: фактичними цифрами, для початку.


08.12.2024

Дев-адвент 8: TreeMap

Вчора вже засинав та згадав чудову програму DaisyDisk - вона візуалізує зміст диска через вкладені “пироги”. Та тут же ж згадав іншу чудову програму - SequoiaView - яка років 20 тому будувала мапу диска методом TreeMap. (А якщо вам цікавий сучасний аналог для Windows, то є WinDirStat, я й досі ним користуюся.)

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

Якщо перший раз чуєш про “деревні мапи”, то це розбиття прямокутника на прямокутні частини, які пропорційні заданому розподілу ваг. Зазвичай розбиття рекурсивне. Є декілька алгоритмів для побудови TreeMap. Наївні алгоритми просто ріжуть в одному напрямку та роблять з прямокутника вафлю, що ніяк не наочно. Я обрав Squarify - це жадібний алгоритм, який намагається робити частини квадратніше. Перший результат — зверху.

Про побудову дерева тегів поки придумав робити так: перебираю теги від більшого до меншого; кожний тег “зʼїдає” всі збіжні (та менші) використання тегів; після чого продовжую з рештою тегів. Так деякі менші теги будуть розбиті між батьками: наприклад, може бути “комп/дзвінок” та “телефон/дзвінок”, зате буде досягнуте повне покриття без перетинів. Та, мені здається, так буде зручно досліджувати витрати часу.

Якщо ще додати параметри “завжди показувати тег на верхньому рівні” або навпаки, “ніколи не показувати”, то це покриє крайні випадки — наприклад “робота” значно важливіша, ніж “коворкінг/робота” та “вдома/робота”.


07.12.2024

Дев-адвент 7: щотижневий звіт

📜 Перейшов до звітової частини застосунку. (В мене частина для вводу даних готова на 90%, а для виводу, тобто звіти, десь на 20%.) Зрозуміло, що без гарних звітів всякий таймтрекер буде марними витратами часу.

🥧Гадаю, першим звітом повинен бути “а куди все ж йде мій час?” Оскільки мій метод збирає статистику 24 на 7, то й звіт можна дати стовідсотковий, і це неперевершено. (Є нюанс, що одним з сегментів буде “дані відсутні”, але це теж показник — наскільки ти уважно взаємодієш з трекером.)

⬆️ Початок — зверху. Але відразу спадає в очі головна проблема такого звіту: теги перетинаються, бо кожна проба містить декілька тегів. Та це просто нівечить графік: “макротеги” займають все коло, а деталізовані збиваються в маленьку купку.

🌳 Наприклад: в мене є тег “робота” та теги для проєктів. Але це не є простим деревом: також є теги “комп” та “телефон”, “розробка” та “налагодження” тощо. До того ж я на початку закладав в модель дерево тегів, але знайшов його надто складним в роботі. Бо все ж задачею номер 1 є легкий та швидкий ввід даних. Втім, це значить, що доведеться добудувати цю структуру на виводі.

🤔 Поки в мене тільки абстрактні ідеї, як це робити. Якщо дивитися на цей графік, то, певно, має сенс дивитися, як великі теги розрізати на менші; наприклад, якщо є 30 відміток “робота” та є з них 10 проб “фронтенд”, то можна показати сектор “10: фронтенд” та “20: робота (інше)”; продовжувати до досягнення приємних результатів: наприклад, щоб найбільші 10 секторів були якнайменшими.


06.12.2024

Дев-адвент 6: внутрішня структура SQLite

Сьогодні часу не дуже багато було, тому задовольнився тим, що розібрався у внутрішній структурі SQLite, щоб зрозуміти її в контексті локальної бази даних, де сидітиме більшість даних застосунку. (До речі, в документації SQLite достатньо цікавих технічних деталей, щоб просто читати її для загального розвитку. Рекомендую.)

Щодо представлення записів (рядків таблиці): воно компактне, наскільки це можливо, та чимсь нагадує формати на кшталт msgpack. Для кожного значення відзначений свій тип, як памʼятаєте, але маркер типу складається з одного байту та навіть може містити в собі значення NULL, 0 та 1. Цілі числа зберігаються зі змінною шириною. В іншому, зрозумілий компактний формат, що інколи може бути важливо. Наприклад, JavaScript на ті ж дані витратить в рази більше памʼяті.

Щодо структури таблиць: тут все напрочуд просто та, на відміну від, наприклад, PostgreSQL, однозначно. Таблиця зберігається як Б-дерево. (Б-дерево це дерево, яке мінімізує свою висоту: тобто для БД, кількість вузлів-сторінок, за якими потрібно пройтися від кореня, щоб знайти запис.)

В кожного запису є ідентифікатор (rowid) - ціле число — та воно й використовується для побудови дерева. Цікаво, що якщо зробити ключем поле типу INTEGER PRIMARY KEY, то воно фактично й буде містити цей rowid, що пришвидшить пошук за ключем. (В іншому разі, спочатку потрібно знайти rowid за індексом, а потім вже запис за rowid.)

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

Нарешті, в SQLite вся база даних міститься в одному файлі, він розбитий на сторінки (зазвичай по декілька КБ), та в памʼять можна завантажувати тільки ті, що потрібні. Що може стати дуже корисним, якщо даних багато, але ми не плануємо всі їх читати постійно.

Отже, що ми фактично отримуємо, порівняно зі збереженням даних в простих структурах в памʼяті? Прозорий спосіб заощадити на памʼяті (відвантаженням на диск), та також можливість швидко шукати з логарифмічною складністю — особливо за наявністю індексів.


05.12.2024

Дев-адвент 5: SQLite - база з динамічною типізацією?

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

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

⚠️ Сторінка про типи даних в SQLIte трохи спантеличує, якщо очікувати традиційної для БД моделі типів, та не читати першого речення, де сказано, що на відміну від всіх інших баз, в SQLite стовпчики не мають типу, а мають тільки схильність (affinity) до типу. Про це далі. У значень, втім, типи є.

🤔 Типів лише пʼять: ціле число, число з рухомою комою, текст, блоб та нуль. (Мало? Авжеж.) Та хоч в SQLite є підтримка дат, JSON, GIS, але все це зберігається в одному з цих типів. Дати — особливо дивно, бо функції дат приймають як рядок, так і число. Тобто ми легко можемо зберігати дати текстом та навіть не помітити.

🤯 А зі стовпчиками ще дивніше, бо команда CREATE TABLE дає можливість вказати як тип наче будь-яку назву. Яку ж “схильність” отримує стовпчик? Це (мені навіть дико це писати) визначається за змістом назви… наприклад, якщо в назві є підрядок INT, то буде схильність INTEGER. Що для мене важливо, DATETIME теж можна вказати типом, а схильність при цьому буде NUMERIC. Що наче гарно, але ні.

😱 Бо навіть зі схильністю NUMERIC, якщо писати в стовпчик рядок, то він буде конвертований в число тільки коли є числом. А якщо це рядок дати, то він рядком буде і збережений! І наче все буде працювати, тільки 1) з гіршою швидкістю та витратами памʼяті, 2) з загрозою дублікатів, якщо ми також писатимемо дати в чисельній формі, 3) з незрозумілим сортуванням. Я в шоці.

Підсумок: зробив дати чесним полем INTEGER, увімкнув режим STRICT, де тип значення повинний збігатися з типом стовпчика, та переналаштував кодування дат в клієнті. Порядок.