• 카테고리

    질문 & 답변
  • 세부 분야

    프로그래밍 언어

  • 해결 여부

    해결됨

flatMapSingle() 메소드에 대하여

21.06.28 19:25 작성 조회수 405

0

안녕하세요,

ObservableGroupByExample03, 04에서 등장한 flatMapSingle() 메소드에 대해 몇 가지 질문을 드립니다. 

1. 우선 flatMapSingle()과 map()의 마블 다이어그램이 서로 비슷해보입니다. map() 메소드부터 살펴보면

단일 원소 값을 원하는 값으로 변환해주고 있습니다.

그리고 flatMapSingle() 메소드의 마블 다이어그램에서의 함수 내부에 map()과 달리 '→'이라는 표시가 있는데, 저 화살표의 의미가 무엇인지와 이 두 함수 간의 차이점이 무엇인지 궁금합니다.


2. flatMap() 메소드의 리턴 값은 모든 Observable 형태를 허용하고, flatMapSingle()은 Single 형태의 클래스만 허용하는 것이 맞나요?

다음 ObservableGroupByExample04.java 예제에서

public class ObservableGroupByExample04 {
public static void main(String[] args) {
Observable<GroupedObservable<CarMaker, Car>> observable =
Observable.fromIterable(SampleData.carList)
.groupBy(car -> car.getCarMaker());

observable
.flatMapSingle(carGroup ->
Single.just(carGroup.getKey())
.zipWith(
carGroup.flatMap(car -> Observable.just(car.getCarName()))
.reduce((p1, p2)-> p1 + ", " + p2)
.toSingle(),
(key, sum) -> key + " : " + sum
)
)
.subscribe(System.out::println);
}

빨간색으로 표시된 Single 클래스를 flatMapSingle 내에서 시작해야 하는 이유를 알고 싶습니다.

제가 너무 별것도 아닌걸로 질문을 많이 드려서 강사님을 힘들게하는게 아닌가 죄송하네요;

답변 6

·

답변을 작성해보세요.

1

안녕하세요? 오늘도 답변이 조금 늦었습니다.

그때 그때 바로 답변을 해 드리고 싶은데 그러지 못하는점 양해부탁드리겠습니다.

질문에 대한 답변부터 하자면,

1. 'flatMapSingle( )은 (생략) 전달 받은 데이터를 내부에서 가공해서 단 하나의 데이터만 Downstream에 전달을 하는 것입니다.' 이 말의 의미는 Upstream에서 통지 되는 여러개의 데이터 중에 단 하나만 최종적으로 통지가 된다는 말이 아니구요. Upstream에서 통지되는 데이터를 하나씩 전달 받아서 flatMapSingle 연산자 내부에서 Single로 하나의 데이터를 Downstream에 전달한다는 의미였습니다.

이 말이 헷갈리시면 제가 올려둔 flatMap 예제를 보시면 되는데요.

==== ObservableFlatMapExample02.java ====

public static void main(String[] args){
Observable.range(2, 1)
.flatMap(
num -> Observable.range(1, 9)
.map(row -> num + " * " + row + " = " + num * row)
)
.subscribe(data -> Logger.log(LogType.ON_NEXT, data));
}

이 코드를 보시면 flatMap 연산자는 내부에서 Observable.range( ) 로 여러개의 데이터를 Downstream에 전달하는것이 보이시죠? 이해가 되실까 모르겠네요. 

2. 차량 제조사별 차량 가격의 평균을 구하는 예제는 아래 소스 코드처럼 구현하면 가능 할 것 같지만 실행해보면 에러가 납니다. 

observable
.flatMapSingle(carGroup ->
Single.zip(
Single.just(carGroup.getKey()),
carGroup.flatMap(car ->
Observable.just(car.getCarPrice()))
.reduce((p1, p2)-> p1 + p2)
.toSingle(),
carGroup.count()
, (key, sum, count) -> key + ": " + (sum / count)
)
)
.subscribe(System.out::println);

이유는 carGroup 이라는 구독자는 하나여야 하는데 두개의 구독자를 사용했기때문인데요.

위 코드만으로 굳이 합계를 구하실려면, carGroup.count( ) 를 대체해서 차량 제조사별 차량의 대수를 가져올 별도의 데이터셋이 필요할 것 같아요. 데이터 셋의 저장 위치는 Map 과 같은 메모리가 될 수 도 있고, 데이터베이스가 될수도 있겠구요.

또 궁금하신 사항이 있으면 질문주시면 감사드릴게요.

그룹별로 평균 등 여러 집계 결과를 나타낼 수 있는 방법을 찾아봤습니다.

public class ObservableGroupByExample {
public static void main(String[] args) {
Observable.fromIterable(SampleData.carList)
.groupBy(Car::getCarMaker)
.flatMapSingle(
groupedCar ->
Single.fromCallable(() -> groupedCar.getKey())
.zipWith(
groupedCar.toList()
.map(cars -> cars.stream().collect(Collectors.summarizingDouble(Car::getCarPrice)))
,
(carMaker, summarizing) -> carMaker + " : " + summarizing
)
).subscribe(System.out::println);
}
}
HYUNDAE : DoubleSummaryStatistics{count=2, sum=61000000.000000, min=28000000.000000, average=30500000.000000, max=33000000.000000}
SAMSUNG : DoubleSummaryStatistics{count=2, sum=75000000.000000, min=35000000.000000, average=37500000.000000, max=40000000.000000}
SSANGYOUNG : DoubleSummaryStatistics{count=2, sum=66000000.000000, min=23000000.000000, average=33000000.000000, max=43000000.000000}
CHEVROLET : DoubleSummaryStatistics{count=3, sum=91000000.000000, min=18000000.000000, average=30333333.333333, max=50000000.000000}

다시 복습하면서 다시 시도해봤는데, GroupedObservable에도 toList()를 적용할 수 있었네요

그리고나서 통지하는 List형 데이터를 stream() 메소드를 통해 여러 집계 함수를 사용할 수 있었습니다!

잘 하셨네요.ㅎ 저보다 더 나으신거 같습니다. ^^

열심히 하시는 모습이 너무 보기 좋으세요!

감사합니다!ㅎㅎ

1

아, 근데 대댓글 이메일 알림 기능은 없는게 맞나봐요. 암튼 도움 되셨다니 다행이에요!

1

안녕하세요? 질문은 언제든지 편하게 하셔두 되니까 부담 갖지 마시고 편하게 하시면 될것 같아요. ㅎ

수강생분들이 해주시는 질문이나 의견 등이 질문자님 뿐만 아니라 저와 다른 수강생 분들에게도 도움이 되기때문에 언제든지 편하게 질문해주시면 감사드리겠습니다.

질문 하신 내용에 대해서 답변을 드리자면,

먼저 첫번째 질문에 대해서 말씀을 드릴게요.  flatMapSingle( ) 내부의 화살표('→')는 연산자 내부에서 또 다른 생산자(Publisher) 즉, 여기서는 Single이 데이터를 통지함을 의미합니다.

앞으로 마블 다이어그램을 보시다가 flatMapSingle( ) 처럼 연산자 내부에 화살표('→')가 있다면 연산자 안쪽에서 또 다른 생산자가 데이터를 통지(발행, 방출, emit)한다라고 보시면 되겠습니다.

그리고 map( )과 flatMapSingle( )의 차이점을 말씀 드리자면, 마블 다이어그램에서 보시다시피 map( )은 Upstream에서 통지된 데이터를 다른 형태로 하나씩 변환을 해서 Downstream에 전달을 하는 연산자이구요.

flatMapSingle( )은 map( )처럼 Upstream에서 통지된 데이터를 하나씩 차례대로 Downstream에 전달을 하는 것이 아니라 전달 받은 데이터를 내부에서 가공해서 단 하나의 데이터만 Downstream에 전달을 하는것입니다. Single은 단 하나의 데이터만 통지를 하는 생산자이기때문에 그래서 연산자 이름이 flatMapSingle( ) 이 되는것이구요.

두 번째 질문에 대해서 잠깐 답변을 드리자면, flatMap( )과 flatMapSingle( )의 리턴 타입은 내부적으로는 모두 Observable입니다. 다만, flatMap( ) 파라미터로 입력되는 람다 표현식인 mapper의 body return 타입이 Observable만 허용하고, flatMapSingle( ) 은 Single만 허용합니다. 좀 헷갈리실텐데 아래 코드는 Observable에 있는 flatMap( )과 flatMapSingle( )의 소스코드인데요.

==== Observable.java > flatMap ====

public final <R> Observable<R> flatMap(Function<? super T, ? extends 
ObservableSource<? extends R>> mapper) {
return flatMap(mapper, false);
}

: ObservableSource 로 리턴 타입을 제한하고 있는것 보이시죠?

==== Observable.java > flatMapSingle ====

public final <R> Observable<R> flatMapSingle(Function<? super T, 
? extends SingleSource<? extends R>> mapper) {
return flatMapSingle(mapper, false);
}

: SingleSource로 리턴 타입을 제한하고 있는것을 보실 수가 있습니다.

마지막으로

"ObservableGroupByExample04.java 예제에서
'observable.flatMapSingle(groupedCar ->  Single.fromCallable(~~~)'에 대해서 Single 클래스를 사용해야 하는 이유를 아직도 모르겠습니다."

이 부분은 제가 작성한 ObservableGroupByExample04.java 소스 코드와 질문해주신 코드가 달라서 설명을 드리기가 조금 곤란한데, 혹시 질문자님이 보신 코드가 아래 소스코드가 맞나요?

=== ObservableGroupByExample04.java ====

public class ObservableGroupByExample04 {
public static void main(String[] args) {
Observable<GroupedObservable<CarMaker, Car>> observable =
Observable.fromIterable(SampleData.carList)
.groupBy(car -> car.getCarMaker());

observable
.flatMapSingle(carGroup ->
Single.just(carGroup.getKey())
.zipWith(
carGroup.flatMap(car ->
Observable.just(car.getCarPrice()))
.reduce((p1, p2)-> p1 + p2)
.toSingle()
, (key, sum) -> key + ": " + sum
)
)
.subscribe(System.out::println);
}
}

: groupby 연산자의 네번째 예제는 제가 최근에 추가로 올린 위 코드여야 하는데 어떤 코드를 보셨는지 잘 모르겠네요. github 소스 코드를 확인해봐도 위 코드와 동일해서요.  다시 확인 부탁드릴게요.

답변이 도움이 되셨으면 좋겠네요. 그럼 또 뵐게요~

0

답변 감사합니다!

1. 저 예제를 보여주시니까 알 것 같네요ㅎㅎ;  저 예제에서 Downstream은 map( )으로 넘어가는 단계겠죠? 감사합니다!

2. 별도의 데이터 셋의 도움없이 평균 값을 구하는 것이 쉽지가 않군요 참고하도록 하겠습니다!

네, flatMap 내부의 Observable.range()의 Downstream은 map이 맞습니다. 명령형 언어 문법의 이중 for 문을 생각하면 이해가 좀 더 되실지 모르겠네요. flatMap으로 데이터가 하나씩 순차적으로 들어오면 flatMap 내부에서 마치 for 문을 한번 더 도는것처럼 Observable.range().map()으로 데이터를 전달하고 있습니다. 

0

1. 처음 답변해주신 내용 중 읽어보면서 궁금한 부분이 있습니다.
우선 flatMapSingle( ) 함수에 대해 설명해주셨을 때 '
flatMapSingle( )은 (생략) 전달 받은 데이터를 내부에서 가공해서 단 하나의 데이터만 Downstream에 전달을 하는 것입니다.'라고 하셨고, 강사님이 작성하신 게시글을 확인하고 나서야 저도 처음에 아 Single을 반환하니까 하나의 데이터를 전달(emit)하는구나라고 이해할 수 있었습니다.

그런데 제가 이해한 것과 해당 메소드의 마블 다이어그램 표현이 서로 조금 매치가 안되는 느낌이 있습니다. 마블 다이어그램 표현은 제가 이해한 것과 다르게 Downstream에 여러 데이터를 하나 씩 emit하던데, 제가 잘못 이해한걸까요?

2. 그리고 ObservableGroupByExample04.java에 대한 다른 질문 하나 더 드려봐도 될까요?
이 예제랑 별개로 저 혼자서 '제조사별 차량 이름', '제조사별 차량 개수' 등 실습해보았습니다.
그런데 '제조사별 차량 평균 판매가(price)'를 구하고 싶은데, 차량의 개수를 구하는 방법(carGroup.count().toSingle())을 zipWith() 메소드 내에서 어떻게 활용하여 평균 값을 구해야하는가에 막히고 있습니다.

0

답변 감사합니다!

마지막 질문은 답변해주신 그 코드가 맞습니다!

질문하기 전에 제가 스스로 코드를 다시 짜보다가 원본과 다르게 올렸었네요..그리고 읽기 힘드실까봐 저렇게 대충 요약해서 올렸고요;; 질문 수정하도록 하겠습니다.

3번째 질문을 다시 드리자면, ObservableGroupByExample04.java의 9 라인에서 Single 클래스로 시작하는 이유가 작성해주신 두번째 답변과 같이 flatMapSingle( )의 람다표현식 body 리턴 값이 Single로 한정되었기 때문인가요?

*p.s 답장 주시고 1시간 이내에 대댓글을 작성드렸는데 인프런에서 대댓글 알람 기능이 없는 것 같아 다시 댓글 작성했습니다!

네, 맞습니다.  flatMapSingle( ) 연산자의 body 리턴값이 SingleSource로 한정이 되어 있어서 입니다.

질문 달아주시면 원글이든 답글이든 제 이메일로 메일이 와요.

그런데 제가 업무 중에는 답을 제대로 하기가 힘든 부분이 있어서 항상 퇴근하고 답변을 달아드리는데 불편하시더라도 양해를 좀 부탁드릴게요. ^^;

답변 감사합니다! 답장 기능은 문제가 없군요;

덕분에 속이 후련해졌습니다ㅎㅎ