countBy

계산된 키별로 원소가 몇 개씩 대응되는지 집계합니다.

Map<K, int> countBy<A, K>(K Function(A a) f, Iterable<A> iterable) Future<Map<K, int>> countByAsync<A, K>(FutureOr<K> Function(A a) f, FxAsyncIterable<A> iterable) Map<K, int> Fx.countBy<K>(K Function(T a) f) // chain (sync) Future<Map<K, int>> FxAsync.countBy<K>(FutureOr<K> Function(T a) f) // chain (async)

강의

countBygroupBy, indexBy와 함께 삼총사를 완성합니다. 파이프라인 전체를 끌어당기며 원소마다 키를 계산한다는 발상은 같지만, 이번에는 원소를 전혀 보관하지 않고 키별 카운터만 증가시킵니다. 결과는 Map<K, int>, 즉 각 키를 만들어 낸 원소가 몇 개인지입니다.

이 셋은 같은 그룹화에 대해 서로 다른 질문에 답한다고 생각하면 됩니다. groupBy는 "이 키에 해당하는 원소를 전부 달라", indexBy는 "이 키의 마지막 원소를 달라", countBy는 "이 키를 가진 원소가 몇 개냐"입니다. 집계만 필요하다면 countBygroupBy(...).map((k, v) => MapEntry(k, v.length))보다 저렴합니다. 중간 리스트를 전혀 할당하지 않기 때문입니다.

그리고 대신 여러분이 짰을 반복문보다도 저렴합니다. 누구나 떠올릴 counts[k] = (counts[k] ?? 0) + 1은 원소마다 해시 맵을 두 번 건드립니다. 읽으려고 한 번, 다시 쓰려고 한 번. 그리고 하는 일이 세는 것뿐일 때 맵은 사실상 비용의 전부입니다. countBy는 맵 안에 세워 둔 가변 셀에 세므로, 맵은 원소마다가 아니라 서로 다른 키마다 한 번 쓰입니다 — 백만 원소에서 손으로 짠 반복문보다 약 1.5배 빠르고, 그 격차는 키가 몇 개일 때부터 수만 개일 때까지 유지됩니다. 가장 잦은 로그 레벨이 그 숫자를 처음부터 끝까지 따라갑니다.

늘 그렇듯 종결 연산자입니다 — countBy가 값을 끌어당기기 전까지 상류에서는 아무 일도 일어나지 않습니다.

데모 1 · 기본

데모 2 · 비동기

직접 해 보기

연습: 각 후보가 몇 표를 받았는지 세어 보세요.

관련 항목: groupBy — 개수 대신 모든 원소를 보관합니다 · indexBy — 개수 대신 마지막 원소를 보관합니다 · size — 키 없이 전체 개수만 셉니다