BOID

[swift] 고차함수(map, filter, reduce)란? 본문

swift 시작기

[swift] 고차함수(map, filter, reduce)란?

HoonIOS 2021. 4. 14. 13:00

안녕하세요. HoonIOS입니다. :)

 

오늘은 알고리즘을 공부하다가 map, filter에 대해 좀 더 자세하게 알고 싶어서 공부를 좀 했습니다. (-.-) (_ _) (-.-)

※ 고차 함수란?

- 매개변수로 함수를 갖는 함수

- 맵, 필터, 리듀스 등이 있습니다.

- 이런 고차 함수들은 알고리즘을 공부할 때 readLine을 통해 입력받은 값을 정리하는 데 사용이 되기 때문에 확실히 알고 가는 게 좋을 거 같습니다.

 

 

 

 맵(Map) 이란?

 

- 자신을 호출할 때 매개변수로 전달된 함수를 실행하여 결과를 다시 반환하는 함수입니다.

- 배열, 딕셔너리, 세트, 옵셔널 등의 환경(?)에서 사용을 할 수 있습니다.

- 맵의 원리는 컨테이너가 담고 있던 각각의 값을 매개변수를 통해 받은 함수에 적용을 한 후 다시 컨테이너에 포장하여 반환을 합니다.
근데 여기서 중요한 것은 기존 컨테이너의 값은 변경이 되지 않고 새로운 컨테이너가 생성되어 반환이 됩니다.

 

※ 문법

- container.map( f(x) ) => return값을 새로운 컨테이너에 f(container의 각 요소)가 리턴됩니다.

- map 함수는 for_in 함수를 변형해서 사용할 수 있습니다. 그럼 둘의 차이는 무엇일까요?

코드의 재사 용면에서 용이하고 컴파일러 최적화에서 성능의 차이가 좀 있습니다. 또, 다중 스레드일 때 컨테이너의 값이 스레드에서 변경되는 시점에 다른 스레드에서도 동시에 값을 변경하려고 하면 똑같은 값을 둘 다 변경하려고 하는 부작용을 막을 수 있습니다.

 

- map의 예시를 봐보겠습니다.

* 코드 설명

  •  doubleNumber는 number 배열 값을 * 2 씩 해줘서 리턴을 해줘 새로운 int형 배열의 doubleNumber 배열 변수에 넣는 것입니다.
  • returnString은 각 number 배열 값을 " " 안에 넣어주어 String으로 변환하고 return 함으로써 returnString 배열 변수에 들어가게 됩니다.

 

- 다시 말해 map은 뒤에 컨테이너에 담고 있는 코드를 실행해 새로운 컨테이너로 생성해 값을 넣어준다고 생각을 하면 됩니다. 

 

- 위의 예시는 간결화를 하지 않은 코드입니다. map도 뒤의 클로저 함수들을 클로저처럼 간결화를 할 수 있습니다.

한번 간결화를 해보겠습니다.

* 코드 설명

  • 클로저 안에 매개변수를 사용하지 않는다면 생략을 할 수 있습니다.
     대신 $0, $1... 을 사용하여 클로저의 인자 값을 참조할 수 있습니다.
  • 반환 키워드인 return을 생략할 수 있습니다.
  • 후행 클로저를 사용할 수 있습니다.

클로저를 map에 사용을 할 수도 있습니다.

 

아 이걸 어떻게 설명을 해야 하지.... 그냥 말로 하려고 하니깐 제가 써도 무슨 말인지 모르겠네요 ㅠㅠㅠㅠ

 

코드를 보고 한번 설명해보겠습니다.

* 코드 설명

  •  인자 값을 * 2 해주는 multiple 클로저를 선언을 해줬습니다. 이 클로저를 map안에서 사용을 할 수 있습니다.
  •  밖에서 클로저를 설정해주면 같은 기능을 여러 번 사용할 수도 있습니다.

 

- 배열뿐만 아니라 다른 딕셔너리 등 다른 데서 쓰이는 것도 한번 봐보겠습니다.

* 코드 설명

  •  딕셔너리에서 사용을 하면 key값, value값 따로따로 구분해서 출력을 할 수도 있습니다.
  •  세트에서 사용을 하면 다른 것과 같이 * 2 등 연산 작업을 할 수 있습니다.
  •  range에 해당하는 값들을 map을 통해서 * 2 연산 작업을 할 수 있습니다.

 필터(filter) 란?

 

- 컨테이너 내부의 값을 걸러서 추출하는 역할을 합니다. 말 그대로 필터 하는 거예요 ㅎㅎ

- 앞에서 설명한 map같이 기존 값을 변경하는 게 아니라 특정한 조건에 맞춰 걸러진다고 생각을 하시면 됩니다.

- filter의 반환 타입은 Bool이므로 해당 값을 포함할 거면 true를 거를꺼면 false를 반환하면 됩니다.

 

간단한 어느 배열의 짝수 홀수를 filter을 통해 나누는 것을 보겠습니다.

* 코드 설명

  • . filter을 통해서 해당 값이 참인 것만 새로운 컨테이너 배열에 추가되고 그렇지 않으면 새로운 컨테이너 배열에 추가되지 않는 것을 볼 수 있습니다.
  • 형태는 map과 같습니다. return값을 보면 2를 나눈 나머지가 0이면 포함하므로 짝수이고 밑에 있는 것은 2로 나눈 나머지가 0아 아니므로 홀수가 되는 형식입니다.

놀랍게도 맵과 필터를 같이 사용을 할 수 있습니다. 놀. 랍. 죠.? 그럼 한번 예시를 봐보겠습니다.

 

* 코드 설명

  • mapNumber는 numbers배열을 map 한 것으로 * 2를 해주고 반환한 값을 받습니다.
  • evenNumber는 mapNumber배열을 filter 한 것으로 해당 배열을 % 2 == 0 을한 조건을 통해 짝수 값만 filter 해 반환한 값을 받습니다.

이렇게 map과 filter를 사용할 수 있지만 따로따로 구현을 해줘 보기에도 복잡하고 그럽니다.... 그래서 이렇게 따로따로 실행시켜주는 거보다 같이 체인처럼 연결하여 사용을 할 수가 있습니다.

* 코드 설명

  • mixNumber는 map과 filter를 모두 한 번에 처리하는 배열입니다.
  • 코드 실행 순서는 왼쪽에서 오른쪽으로부터 즉 map부터 실행이 됩니다.
  • 훨씬 더 간결하고 보기 쉬운 것을 알 수 있습니다.

 리듀스(reduce) 란?

 

- 리듀스는 컨테이너 내부의 콘텐츠를 하나로 합하는 기능을 실행하는 고차 함수입니다.

- 만약 배열이 있다면 리듀스를 통해 배열의 모든 값을 전달 인자로 전달받은 클로저의 연산 결과로 합해줍니다.

 

- 리듀스는 총 2가지 형태가 있습니다.

※리듀스의 총 2가지 형태

  • 첫 번째 형태
    - 클로저가 각 요소를 전달받아 연산한 후 값을 다음 클로저 실행을 위해 반환하며 컨테이너를 순환합니다. 모든 순회가 끝나면 리듀스의 최종 결과값이 처음 매개변수에 있는 값에 저장됩니다.
  • 두 번째 형태
    - 컨테이너를 순환하며 클로저가 실행이 되지만 클로저가 따로 결괏값을 반환하지 않는 형태

 

우선 리듀스의 첫 번째 형태에 대해 살펴보겠습니다.

 

* 코드 설명

  •  코드를 보면 reduce의 두번째 매개변수에서 result는 앞에 있는 첫번째 매개변수 ( 0 )이 되고 next는 그다음 배열 값이 됩니다.
  •  이해가 안 되시면 밑에 result, next 값 출력되신 걸 보시면 이해하시길 편할 것 같습니다. result는 첫 번째 0 값 next는 그다음 값 1이 되고 이 두 개가 더해지는 게 다음 실행될 때 result가 되는 것을 볼 수 있습니다.
  • 결과적으로 하나하나 더해져서 reduce의 첫 번째 매개변수에 쌓이고 최종적으로 마지막 두 개(result, next)의 합이 합쳐지는 게 return 하게 됩니다.
  • 뺄셈도 마찬가지겠죠?

이제 이것은 길게 늘린 거니깐 위 코드를 축약해보겠습니다.

 

* 코드 설명

  • 크게 값이 달라진 것이 없습니다. 축약을 했기 때문이죠 단지. reduce(0)에 초깃값을 설정해주고 클로저로 계속 값을 구해 해당 값을 업데이트해주는 것입니다.
  • map, filter랑 비슷한 것 같으면서 뭔가 다르네요 ㅎㅎ

리듀스의 두 번째 형태를 봐보겠습니다. 

* 코드 설명

  • 첫 번째 형태랑 제일 큰 차이점은 return을 하지 않는다는 것입니다. 반환하지 않고 내부에서 값을 계속 변경하고 있는 것을 보실 수 있습니다.
  • 어떤 특성 때문에 return을 하지 않고 내부에서 값을 변경하는 것일까요?
     바로 inout이라는 입출력 매개변수 때문입니다. 이것을 이용하면 해당 함수 컨테이너 밖에서도 바뀐 값을 적용시킬 수 있습니다. 따라서  inout 한 result 매개변수가 내부에서 값을 계속 변경되면서 into 매개변수 값을 변경하기 때문입니다. 

※ inout 매개변수란?

- 함수에서 직접 파라미터로 접근을 할 수 있게 해주는 것으로 함수 밖의 변수의 주소 값으로 직접 접근을 하수 있게 해주는 기능입니다. 

- 만약 inout을 매개변수에 포함한 함수를 호출할 때는 &변수명을 넣어줘야 합니다.

( 그 이유는 변수의 주소를 보내줘야 하니깐 그렇습니다. )

 

 

지금까지 고차 함수에 대해 알아봤습니다. 우선 큰 개념은 매개변수 함수를 갖는 함수라고 생각하면 될 거 같습니다. 이번 공부를 하면서 뭔가 더 처음 공부할 때보다 느껴지는 게 달라 뿌듯하네요

 

긴 글 읽어주셔서 감사합니다. ٩( ᐛ )و

 

반응형
Comments