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

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

Підписатись на RSS
📢 Канал в Telegram @stendap_sogodni
🦣 @stendap_sogodni@shevtsov.me в Федиверсі

14.12.2023

Дев-адвент 14: Експорт в CSV

Отже, експорт даних в CSV. Зробив. Фіча ця збирається з послідовності компонентів, як з кубиків.

csvString.write(
  to: URL.temporaryDirectory.appending(path: "diet.csv"),
  atomically: true,
  encoding: .utf8
)

Так, запис у файл це метод на рядку. А ще у Swift шляхи до файлів є URL.

Імпорт буде складніше, бо CSV доведеться інтерпретувати.


13.12.2023

Дев-адвент 13: лінійна регресія

Згадав ще один спосіб розбивати графік на відрізки. А саме, розглядати кожний відрізок як окрему лінійну регресію.

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

Що тут таке ціна? На перший погляд, все знову очевидно — ціна — це сума розбіжностей між наближеннями з регресії та реальними значеннями. Але цього недостатньо, бо, наприклад, для двох точок регресія дає ідеальне наближення, та й з більшою кількістю буде віддавати перевагу відрізкам, де виміри однакові. (Про збіги ваг взагалі можна окремий пост.) Тобто окрім розбіжностей, ціна має враховувати довжину відрізка; чим довше, тим більше ми можемо пробачити.

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

Поки знайшов гарну інкрементальну формулу для обчислення регресії. Інкрементальна формула для мене дуже важлива, щоб з квадратичної складності не отримати кубічну.

А ще зрозумів, що експорт даних в CSV суттєво розблокує мені дослідження, бо тоді можна буде робити експерименти не на Swift, та результати бачити не в консолі, а в Екселі — який для таких експериментів встократ краще пристосований.


12.12.2023

Дев-адвент 12: фокусування

Все ж моєю метою є випустити продукт до різдвяних канікул в App Store. Тому роблю паузу, щоб зібрати з фіч цілісну картинку.

Пропозиція програми — допомогти з контролем ваги. Для цього вона витягує з вимірів ваги якомога більше розуміння. Це: побудова графіка за методом рухомого середнього (це не є придумав, книжка є);автоматичне визначення періодів схуднення, стабільної ваги та набору ваги; та побудова тренду.

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

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

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

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


11.12.2023

Дев-адвент 11: сповіщення

Раз мій метод ведення графіку потребує щоденних зважень, про них потрібно нагадувати. Для того є в iOS проста система локальних сповіщень.

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

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

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

Далі, маємо зберігати власні налаштування того, чи включені сповіщення. Для того ідеально підходить база даних UserDefaults. У Swift робота з нею виглядає приблизно так:

.onAppear {
    isEnabled = UserDefaults.standard.bool(forKey: "enabled")
}.onChange(of: isEnabled) {
    UserDefaults.standard.setValue(isEnabled, forKey: "enabled")
}

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


10.12.2023

Дев-адвент 10: Прибирання

Трохи ресурсів, якими я користуюсь для розробки на Swift.

PS: чи знаєте ви, що у Swift є офіційна співачка? Може то й занадто розкрутки від Apple, але ж музика чудова! 💃


09.12.2023

Дев-адвент 9: виявлення проміжків стабільності

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

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

Звучить, воно, може, й просто — стабільність це коли вага незмінна. Але маленькі коливання нас теж влаштують. Але маленькі коливання — це скільки? Та навіть маленькі коливання мають бути збалансованими.

По-перше, відмовився від ідеї “ціни” відрізка як математичної функції. Довго намагався (в чудовій програмі Grapher) винайти формулу, яка виконує всі мої вимоги, але реально набагато простіше зробити парою if. Відповідно, відрізок або стабільний (за рядом ознак), або направлений. Щоб перевірити стабільність, дивлюся на мале значення зміни ваги, а також щоб відношення між зміною в обидва напрямки було майже 1 (тобто була збалансованість).

Поки є проблемний випадок: за одною сумою змін стабільність можна виявити тільки на коротких проміжках. На проміжках більших десь за 3 тижні сума маленьких змін накопичується настільки, що вже незрозуміло, чи то результат непомітних коливань, чи суттєвого набору, а потім скиду ваги. Що вже стабільністю не назвеш. Тому зʼявляються відрізки стабільності, які йдуть один за одним, щоб не перебільшувати межу по сумі. А якщо нормалізувати суму на тривалість, тоді інша проблема — будь-який достатньо довгий проміжок часу виглядає як стабільність.

Треба придумати ще додаткову метрику, щоб це розрізняти. Можливо, підійде дельта між найбільшим та найменшим значенням за проміжок.


08.12.2023

Дев-адвент 8: збереження додаткової інформації

Apple Health - не універсальна база даних, та окрім ваги туди нічого корисного не запишеш (Ну, хіба що ще обхват талії. Чому саме талії, а не будь-якої іншої частини тіла? Бо, як я розумію, обхват талії використовується для виявлення діабету. Порівняно з цим, для відстеження харчування є метрика для кожного мікроелемента. Мабуть, записувати, скільки зʼїв хрому важливіше, ніж обхват груді чи стегна, чи біцепса. Але то таке.)

Але для мене головне, що в Apple Health не можна анотувати показники текстом. Бо без подробиць з історією ваги мало що зробиш (а точніше, їх доведеться згадувати в будь-якому разі.)

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

Для збереження анотацій додав SwiftData, про який я писав місяць тому. Ще раз зрадів тому, наскільки мало для цього потрібно зробити — буквально пару рядків (плюс сама модель, запити — тобто значущий код.) Так само легко додати й синхронізацію бази через iCloud.

Є ще така фантазія: оскільки програма знає дати локальних мінімумів та максимумів ваги, вона може пропонувати обрати фотографію з тих дат, та утворити справжню галерею “до/після” в напівавтоматичному режимі.


07.12.2023

Дев-адвент 7: інтерпретація графіка та побудова історії

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

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

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

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

Наприклад, хочу я, щоб проміжки без зважувань теж відокремлювалися… але якщо це проміжок не у 2-3 дні. Можна видумувати ще алгоритм, а можна просто доробити функцію ціни.


06.12.2023

Дев-адвент 6: підбірка ламаної під графік

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

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

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

Побудував алгоритм за методом динамічного програмування. Задача побудови ламаної зводиться до розвʼязку для меншої кількості даних. Для двох позицій даних розвʼязок тривіальний. Далі, для кожної нової позиції i шукаємо таке j<i, щоб відрізки, що складають розвʼязок для позиції j - вже відомий — плюс новий відрізок від j до i мали оптимальну ціну з усіх можливих j. Залишається повторити для i від 2 до N і ламана побудована.

Щоб розвʼязати задачу динамічним програмуванням, потрібно визначити ціну розвʼязка. Я поки зупинився на (i-j)^1.2 * (1 - counter/amount). Тобто з першим множником все ясно — чим довше відрізок, тим краще (ступінь можна підібрати емпірично). А другий множник визначає, наскільки у відрізку багато позицій, що йдуть супротив його напрямку.

🧠 Мозок розімʼяв успішно, одним словом. А головне, що алгоритм працює — дивись ілюстрацію вище.


05.12.2023

Дев-адвент 5: історичні дані та графіки з прокруткою

Сьогодні доробляв графік, щоб можна було подивитись не тільки поточні, а й історичні дані.

Отримати історичні дані ваги дуже просто, бо їх обсяг в компʼютерних масштабах непомітний: 365 записів за рік це менше даних ніж цей пост. API HealthKit HKSampleQuery віддає всі дані одним запитом. Залишається показати їх красиво.

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

По датах робимо прокрутку. Для прокрутки графік розташовуємо в ScrollView. Оскільки хочемо, щоб графік прокручувався “ліворуч”, тобто в напрямку минулого, то його просто відразу перемотати на кінець. Такі операції у SwiftUI робляться через “проксі”-обʼєкти, в цьому разі - ScrollViewProxy. Вони нагадують ref у React.

О, ще цікавий момент: SwiftUI позбувся моделі “екранів” в додатку. Це повністю протилежне старої моделі інтерфейсу для iOS, де, навпаки, у кожного екрану був свій контролер та була сувора та незручна схема переходу між ними. Навіть React Native був вимушений емулювати таку саме модель — принаймні React Native Navigation робив окреме дерево React на кожний екран. А тепер, у SwiftUI, ніяких екранів немає, та структура компонентів дуже нагадує те, до чого ми звикли в React.