Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
26.12.2023
Дев-адвент 26: історія
Сьогодні історія набула приблизно такого вигляду, якою я хочу її бачити. Також додав деяку статистику (а для того треба було придумати, куди.)
З технічних моментів: у Swift є такі “обчислювані властивості”, які виглядають, як властивість з ініціалізатором. Я, чомусь, наївно думав, що такі властивості зберігають своє значення. Але насправді нічого такого немає, обчислювана властивість — це звичайна функція, відрізняється лише синтаксис її виклику. Довелося трохи порефакторити.
А ще виправив алгоритм, щоб він не утворював послідовні проміжки стабільності. Була в нього така особливість, що якщо вага дуже повільно зростає або зменшується, то виходить, наприклад, три тижні однієї “стабільної” ваги, а потім два тижні вже іншої “стабільної” ваги. Виправити було дуже легко — просто, буквально, не дозволяю алгоритму брати два стабільних відрізки підряд. В результаті перебір знайде наступний за оцінкою розвʼязок та все буде добре.
Історія буде ще цікавішою, коли там зʼявляться коментарі та світлини, але це не для MVP.
25.12.2023
Дев-адвент 25: яка статистика цікава?
Нарешті дістався до візуально цікавого — збагачення історії визначними точками. Ось поточний перелік ідей по тому, що можна вчепити, навіть без доступу до додаткових джерел інформації та ручного вводу:
-
Найбільша та найменша вага — за весь час та за рік. Звісно, з датами. Це, певно, найцікавіше з всього.
-
Найдовший проміжок — набору, схуднення, стабільності. Альтернативно: проміжки з найбільшою різницею. Довжина вказує на стійкість намірів, але різниця — на їхню успішність.
-
Перетин визначних значень: круглих дат, категорій індексу маси тіла. Наближення до найбільшої та найменшої ваги. (Також колись я додам можливість вказати значення-ціль, тоді можна буде відзначати наближення до цілі.)
-
Особливі дати — свята та сезони. З Apple Health можна навіть день народження взяти. (Інженер в мені каже, що можна інтегруватись з календарем, але насправді то надто складно та можливість додавати коментарі на дату цілком перекриває таку потребу.)
Перелік такого роду в голові виглядає довжелезним, аж поки не спробуєш його записати. А ще треба придумати набір коментарів до поточного стану.
24.12.2023
Дев-адвент 24: звʼязаний список
🎄 Всіх з Різдвом, хто святкує! Дізнався, що в App Store вже кілька років немає різдвяних канікул, та публікувати програми можна протягом всіх зимових свят. Що приємно, бо до Нового року я як раз дотягну решту функціонала.
Сьогодні нарешті закінчив роботу над збереженням стану системи та інкрементальним оновленням.
Як один з останніх нюансів, знайшов ефективнішу схему зберігання розвʼязку свого алгоритму. Як я писав, алгоритм будує розвʼязки для більших послідовностей через розвʼязки для коротших; але раніше я зберігав кожний розвʼязок наново. Виходила купа даних з квадратичним характером зростання.
Так от, зрозумів, що можна буквально зберігати кожну нову послідовність через номер коротшої послідовності, з якої вона побудована; виходить такий звʼязаний список. Тепер розмір даних зростає лінійно — та ще й без зайвого копіювання. Ба більше, я майже впевнений, що коли ми вчили динамічне програмування, то так воно й зберігалося.
Наступний етап — нарешті, доповнення історії різними цікавими статистичними фактами.
23.12.2023
Дев-адвент 23: правильне керування складністю
Трохи застопорився з оптимізацією обчислень. Хотілося розділяти графік на відрізки, коли є великі розриви в даних (більш як два тижні.) Намагався доробити алгоритм так, щоб він ділив послідовність на декілька. Це є набагато складніше, ніж мені спочатку здавалось.
Висновок 1: я запізно зрозумів, що відрізки між розривами, фактично, ніяк між собою не повʼязані, тому задачу можна розділити на 2 етапи: знайти безперервні відрізки та зробити аналіз по кожному окремо. Натомість я працював над розширенням алгоритму, щоб він враховував розриви.
Ніби в теорії те саме, але в залежності від перспективи можна отримати або ускладнення логіки, або акуратний підхід “розділяй та володарюй.”
Висновок 2: взагалі в мене вже є алгоритм, який працює з розривами — я про нього писав ще 2 тижні тому. Але той алгоритм “не чистий”, бо не робить з розривів “спеціальний випадок”. На практиці це майже нічого не значить - “спеціальність” можна показати вже в інтерфейсі.
Там був ще аргумент за оптимізацію пошуку відрізків, бо якщо ти натрапляєш на розрив, то раніше можна не шукати. Але, цей пошук і так і так має лінійну складність, тож багато не оптимізуєш.
Підсумок: треба було розв’язувати проблеми одна за одною, а не замахуватись разом все оптимізувати.
22.12.2023
Дев-адвент 22: async/await у Swift
Як і в JavaScript, у Swift є синтаксис async/await
. Він зʼявився два роки тому - Swift молода мова, та не стримується від капітальних змін.
Async/await - одна з моїх улюблених особливостей JavaScript, та від Swift очікував чогось схожого. В базовому розумінні це дійсно одна й та сама фіча — вона надає можливість писати асинхронний код в послідовному стилі. Та як і в JavaScript, так і у Swift це робить код легшим до розуміння.
Втім, є важлива різниця. У Swift немає загально прийнятної абстракції Promise. Попереднім способом побудови асинхронного коду були колбеки. Проблема в тому, що немає способу замінити колбек на async/await
- для цього потрібно переписати функцію, яку будемо викликати. Тому бібліотеки з підтримкою async/await
зʼявляються тільки поступово.
Та ще й потрібно дізнатися, що альтернатива існує та знайти її. Наприклад, в Apple Health для переходу на async/await
потрібно замінити HKSampleQuery на HKSampleQueryDescriptor.
Так що тепер ще більше ціную те, як JavaScript стандартизувався навколо Promise, бо вони не просто замінили колбеки, а ще й стали універсальним протоколом, який можна комбінувати як заманеться.
21.12.2023
Дев-адвент 21: все програмування — це побудова абстракцій
Успішно розвʼязав вчорашні проблеми та ще декілька — завдяки тому, що впровадив правильну абстракцію для дат.
Так, я хотів спочатку зробити тип “календарна дата”, як я робив для JavaScript. Але вранці придумав простіше та краще рішення: перекласти дати в порядкові числа.
Взяв за 0 перше січня 1970 - початок “епохи Unix”, і далі послідовно. Поточний індекс — лише 19712… тобто можна було б взяти початкову дату набагато раніше. Вийшов ось такий маленький клас.
Така абстракція чудово підходить для моїх обчислень, бо вони відбуваються за послідовністю дат. Відстань між датами обчислити легко. Зберігати тривіально.
Це порівняно з використанням стандартного класу Date
, який насправді зберігає момент в часі, та з яким потрібно було завжди памʼятати, чи звів я момент до початку дня, а також ризикувати натрапити на проблему літнього часу, або високосну секунду…
Проблема не в тому, що це неможливо, а в тому, що доводиться тримати в голові інформацію, зайву для поточної задачі. В програмуванні так відбувається часто, та розвʼязок один — рефакторити так, щоб бачити тільки ті подробиці, які потрібні. Вирівнювати абстракцію.
Та в тому, думаю, й полягає успішність сучасного програміста — не хитрі алгоритми або енциклопедичні знання бібліотек, а вдале керування абстракцією.
20.12.2023
Дев-адвент 20: інкрементальне оновлення — це складно
З вчора, як писав, залишилась задача зробити інкрементальне оновлення. Тобто нові виміри я вже зрозумів, як отримувати, але ж треба їх застосувати до вже обчислених з мінімальними витратами.
Оновлення приходять з HKAnchoredObjectQuery
у вигляді двох списків: доданих записів та видалених записів. Ну, хоч редагування в Apple Health немає. Зате користувач може додати або видалити записи з будь-якого місця історії — наприклад, імпортувати старі дані.
В загальному вигляді інкрементальне оновлення зробити складно. Тому беру урок у Redshift та використовую інкрементальне оновлення тільки в прямолінійному випадку. Для інших випадків роблю повне обчисленння, яке вже готово і працює.
Для мого алгоритму “прямолінійний” випадок — це поява даних на наступні дати. В тому сенсі, що така зміна і є інкрементальною. Але також і в тому, що саме таким буде типове щоденне оновлення.
Мій внутрішній перфекціоніст каже, що належно обробити кожну можливу ситуацію та зробити алгоритм інкрементальним для всіх випадків. Втім мій внутрішній менеджер заперечує: на це немає ані часу, ані потреби.
До того ж оновлення нелегко зробити й в простому випадку, бо треба боротися з датами, а типу календарної дати у Swift немає, і може варто було б вже зробити його власноруч.
19.12.2023
Дев-адвент 19: збереження стану
Як вже казав, завантаження даних з Apple Health та їх обробка займають невеличкий, але помітний проміжок часу. Це значить, що старт програми гальмує. Розвʼязок простий — зберігати вже оброблені дані, щоб не обчислювати все наново.
Саме збереження-відновлення зробити нескладно. Обрав як метод серіалізацію в JSON. У Swift прості структури можна серіалізувати, якщо навісити на них тип Codable - решта робиться автоматично. Я вже писав, що запис у файл — це метод на рядку, що дуже дивно. Насправді ще є клас FileHandle з більш звичним API читання-запису.
Нехитре збереження стану в файл JSON роблять завантаження під час запуску непомітним. Ніби справа зроблена — але не до кінця. Потрібно ще доповнити дані тими, що могли зʼявитися після останнього запуску програми.
На цей час знайшов брутальне рішення — роблю обчислення в повному обсязі після того, як завантажу початковий стан. Такий підхід вигідний своєю простотою, але даремно витрачає батарею, та й займає час — тобто оновлення даних все ж доведеться чекати, хіба що не дивлячись в порожній екран.
Проте хочеться натомість отримувати тільки нові дані; при чому алгоритм готовий їх прийняти без повного переобчислення. Але як взяти нові дані? Виявилося, що в Apple Health є спеціальний тип запиту саме для отримання нових (для нас) даних - HKAnchoredObjectQuery. Цей запит повертає значення-“якір”, яке можна навіть зберігати між запусками програми. Також його можна виконувати в режимі спостереження, тобто замінити HKObserverQuery
, про який я писав.
18.12.2023
Дев-адвент 18: заповнення порожнеч на графіку
Мій графік ваги (та весь підхід) розрахований на щоденні зважування. Але, звісно, трапляється таке, що зважуватися не виходить — наприклад, в подорожах. Виникає питання, що робити з графіком, якщо на цей день немає реальних даних.
Хоч “реальне” значення ваги обчислюється за методом рухомого середнього, але складники цього середнього завжди беруться з попередніх дат — тож якщо даних на дату немає, отримаємо не інтерполяцію, а просто майже сталу лінію — обчислене значення наближатиметься до останнього відомого, от і все.
Коли йдеться про найсвіжіші дні, то можна взагалі нічого не малювати, та чекати, поки ми повернемось з відпустки та внесемо наступну вагу. А от з порожнечею між відомими значеннями вже можна щось зробити.
Є два випадки. Якщо порожнеча довша за вікно рухомого середнього, то наступне після неї значення буде, фактично, початком нового графіку. Таку порожнечу краще нічим не заповнювати, а навпаки, показати як розрив. Це трохи технічно складно, тому поки я цим не займався.
Інший випадок — коли значення після порожнечі все ще коректується минулими значеннями (як на ілюстрації.) Як можна побачити, наївний алгоритм просто робить стрибки в графіку там, де зʼявляється нова інформація. Більш природно інтерполювати обчислене, “реальне” значення, що я й зробив.
Все це суттєво складніше, ніж просто зʼєднувати точки лінією, як це робить Apple Health. Зате отримуємо: 1) наочну динаміку та 2) відсутність штучних стрибків, які цю наочність підривають та примушують нас вмикати мозок.
17.12.2023
Дев-адвент 17: фокусування
Зрозумів, що якщо я хочу зробити привабливий MVP та ще й в найближчі 8 днів, доведеться обмежити функціонал до дійсно мінімального. Це значить, залишити тільки графік та аналітику по ньому.
*(Треба памʼятати, що продукт з однієї чудової функції більш привабливий ніж з однієї чудової та пʼяти недороблених. Дивись ефект менше-краще.)_
Тож зараз працюю над генерацією текстів пояснення за результатами аналізу (такими, як “скидаєш вагу з 10 грудня, такими темпами досягнеш мети через 2 місяці.”.)
До речі, все ж відмовився від регресії як оцінки проміжків. Окрім того, що це повільно, як писав вчора, то ще й результати не краще ніж мій попередній метод. В деяких випадках - навіть гірше, бо найкраща регресія в мене не обовʼязково закінчувалася на піках графіку.
А регресія знадобиться для визначення поточного тренду. Тобто для того, для чого її придумали.