본문 바로가기

부스트캠프/챌린지

[부스트캠프 웹모바일 9기] 챌린지 Day 8 학습 정리

고차 함수 - map, filter, reduce

1. map

기능 : 모든 요소에 대해 주어진 함수를 적용하여 새로운 리스트를 생성한다.

const list = [1, 2, 3, 4]
console.log(list.map(x => x * 2)) // [2, 4, 6, 8]

 

2. filter

기능 : 주어진 조건을 만족하는 요소만을 선택하여 새로운 리스트를 생성한다.

const list = [1, 2, 3, 4]
console.log(list.filter(x => x % 2 === 0)) // [2, 4]

 

3. reduce

기능 : 리스트의 모든 요소를 주어진 함수로 누적하여 단일 결과값을 생성한다.

const list = [1, 2, 3, 4]
console.log(list.reduce((acc, cur) => acc + cur), 0) // 10

acc에 값이 저장되며, cur으로 값을 순회한다.

 


불변성을 유지하며, 값을 어떻게 변경할까?

다음과 같은 변수가 있다고 가정해보자.

 

우리는 이 변수의 값을 1로 바꾸고 싶다면 단순히 x = 1, 이런 식으로 바꿀 수 있다.

 

 

하지만 이렇게 되면 "데이터가 변경되지 않아야 한다."는 불변성 원리에 어긋나게 된다.

만약 이러한 불변성을 해치지 않으려면 우리는 새로운 객체를 생성하는 식으로 불변성을 유지해야 한다.

 

 

 

 

 

그렇다면 배열의 불변성은?

 

[1, 2, 3]이라는 배열에서 4라는 요소를 뒤에 추가하고 싶다고 가정해보자.

그렇다면 일반적으로 사람들은 다음과 같이 작성할 것이다.

const arr = [1, 2, 3]
arr.push(4)

 

하지만 이 또한 불변성의 원리에 어긋난다.

arr는 [1, 2, 3]으로 초기화가 된 순간부터 프로그램이 끝날 때까지 [1, 2, 3]으로 유지가 되어야 불변성을 유지할 수 있다.

그러므로 위의 예시는 arr의 값이 바뀌고 있으므로 불변성에 어긋난다.

 

 

 

 

그렇다면 불변성에 어긋나지 않고, 기존 배열을 이용하여 어떻게 값을 수정할 수 있을까?

const arr = [1, 2, 3]
const newArr = [...arr, 4]

 

바로 다음과 같이 값을 수정할 수 있다.

우리는 이런 식으로 불변성을 유지하며, 값을 수정할 수 있다.

 


lazy evaluation과 불변성과의 관계

개념을 잘 못 알고 있었네요....추후 수정하겠습니다.

 

Eager Evaluation : 코드가 실행될 때 즉시 값을 계산하고 반환한다. 

Lazy Evaluation : 값이 실제로 필요할 때까지 계산을 미룬다.

 

 

우리는 Lazy Evaluation으로 불변성을 유지할 수 있다.

이게 무슨 생뚱맞은 말인가 싶을 수 있는데 링크드 리스트를 예시로 들어보자.

 

Index를 사용하는 배열은 조회 시 O(1)만큼 걸리는 데에 반해, 링크드 리스트는 조회 시 O(n)만큼 걸린다.

이 점을 잘 생각하며, 3을 만나면 6으로 바꾼 링크드 리스트를 반환하는 함수를 생각해보자.

 

 

 

 

 

Eager Evaluation은 코드를 만나면 값을 바꾼다.

그렇기 때문에 우리가 일반적으로 생각하는 방법은 다음과 같다.

 

class LinkedList {
    value
    next
    
    change3To6() {
    	if(this.value === 3) this.value = 6
        else if(this.next !== null) this.next.change3To6()
    }
}

 

당연하겠지만 3을 6으로 값을 바꾸는 순간 불변성을 해치게 된다.

이렇게 값을 즉시 바꾸는 것을 Eager evaluation이라고 한다.

참고로 시간 복잡도는 O(n)이다.

 

 

 

 

 

하지만 만약 값을 다음과 같이 변환한다면 이야기는 달라진다.

 

class LinkedList {
    #_value
    #_next
    
    constructor(value = null, next = null) {
    	this.#_value = value
        this.#_next = next
        
        Object.free(this)
    }
    
    change3To6() {
    	if(this.#_value === null) return new LinkedList(this.#_value, this.#_next)
        if(this.#_value === 3) return new LinkedList(6, this.#_next)
        if(this.#_next === null) return new LinkedList(this.#_value, this.#_next)
        return new LinkedList(this.#_value, this.#_next.change3To6())
    }
}

 

change3To6는 자신의 값을 바꾸는 것이 아니라, 새로운 링크드 리스트를 반환하는 것이다. 

이전 예시와 같이 값을 찾자마자 바로 바꾸지 않고, 바꾼 값을 받으면서 처리한다면 우리는 불변성을 유지하면서 코드를 작성할 수 있을 것이다.

이렇게 값이 필요할 때까지 계산을 미루는 방식을 Lazy Evaluation이라고 한다.

참고로 시간복잡도는 O(2n)이다.

 


여담 : 테스트 코드 맛있다.