foldBy
한 번의 순회로 키별 값을 접습니다 — 그룹 없이 집계만.
강의
foldBy는 fold를 소스
전체에 한 번이 아니라 키마다 한 번씩 실행하는 것입니다. 각
원소가 자기 키를 고르고, 그 값이 해당 키의 누적값에 접혀 들어갑니다.
그래서 결과는 원소가 아니라 답의
Map<K, Acc>입니다.
이 함수가 존재하는 이유는 이것이 하지 않는 일에 있습니다.
groupBy 다음에 그룹마다 접는
방식은 먼저 키마다 List를 만들어야 합니다. 답은
키 개수에 비례하는데, 할당은 입력에
비례하는 셈입니다. 카테고리별 합계만 원한다면 그 리스트들은 만들어졌다가
그대로 버려집니다. foldBy는 손으로 짠 반복문처럼 결과 맵에
곧바로 누적합니다.
// 손으로 짠다면 이렇게
for (final t in txns) {
totals[t.category] = (totals[t.category] ?? 0) + t.amount;
}
// 같은 일에 이름을 붙인 것
foldBy((Tx t) => t.category, 0.0, (sum, t) => sum + t.amount, txns);
카테고리 다섯 개에 걸친 백만 건의 거래에서, 먼저 그룹으로 묶는 방식은
손으로 짠 반복문의 2.7배가 듭니다. foldBy는
0.91배입니다 — 바로 옆의 반복문보다 조금 빠르고,
반올림 착시가 아닙니다. 반복문은 맵을 읽고 다시 쓰므로 거래마다 카테고리를
두 번 해시합니다. foldBy는 맵 안에 세워 둔 가변 셀에
누적하므로, 맵은 거래마다가 아니라 카테고리마다 한 번 쓰입니다.
Dart vs FxDart 예제 중 여럿이
바로 이 이유로 이 함수로 옮겨 갔습니다.
다만 그 차이를 과대 해석하지는 마세요. 여기서 폴드 콜백은 덧셈 하나라
맵이 작업의 대부분입니다. 누산기가 무거워지면 절약은 그대로지만 콜백
자신의 비용에 묻힙니다 — 맨 아래 레코드에 관한 메모를 보세요.
foldBy에 손을 뻗는 이유는 뜻하는 바를 그대로 말해 주기
때문이고, 반복문보다 조금 빠른 것은 논거가 아니라 덤입니다.
키는 처음 등장한 순서로 나옵니다.
groupBy와 마찬가지입니다. FxTS 포팅이 아니라, Kotlin의
groupingBy().fold()에서 온 형태입니다.
fold에서와
똑같이 seed는 모든 키의 시작점으로 쓰이는 하나의
값입니다. 새 값으로 접어 나가는 숫자나 문자열이라면 문제없습니다.
하지만 리스트·집합·맵 같은 가변 seed는 모든 키가 공유하며
모두가 그것을 변경하게 됩니다. 그룹마다 가변 구조에 누적해야 한다면
groupBy를 쓰세요.
데모 1 · 기본
데모 2 · 비동기
직접 해보기
연습: 데모는 첫 글자별로 단어 수를 셉니다. 이것을 첫
글자별 글자 수 합계로 바꿔서, fig와
fx가 {f: 5}가 되도록 해보세요.
groupBy를 쓰세요.