[iOS- Swift] 스위프트 필수 문법 문제 2번 풀이

TIL 11일 차 - Swift 필수 문법 문제 2번 풀이

 

필수 문제 2

 

 

문제 2-1.

아래 for-in 문을 map을 사용하는 코드로 변환해 주세요.

let numbers = [1, 2, 3, 4, 5]

var result = [String]()

for number in numbers {
  result.append(number)
}

 

 

고차함수를 써본 적이 거의 없고 개념 정리가 안되어있어서 챕터 5에 있던 고차함수강의를 우선수강하고 개념 정리 후 문제를 풀었다.

let numbers = [1, 2, 3, 4, 5]

var result = [String]()

result = numbers.map { String($0) }
print(result)

map 이란?

- 배열의 각 요소를 변환하여 새로운 배열을 생성함

- 각 요소를 변형하는 작업을 반복하며, 변형된 값을 새로운 배열로 반환함

즉 map함수가 numbers 배열을 하나씩 돌면서 String으로 타입을 변형시키고 result에 append 해준 것이다.

 

 

문제 2-2.

  • 주어진 입력값을 고차함수를 체이닝 하여 주어진 출력값이 나오도록 구현해 주세요.
    • 입력: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - 타입: Array <Int>
    • 출력: [”2”, “4”, “6”, “8”, “10”] - 타입: Array <String>
    • 힌트
    • map과 filter를 이용해 볼 수 있을 것 같습니다.

 

내가 처음 풀었던 풀이

let numbers2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

var result2 = numbers2.filter { $0 % 2 == 0}
var result3 = result2.map { String($0) }

 

이 풀이의 문제점을 마스터 1팀 예슬 님이 pr에 리뷰를 해주셨는데 문제에서 제시한 체이닝 방식이 쓰이지 않았다는 것이다.

 

스위프트에서 체이닝 방식이란

- 앞에서 나온 결과를 변수에 담지 않고 바로 다음 함수로 이어서 처리하는 방식이다.

let numbers2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

let result2 = numbers2 // 후에 바뀔일이 없는 배열이므로 var 대신 let으로 선언하는것이 좋다.
	.filter { $0 % 2 == 0}
	.map { String($0) }

이런 식으로 연결해서 쓸 수 있는데

중요한 포인트는 filter의 결과가 배열이기 때문에 그 배열에 바로 map함수를 호출하여 사용할 수 있다는 것이다.

 

 

그리고 이번엔 마스터1팀 예린 님이 리뷰해 주셨던 부분인데

result2 배열은 후에 더 이상 바뀔 일이 없으므로 let으로 선언하는 것이 좋다.

var대신 let으로 선언하면 대체 뭐가 좋냐.!!

(1) 이 값은 안 바뀐다는 의도를 명확히 전달함

(2) 스위프트의 철학 중 하나가 변경이 필요할 때만 var를 쓰고 나머지는 let을 써주는 게 좋음

(3) 값이 바뀌지 않아야 하는 상수를 후에 실수로 고치게 되는 것을 컴파일 단계에서 차단해 줌

 

 

 

문제 2-3.

  • 이제 고차함수를 직접 만들어 봅니다.
    • 함수명: myMap
    • 파라미터는 2개입니다.
      1. 배열: [Int]
      2. 변환 클로저: (Int) → String
    • 반환 값은 1개입니다.
      1. [String]
    • myMap 함수 구현 내부에서 map, filter, reduce 등 고차함수를 직접 사용하지 않아야 합니다.
    • 완성된 myMap 호출 예시
    • let result = myMap([1, 2, 3, 4, 5]) { String($0) } print(result) // ["1", "2", "3", "4", "5"]

내가 처음으로 풀었던 막장 풀이 대공개

func myMap (a: [Int], operation: (Int) -> String) -> [String] {
    var result = [String]()
    for i in a {
        result.append(String(i))
    }
// 이렇게 엉망진창으로 풀어놓고도 잘 출력되길래 일단 뿌듯해함

 

마스터팀 에이스 예슬님의 소중한 코드리뷰.....🥹

호출되었던 클로저가 얼마나 서운했을까.. 불러놓고 쓰지도 않고

 

일단 이 문제부터 깊게 파고 들어보자 myMap함수를 구현하는데 왜 클로저를 파라미터로 쓰라고 한 걸까?

만약 파라미터로 클로저를 사용하지 않으면 이런 식으로 구현하게 된다.

func myMap(_ array: [Int]) -> [String] {
	// 변환방식이 하나로 고정되어 항상 문자열로만 변환이 가능하고 재사용은 불가함
}

 

하지만.. 클로저를 사용한다면?

변환 방식만 밖에서 주입해 주어 어떻게 바꿀지는 함수 밖에서 결정하게 해 재사용성이 뛰어난다.

 

따라서 최종 리펙토링 한 코드: 

func myMap (a: [Int], operation: (Int) -> String) -> [String] {
    var result = [String]()
    for i in a {
        result.append(operation(i)) // 파라미터로 받은 클로저 이용해서 변환
    }
    return result
}

let result4 = myMap(a: [1, 2, 3, 4, 5]) {
    String($0) // Int를 받아 String을 돌려줘야하므로
}

print(result4) // ["1", "2", "3", "4", "5"] 출력