반응형

수학 및 결합 연산자의 종류

  • count 연산자
  • max 연산자
  • min 연산자
  • reduce 연산자

특징

  1. 수학 개념을 기반으로 만들었다.
  2. 결합 속성이 있다
  3. 소스 옵저버블에서 complete 함수를 호출해야 결과를 next 함수로 발행할 수 있다.

1. reduce 연산자

reduce 연산자는 누적자 함수를 이용해 소스 옵저버블에서 발행한 값을 누적한다. 소스 옵저버블에서 complete 함수를 호출하면 지금까지 누적한 결과를 next 함수로 발행하고 완료한다.

 

scan 연산자와의 차이점 - 매번 누적한 결과를 리턴하는 것이 아니다. 값을 계속 누적만 하고 발행하지 않다가 complete 함수를 호출할 때 한 번만 누적 결과를 발행한다.

reduce 연산자의 마블 다이어그램

연산자 원형
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 -> 누적값 = 1 저장
  2. 소스 옵저버블 2 -> 누적자 함수 호출 (acc = 1, curr = 2), 1 + 2 = 3 계산 후, 누적값 = 3
  3. 소스 옵저버블 3 -> 누적자 함수 호출 (acc = 3, curr = 3), 3 + 3 = 6 계산 후, 누적값 = 6
  4. 소스 옵저버블 4 -> 누적자 함수 호출 (acc = 6, curr = 4), 6 + 4 = 10 계산 후, 누적값 = 10
  5. 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]실행 과정

  1. 소스 옵저버블 0 -> 누적자 함수 호출 (초기값 acc = 1, curr = 0), 1 + 0 = 1 계산 후, 누적값 = 1
  2. 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. 소스 옵저버블 1 -> 누적자 함수 호출 (초기값 acc = 1, curr = 1), 1 + 1 = 2 계산 후, 누적값 = 2
  2. 소스 옵저버블 2 -> 누적자 함수 호출 (acc = 2, curr = 2), 2 + 2 = 4 계산 후, 누적값 = 4
  3. 소스 옵저버블 3 -> 누적자 함수 호출 (acc = 4, curr = 3), 4 + 3 = 7 계산 후, 누적값 = 7
  4. 소스 옵저버블 4 -> 누적자 함수 호출 (acc = 7, curr = 4), 7 + 4 = 11 계산 후, 누적값 = 11
  5. complete -> 누적값 11 발행 후 구독 완료 (next(10), complete 함수를 차례로 호출)

1-3. 누적자 함수의 index 파라미터

0부터 시작해 누적자 함수를 호출할 때 소스 옵저버블의 몇 번째 값을 전달하는지를 나타낸다.

초기값이 없으면 index 는 1이 된다. 초기값이 있으면 index 는 0이 된다.


2. max 연산자

max 연산자는 발행되는 값 중 가장 큰 값을 출력하는 함수다. reduce 연산자에 제일 큰 값을 누적한 함수의 값을 전달하는 방법으로 동작한다.

max 연산자의 마블 다이어그램

연산자 원형
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 연산자의 마블 다이어그램

연산자 원형
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 함수에 전달해 조건을 만족할 때 리턴하는 값만 카운트해서 마지막에 그 개수를 리턴한다.

 

원하는 값만 필터링한 개수를 카운트 하는 용도로 사용 할 수 있다.

count 연산자의 마블 다이어그램

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

+ Recent posts