Kelly Dev

Blog home

TIL. Aug 18, 2026

오늘 한 내용 Stack Day LocalDay 날짜 모델 도입 Clock과 FixedClock 추가 완료 기록·취소·보관·삭제 UseCase를 날짜 모델에 맞게 수정 InMemoryHabitRepository, InMemoryCompletionRepository 구현 및 테스트 추가 StackDay. Date로 하루를 표현하면 생기는 문제 배운 내용 LocalDay 도입하기 Stack Day에서는 Date 하나로 특정 시점과 특정 날짜를 모두 표현하고 있었다. 그래서 달력상의 날짜만 필요한 값을 LocalDay로 분리했다. LocalDay: startedOn, archivedOn, completedOn Date: createdAt, updatedAt, recordedAt 현재 시각은 Clock이 제공하고, 그 시각을 어느 날짜로 해석할지는 TimeZone이 결정하도록 분리했다. 전체 설계와 구현 코드는 개발일지에 정리했다. ...

August 18, 2026

StackDay. Date는 날짜가 아니다

StackDay는 일정 주기의 습관 수행 여부를 기록하는 앱이고, MVP에서는 그 주기를 매일로 고정했다. 지금까지 구현할 때, “어느 날에 수행했는가"와 “정확히 언제 기록했는가"를 모두 Foundation의 Date로 표현했다. 두 정보는 비슷해 보이지만 다르다. 이 차이를 타입으로 구분하지 않으면 날짜 검증, 중복 기록 검사, 통계 계산마다 같은 해석을 반복하게 된다. 이번 글에서는 그 문제가 실제 코드에서 어떻게 드러났고, LocalDay·Clock·TimeZone을 어떤 경계로 나눴는지 정리한다. Date타입이 저장하는 것은 순간 Date는 연, 월, 일이나 자정 같은 달력 개념을 저장하지 않는다. ...

August 18, 2026

TIL. Aug 17, 2026

오늘 한 내용 PS LeetCode 1563. Stone Game V WWDC Swift Concurrency. Explore Structured Concurrency in Swift (2) - WWDC21 마이그레이션 Algorithm. Kadane’s Algorithm 마이그레이션 미방문록. 대표 색상 추출 Picker 만들기 마이그레이션 배운 내용 Unstructured Task WWDC21 Explore Structured Concurrency in Swift 세션을 정리를 마무리했다. 1편과 다르게 구조화된 task tree에 속하지 않는 unstructured task와 detached task를 정리했다. Task { ... }로 만든 unstructured task는 생성한 scope가 끝나도 계속 실행될 수 있다. 생성 지점의 actor, priority, task-local value를 상속한다. ...

August 17, 2026

Swift Concurrency. Explore Structured Concurrency in Swift (2) - WWDC21

앞선 포스팅에서는 구조화된 동시성이 task 계층 구조를 활용해 제어 흐름을 일반 동기 코드처럼 만들고, 에러 전파와 cancellation을 단순하게 만드는 방식을 정리했다. 이 포스팅에서는 계층 구조가 없는 task, 즉 unstructured task(구조화되지 않은 동시성)에 대해 정리한다. Not all tasks fit a structured pattern 동기 코드에서 처음으로 비동기 연산을 시작하는 경우처럼 parent task가 존재하지 않거나, task의 생명주기가 하나의 스코프에 들어맞지 않는 경우가 있을 수 있다. 이런 경우는 특히 UIKit에서 delegate를 구현할 때 자주 발생한다. ...

August 17, 2026

LeetCode 1563. Stone Game V

문제 https://leetcode.com/problems/stone-game-v 풀이 돌이 일렬로 놓여 있고, 매 턴마다 현재 구간을 둘로 나눈 뒤 더 작은 합을 가진 쪽을 가져간다. Alice가 얻을 수 있는 최대 점수를 리턴하는 문제이다. 매 턴마다 현재 구간을 둘로 나눈 뒤 더 작은 합을 가진 쪽을 가져가므로, 구간 DP로 보는 게 자연스럽다. dp[left][right]를 stoneValue[left...right] 구간에서 Alice가 얻을 수 있는 최대 점수라고 하자. 구간을 split으로 나눠서 왼쪽 합과 오른쪽 합을 비교하면 된다. 왼쪽 합이 더 작으면 왼쪽을 가져간다. 오른쪽 합이 더 작으면 오른쪽을 가져간다. 두 합이 같으면 둘 중 더 좋은 쪽을 선택한다. 구간 합은 Prefix Sum으로 미리 구해두면 되고, dp는 짧은 구간부터 채우면 된다. 각 구간마다 모든 분할을 확인하므로 전체 시간 복잡도는 $O(n^3)$이다. ...

August 17, 2026

TIL. Aug 16, 2026

오늘 한 내용 PS Codeforces 1360D. Buying Shovels Stack Day CancelCompletionUseCase, ArchiveHabitUseCase, DeleteHabitUseCase 구현 완료 기록 삭제 테스트와 공통 테스트 Repository 정리 StackDay. 완료 기록을 취소할 때 무엇을 삭제해야 할까 배운 내용 in-memory Repository와 테스트 용도 Repository 프로덕션의 In-Memory Repository는 실제 저장 상태를 다루고, 테스트용 Repository는 호출과 오류를 관찰하는 Test Double이다. 같은 형태로 보이더라도 목적이 달라 분리했다. ID와 Business Key Completion.ID는 기록 자체를 식별하고, (habitID, completedOn)은 현재 정책에서 해당 날짜의 기록을 찾는 조건으로 분리했다. 없는 기록 취소를 성공으로 넘기지 않은 이유까지 개발일지에 정리했다.

August 16, 2026

StackDay. 비즈니스 규칙과 Entity Identity 분리하기

현재 StackDay MVP에서는 하나의 Habit에 대해 하루 한 번만 Completion을 기록할 수 있다. 그래서 처음에는 (habitID, completedOn) 조합을 Completion의 identity처럼 사용해도 될 것 같았고, 실제로 그런 코드도 있었다. 하지만 Completion 삭제를 구현하면서 이 둘을 분리해야 한다는 점을 알게 되었다. struct Completion: Equatable, Identifiable { let id: UUID let habitID: UUID let completedOn: Date let recordedAt: Date } 같은 날의 기록을 찾는 조건 같은 Habit의 같은 날 Completion을 찾는 데에는 habitID와 completedOn이 필요하다. 현재 정책에서는 이 조건으로 중복 기록도 막을 수 있다. 하지만 이것은 현재 정책에서 필요한 조회 조건이다. 이후 시간 단위로 습관을 기록하거나, 하루에 여러 번 수행하는 Habit이 생기면 같은 조합으로 여러 Completion이 존재할 수 있다. ...

August 16, 2026

Codeforces 1360D. Buying Shovels

문제 https://codeforces.com/problemset/problem/1360/D 풀이 정확히 n개의 삽을 사야 하고, 한 번에 살 수 있는 패키지 크기는 1부터 k까지이다. 또한 한 번 고른 패키지 크기만 계속 사야 하므로, 결국 n을 나누는 어떤 수 x를 골라 n / x개의 패키지를 사는 문제로 바뀐다. 패키지 개수를 최소화하려면 패키지 크기 x를 최대한 크게 잡아야 한다. 따라서 n의 약수 중에서 k 이하인 가장 큰 값을 찾으면 된다. 예를 들어 n = 8, k = 7이면 8의 약수는 1, 2, 4, 8인데, 이 중 k 이하인 가장 큰 약수는 4이다. 그래서 답은 8 / 4 = 2가 된다. ...

August 16, 2026

LeetCode 2029. Stone Game IX

문제 https://leetcode.com/problems/stone-game-ix 풀이 돌을 순서대로 가져가면서, 합이 3의 배수가 되지 않게 해야 하는 게임이다. Alice가 이길 수 있는지 리턴하는 문제이다. 각 돌의 값은 3으로 나눈 나머지만 보면 된다. 합이 3의 배수인지 아닌지만 중요하기 때문이다. 그래서 0, 1, 2의 개수만 세면 충분하다. 0은 합의 나머지를 바꾸지 않으므로, 실제 승부는 1과 2를 어떻게 번갈아 쓰느냐에 달려 있다. count[0]이 짝수인지 홀수인지에 따라 가능한 진행이 달라진다. count[0]이 짝수면 1과 2가 둘 다 있어야 한다. count[0]이 홀수면 1과 2의 개수 차이가 너무 크면 안 된다. 코드 class Solution { func stoneGameIX(_ stones: [Int]) -> Bool { var count = [Int](repeating: 0, count: 3) for stone in stones { count[stone % 3] += 1 } let count0 = count[0] let count1 = count[1] let count2 = count[2] if count0 % 2 == 0 { return count1 > 0 && count2 > 0 } return abs(count1 - count2) > 2 } }

August 16, 2026

TIL. Aug 15, 2026

오늘 한 내용 PS Codeforces 276C. Little Girl and Maximum Sum LeetCode 3702. Longest Subsequence With Non-Zero Bitwise XOR Swift Concurrency. Explore Structured Concurrency in Swift (1) - WWDC21 배운 내용 Structured Concurrency Explore Structured Concurrency in Swift WWDC 세션을 봤다. Meet async/await in Swift 세션도 마찬가지고, 이번 세션을 보면서 느낀 것은 Swift Concurrency의 중요한 컨셉은 비동기 코드를 일반적인 동기 코드와 비슷한 흐름으로 작성할 수 있게 하는 것이라고 생각했다. async/await가 에러 핸들링과 같은 부분을 비동기 코드도 동기 코드와 비슷하게 사용할 수 있게 해줬다면, Structured Concurrency는 조건문이나 루프같은 제어 흐름도 동기 코드와 비슷하게 사용하게 해준다. ...

August 15, 2026