수학 및 결합 연산자의 종류
- count 연산자
- max 연산자
- min 연산자
- reduce 연산자
특징
- 수학 개념을 기반으로 만들었다.
- 결합 속성이 있다
- 소스 옵저버블에서 complete 함수를 호출해야 결과를 next 함수로 발행할 수 있다.
1. reduce 연산자
reduce 연산자는 누적자 함수를 이용해 소스 옵저버블에서 발행한 값을 누적한다. 소스 옵저버블에서 complete 함수를 호출하면 지금까지 누적한 결과를 next 함수로 발행하고 완료한다.
scan 연산자와의 차이점 - 매번 누적한 결과를 리턴하는 것이 아니다. 값을 계속 누적만 하고 발행하지 않다가 complete 함수를 호출할 때 한 번만 누적 결과를 발행한다.

연산자 원형
reduce<T, R>(
accumulator: (acc: R, value: T, index?: number) => R,
seed?: R
): OperatorFunction<T, R>
1-1. 초기값이 없음
초기값이 없으면 처음 발행하는 값을 초기갑으로 삼아 누적자 함수를 적용하지 않는다. 두번째 발행하는 값을 처음 발행하는 값에 누적하도록 동작한다. 만약 소스 옵저버블에서 1개의 값만 발행하고 complete 함수를 호출하면 이 값만 발행하고 구독을 완료한다.
/* 초기값 없이 reduce 연산자를 사용하는 예 */
const { of } = require('rxjs');
const { reduce } = require('rxjs/operators');
of(0).pipe(reduce((acc, cur) => acc + cur))
.subscribe(result => console.log(`result: ${result}`));
[코드 7-1] 초기값 없이 reduce 연산자를 사용하는 예
실행 결과
result: 0
2개 이상 값을 전달받지 못하므로 누적자 함수를 호출할 수 없다. 따라서 0 그대로 발행한다.
/* reduce 연산자와 range 함수로 4개 값을 발행하는 예 */
const { range } = require('rxjs');
const { reduce } = require('rxjs/operators');
range(1, 4).pipe(reduce((acc, cur) => acc + cur))
.subscribe(result => console.log(`result: ${result}`));
[코드 7-2] reduce 연산자와 range 함수로 4개 값을 발행하는 예
실행 결과
result: 10
누적자 함수 첫번째 인자는 지금까지 누적한 값이고, 두번째 인자는 현재 값이다. 따라서, 1부터 4까지 더한 10을 발행한다.
[코드7-2]에서 누적자 함수를 호출해 누적하는 과정
- 소스 옵저버블 1 -> 누적값 = 1 저장
- 소스 옵저버블 2 -> 누적자 함수 호출 (acc = 1, curr = 2), 1 + 2 = 3 계산 후, 누적값 = 3
- 소스 옵저버블 3 -> 누적자 함수 호출 (acc = 3, curr = 3), 3 + 3 = 6 계산 후, 누적값 = 6
- 소스 옵저버블 4 -> 누적자 함수 호출 (acc = 6, curr = 4), 6 + 4 = 10 계산 후, 누적값 = 10
- complete -> 누적값 10 발행 후 구독 완료 (next(10), complete 함수를 차례로 호출)
1-2. 초기값이 있음
/* 초기값이 있는 reduce 연산자를 사용하는 예 */
const { of } = require('rxjs');
const { reduce } = require('rxjs/operators');
of(0).pipe(reduce((acc, cur) => acc + cur, 1))
.subscribe(result => console.log(`result: ${result}`));
[코드 7-3] 초기값이 있는 reduce 연산자를 사용하는 예
실행 결과
result: 1
소스 옵저버블은 0이란 값 1개만 발행하지만 누적자 함수로 초기값 1을 함께 누적해 최종 1이라는 값을 발행한다.
[코드7-3]실행 과정
- 소스 옵저버블 0 -> 누적자 함수 호출 (초기값 acc = 1, curr = 0), 1 + 0 = 1 계산 후, 누적값 = 1
- complete -> 누적값 1 발행 후 구독 완료 (next(1), complete 함수를 차례로 호출)
/* 초기값 1을 설정해 여러 개 값을 발행하는 예 */
const { range } = require('rxjs');
const { reduce } = require('rxjs/operators');
range(1, 4).pipe(reduce((acc, cur) => acc + cur, 1))
.subscribe(result => console.log(`result: ${result}`));
[코드 7-4] 초기값 1을 설정해 여러 개 값을 발행하는 예
실행 결과
result: 11
[코드7-4]에서 누적자 함수를 호출해 누적하는 과정
- 소스 옵저버블 1 -> 누적자 함수 호출 (초기값 acc = 1, curr = 1), 1 + 1 = 2 계산 후, 누적값 = 2
- 소스 옵저버블 2 -> 누적자 함수 호출 (acc = 2, curr = 2), 2 + 2 = 4 계산 후, 누적값 = 4
- 소스 옵저버블 3 -> 누적자 함수 호출 (acc = 4, curr = 3), 4 + 3 = 7 계산 후, 누적값 = 7
- 소스 옵저버블 4 -> 누적자 함수 호출 (acc = 7, curr = 4), 7 + 4 = 11 계산 후, 누적값 = 11
- complete -> 누적값 11 발행 후 구독 완료 (next(10), complete 함수를 차례로 호출)
1-3. 누적자 함수의 index 파라미터
0부터 시작해 누적자 함수를 호출할 때 소스 옵저버블의 몇 번째 값을 전달하는지를 나타낸다.
초기값이 없으면 index 는 1이 된다. 초기값이 있으면 index 는 0이 된다.
2. max 연산자
max 연산자는 발행되는 값 중 가장 큰 값을 출력하는 함수다. reduce 연산자에 제일 큰 값을 누적한 함수의 값을 전달하는 방법으로 동작한다.

연산자 원형
amx<T>(comparer?: (x: T, y: T) => number): MonoTypeOperatorFunction<T>
comparer? - 두 값을 비교하려고 기본값 대신 사용할 비교 함수
/* max 연산자의 구현 코드 일부분 */
export function max(comparer) {
const max = (typeof comparer === 'function')
? (x, y) => comparer(x, y) > 0 ? x : y
: (x, y) => x > y ? x : y;
return this.lift(new ReduceOperator(max));
}
[코드 7-5] max 연산자의 구현 코드 일부분
reduce 연산자를 이용해 소스 옵저버블에서 complete 함수를 호출해야 지금까지 누적한 가장 큰 값을 발행한다. 인자가 없으면 부등호로 값의 크기를 비교한다. 따라서, 부등호로 비교할 수 있는 값만 발행해야 정상 동작한다. 그렇지 않는 값이나 객체는 comparer 함수를 인자로 사용해 비교해야 한다.
/* comparer 함수를 사용하지 않는 예 */
const { range } = require('rxjs');
const { max } = require('rxjs/operators');
range(1, 10).pipe(max())
.subscribe(result => console.log(`result: ${result}`));
[코드 7-6] comparer 함수를 사용하지 않는 예
실행 결과
result: 10
소스 옵저버블이 발행하는 값이 숫자이므로 부동호 바교하여 가장 큰 값인 10을 발행한다.
2-1. max 연산자에서 comparer 함수 사용
/* comparer 함수를 사용하는 예 */
const { from } = require('rxjs');
const { max } = require('rxjs/operators');
const movies = [
{ title: '영화 1', avg: 5.12 },
{ title: '영화 2', avg: 9.14 },
{ title: '영화 3', avg: 8.28 }
];
from(movies).pipe(max((x, y) => x.avg - y.avg))
.subscribe(x => console.log(JSON.stringify(x)));
[코드 7-7] comparer 함수를 사용하는 예
실행 결과
{"title":"영화 2","avg":9.14}
comparer 함수는 x.avg에서 y.avg를 빼서 평점을 비교한다. x의 평균 평점이 y의 평균 평점보다 클 때 0보다 큰 값을 리턴해 올바른 결과를 출력한다. 최고 평점에 해당하는 객체를 찾아 JSON.stringify를 이용해 JSON 결과를 출력한다.
2-2. 다른 객체지만 같은 값으로 평가할 때의 max 연산자 사용
[코드 7-7]에서 만약 평균 평점이 같은 객체가 여러 개 있으면 지금까지 누적된 x가 현재 값인 y보다 크지 않으므로 y를 리턴한다. 소스 옵저버블에서 가장 나중에 발행하는 객체를 선택한다.
이유 -> max 연산자의 내부 구현에서 reduce 연산자의 누적자 함수의 구현이 (x, y) => x > y ? s : y 이기 때문이다. 부등호가 > 대신 >= 였다면 누적값인 x가 선택되므로 가장 먼저 나온 값을 발행한다.
/* 같은 값으로 평가하는 다른 객체 처리 */
const { from } = require('rxjs');
const { max } = require('rxjs/operators');
const movies = [
{ title: '영화 1', avg: 5.12 },
{ title: '영화 2', avg: 9.14 },
{ title: '영화 3', avg: 8.28 },
{ title: '영화 4', avg: 9.14 }
];
from(movies).pipe(max((x, y) => x.avg - y.avg))
.subscribe(x => console.log(JSON.stringify(x)));
[코드 7-8] 같은 값으로 평가하는 다른 객체 처리
실행 결과
{"title":"영화 4","avg":9.14}
'영화 2'와 '영화 4'가 평균 평점이 같다. 이럴 경우 소스 옵저버블에서 가장 마지작에 발행한 '영화 4'를 발행했다.
3. min 연산자
min 연산자는 reduce 연산자에 가장 작은 값을 누적한 누적자 함수 값을 전달한다.

연산자 원형
min<T>(comaprer?: (x: T, y: T) => number): MonoTypeOperatorFunction<T>
max 연산자의 반대 개녕믜 연산자이다.
/* min 연산자의 구현 코드 일부분 */
export function min(comparer) {
const min = (typeof comparer === 'function')
? (x, y) => comparer(x, y) < 0 ? x : y
: (x, y) => x < y ? x : y;
return this.lift(new ReduceOperator(min));
}
[코드 7-9] min 연산자의 구현 코드 일부분
max 연산자으 구현 코드와 부등호 방향만 다르다.
min 연산자도 값이 같으면 y를 리턴하므로 뒤에 있는 값이 발행된다.
/* comparer 함수를 사용하지 않는 min 연산자 예 */
const { range } = require('rxjs');
const { min } = require('rxjs/operators');
range(1, 10).pipe(min())
.subscribe(result => console.log(`result: ${result}`));
[코드 7-10] comparer 함수를 사용하지 않는 min 연산자 예
실행 결과
result: 1
소스 옵저버블이 발행하는 값이 숫자이므로 부동호 바교하여 가장 작은 값인 1을 발행한다.
/* comparer 함수를 사용하는 min 연산자 예 */
const { from } = require('rxjs');
const { min } = require('rxjs/operators');
const movies = [
{ title: '영화 1', avg: 5.12 },
{ title: '영화 2', avg: 9.14 },
{ title: '영화 3', avg: 8.28 }
];
from(movies).pipe(min((x, y) => x.avg - y.avg))
.subscribe(x => console.log(JSON.stringify(x)));
[코드 7-11] comparer 함수를 사용하는 min 연산자 예
실행 결과
{"title":"영화 1","avg":5.12}
/* 같은 값으로 평가하는 다른 객체를 min 연산자로 처리 */
const { from } = require('rxjs');
const { min } = require('rxjs/operators');
const movies = [
{ title: '영화 1', avg: 5.12 },
{ title: '영화 2', avg: 9.14 },
{ title: '영화 3', avg: 5.12 }
];
from(movies).pipe(min((x, y) => x.avg - y.avg))
.subscribe(x => console.log(JSON.stringify(x)));
[코드 7-12] 같은 값으로 평가하는 다른 객체를 min 연산자로 처리
실행 결과
{"title":"영화 3","avg":5.12}
'영화 1'과 '영화 3'이 가장 작은 평균 평점이며 값은 같다. 이럴 경우 소스 옵저버블에서 가장 마지작에 발행한 '영화 3'를 발행했다.
4. count 연산자
count 연산자는 소스 옵저버블에서 값을 발행할 때마다 개수를 내부에서 카운트한다. 구독을 완료한 후 총 몇 개인지 발행한다.
연산자 원형
count<T>(
predicate?: (value: T, index: number, source: Observable<T>) => boolean
): OperatorFunction<T, number>
predicate 함수는 조건을 만족하는지 아닌지를 검사해 리턴한다. 즉, 소스 옵저버블에서 발행하는 값을 predicate 함수에 전달해 조건을 만족할 때 리턴하는 값만 카운트해서 마지막에 그 개수를 리턴한다.
원하는 값만 필터링한 개수를 카운트 하는 용도로 사용 할 수 있다.

4-1. 기본 동작
/* 인자 없는 count 연산자의 사용 예 */
const { range } = require('rxjs');
const { count } = require('rxjs/operators');
range(1, 20).pipe(count())
.subscribe(result => console.log(`result: ${result}`));
[코드 7-13] 인자 없는 count 연산자의 사용 예
실행 결과
result: 20
발행하는 값이 총 20개 이므로 20을 마지막에 발행한다.
4-2. predicate 함수 사용
/* predicate 함수를 사용해 짝수만 카운트 */
const { range } = require('rxjs');
const { count } = require('rxjs/operators');
range(1, 7).pipe(count(x => x % 2 === 0))
.subscribe(result => console.log(`result: ${result}`));
[코드 7-14] predicate 함수를 사용해 짝수만 카운트
실행 결과
result: 3
소스 옵저버블에서 발행하는 값 중 짝수에 해당하는 값은 3개 이므로 3을 출력한다.
'RxJS' 카테고리의 다른 글
| 9장. 조건 연산자 요약 (0) | 2024.08.05 |
|---|---|
| 8장. 유틸리티 연산자 요약 (0) | 2024.08.02 |
| 6장. 조합 연산자 요약 (0) | 2024.08.01 |
| 5장. 변환 연산자 요약 (0) | 2024.07.31 |
| 4장. 필터링 연산자 요약 (0) | 2024.07.29 |