StackDay. 책임 분리와 타입 분리

이전 작업에서 HabitStreak을 구현하면서 계산 결과와 계산 로직을 분리했다. HabitStreak은 현재 스트릭과 최장 스트릭이라는 값만 표현하고, 계산은 HabitStreakCalculator가 담당한다. struct HabitStreak: Equatable { let current: Int let longest: Int } struct HabitStreakCalculator { func calculate( habit: Habit, completions: [Completion], referenceDate: Date, calendar: Calendar = .current ) -> HabitStreak { // ... } } 같은 원리로 HabitStatistics도 파생 값과 계산 책임을 분리해서 구현했다. HabitStatistics HabitStatistics는 Habit의 수행 기록을 요약한 파생 값이다. MVP에서는 다음 값을 제공한다. struct HabitStatistics: Equatable { let totalCompletedDays: Int let eligibleTrackingDays: Int let completionRate: Double let streak: HabitStreak } totalCompletedDays는 추적 기간 안에서 완료한 날짜의 수이고, eligibleTrackingDays는 시작일부터 기준일까지 추적 대상이 된 날짜의 수다. 아카이브된 Habit은 archivedOn을 마지막 추적일로 사용한다. ...

August 13, 2026

Data Structure. Segment Tree

Segment Tree 배열의 특정 구간에 대한 연산을 빠르게 처리하기 위한 이진 트리 형태의 자료구조. 구간 합, 최솟값, 최댓값처럼 배열의 일정 범위를 대상으로 반복해서 연산해야 하는 경우에 사용할 수 있다. 일반적인 배열에서 특정 구간의 합을 구하려면 해당 구간의 원소를 직접 순회해야 하므로 $O(n)$의 시간이 필요하다. 세그먼트 트리는 배열의 여러 구간에 대한 연산 결과를 트리 형태로 저장하여 구간 조회를 $O(\log n)$에 처리할 수 있도록 한다. 특히 배열의 값이 변경되는 상황에서도 구간에 대한 연산을 반복해서 수행해야 할 때 사용할 수 있다. ...

August 13, 2026

TIL. Aug 12, 2026

오늘 한 내용 LeetCode 2958. Length of Longest Subarray With at Most K Frequency LeetCode 2996. Smallest Missing Integer Greater Than Sequential Prefix Sum StackDay. 엔티티와 계산 책임 분리하기 배운 내용 파생 값을 구현할 때의 책임 분리 StackDay의 HabitEntry와 HabitStreak을 구현했다. 모델링 단계에서 정의한 파생 값을 실제 코드로 옮기면서, 값을 표현하는 타입과 값을 계산하는 로직을 분리했다. AI 에이전트가 작성한 내용중에 별로인 것들을 몇개 직접 수정했는데 리스트로 정리하면 다음과 같다. HabitEntry의 이니셜라이저 전체 Completion 목록을 직접 탐색하고, 생성 실패/성공을 판정했다. HabitStreak의 이니셜라이저 내부에 Streak 계산 로직이 들어있었다. 둘 다 경계가 제대로 구분되지 않았다. HabitEntry 내부에서 전체 Completion을 탐색하면서 조건에 맞는 Completion을 찾아내는건 비효율적이기도 하고, 이건 유즈케이스에서 해야 될 일이다. ...

August 12, 2026

LeetCode 2996. Smallest Missing Integer Greater Than Sequential Prefix Sum

문제 https://leetcode.com/problems/smallest-missing-integer-greater-than-sequential-prefix-sum 풀이 정수 배열 nums가 주어진다. 배열의 첫 원소부터 연속해서 증가하는 부분의 합을 구한 뒤, 그 합보다 크거나 같으면서 배열에 존재하지 않는 가장 작은 정수를 찾으면 된다. 먼저 첫 번째 원소를 합에 더해두고, 현재 원소가 다음 조건을 만족하는지 확인한다. $$ nums[i] = nums[i - 1] + 1 $$ 조건을 만족하는 동안에는 현재 원소를 합에 더하고, 연속 조건이 깨지면 순회를 종료한다. 이제 구한 합을 후보값으로 두고, 후보값이 배열 안에 있으면 1씩 증가시킨다. 배열에 없는 첫 번째 값이 정답이다. 이 부분은 코드처럼 배열에 후보값이 존재하는 동안 반복하면 된다. ...

August 12, 2026

LeetCode 2958. Length of Longest Subarray With at Most K Frequency

문제 https://leetcode.com/problems/length-of-longest-subarray-with-at-most-k-frequency 풀이 정수 배열 nums와 정수 k가 주어질 때, 어떤 숫자도 k번보다 많이 등장하지 않는 가장 긴 연속 부분 배열의 길이를 구하면 된다. 연속 부분 배열이므로 슬라이딩 윈도우를 사용했다. start부터 end까지를 현재 윈도우로 두고, end를 오른쪽으로 이동하면서 숫자별 등장 횟수를 갱신한다. 새로 추가한 숫자의 등장 횟수가 k를 초과하면 조건을 만족할 때까지 start를 오른쪽으로 이동한다. 이때 윈도우의 가장 왼쪽 숫자부터 하나씩 제거하면 된다. 윈도우가 조건을 만족하는 상태가 되면 현재 길이로 정답을 갱신한다. ...

August 12, 2026

StackDay. 엔티티와 계산 책임 분리하기

앞서 도메인, 데이터 모델링한 내용을 실제 코드로 옮기기 시작했다. 모델 자체의 구조는 이미 정해져 있었지만 코드로 구현하니 Habit Entry같은 파생 값들은 어디서 생성해야 하는지 책임이 정해지지 않았다. Completion 먼저 습관을 수행했다는 사실을 기록하는 Completion을 구현했다. struct Completion: Equatable { let id: UUID let habitID: UUID let completedOn: Date let recordedAt: Date } Completion은 저장되는 원본 데이터이므로 완료 사실을 표현하는 데 필요한 값만 가진다. 미래의 날짜인지, Habit의 시작일 이전인지, 같은 날짜의 Completion이 이미 존재하는지와 같은 검증은 Completion 하나만으로 판단할 수 없기 때문에 넣지 않았다. ...

August 12, 2026

StackDay. 데이터 모델링, Entry가 아닌 Completion을 관리해야 하는 이유

StackDay의 MVP 범위와 정책을 가지고 앱의 도메인과 데이터를 모델링한다. 도메인 모델링 -> 데이터 모델링 -> 유즈케이스 검증 순서로 진행하고, 각각의 단계가 이전 단계를 검증하는 방향이다. 앱의 핵심 동작 이전 포스트에서 정한 앱의 컨셉과 정책에서부터, 이 앱이 어떤 동작을 해야 할지 먼저 정리했다. 사용자가 습관을 만들면 그날부터 습관의 추적이 시작된다. 사용자는 매일 습관을 수행했는지 기록하고, 앱은 누적된 기록을 바탕으로 현재 스트릭과 통계를 보여준다. 더 이상 이어가지 않을 습관은 추적을 종료할 수 있다. ...

August 11, 2026

TIL. Aug 10, 2026

오늘 한 내용 devbox-light-server Docker 이미지 작성 및 컨테이너 실행 .data 볼륨 연결과 healthcheck 추가 GitHub Actions에서 테스트 이후 이미지를 빌드하도록 구성 main 브랜치에 push하면 GHCR에 Docker 이미지를 자동으로 배포하도록 연결 PS LeetCode 1510 LeetCode 1140 배운 내용 Stone Game의 승패 DP LeetCode 1510 Stone Game 시리즈는 현재 플레이어가 이길 수 있는지를 상태로 두고 풀면 된다. Alice가 먼저 시작하는 것은 고정되어 있으므로, Alice와 Bob을 따로 나누기보다 현재 차례의 플레이어 기준으로 생각하는 편이 자연스럽다. ...

August 10, 2026

LeetCode 1140. Stone Game II

문제 https://leetcode.com/problems/stone-game-ii 풀이 돌이 일렬로 놓여 있고, 현재 M일 때 1개부터 2M개까지 가져갈 수 있다. 돌을 가져간 뒤에는 M이 max(M, 가져간 개수)로 바뀐다. Alice가 항상 먼저 시작하고, Alice가 얻을 수 있는 돌의 최대 개수를 구하면 된다. 현재 차례인 사람이 상대보다 얼마나 더 많이 가져갈 수 있는지로 DP를 정의할 수도 있지만, 이 문제에서는 현재 상태에서 현재 플레이어가 최대로 가져갈 수 있는 돌의 수를 저장하는 방식이 더 직관적이다. dp[i][m]을 i번째 돌부터 시작하고 현재 M이 m일 때, 현재 플레이어가 얻을 수 있는 최대 돌의 수라고 하자. suffix[i]는 i번째 돌부터 끝까지 남은 돌의 합이다. ...

August 10, 2026

LeetCode 1510. Stone Game IV

문제 https://leetcode.com/problems/stone-game-iv 풀이 돌이 n개 있고, 매 턴마다 제곱수만큼의 돌을 가져간다. 마지막 돌을 가져가는 사람이 이긴다고 할 때, Alice가 이길 수 있는지 구하면 된다. dp[i]를 돌이 i개 남았을 때 현재 차례인 사람이 이길 수 있는지로 정의한다. 어떤 제곱수 $r^2$을 가져간 뒤 상대 차례의 상태가 패배라면, 현재 플레이어는 그 선택으로 이길 수 있다. 따라서 점화식은 다음과 같다. $$ dp[i] = \text{true} \quad \text{if there exists } r \text{ such that } dp[i - r^2] = \text{false} $$ ...

August 10, 2026