코딩 공부/React

[React] 3.3.3 데이터 변환(함수형 프로그래밍의 개념) - 가온 코딩

Cosmic-dust 2022. 7. 25. 16:20
728x90
반응형

Transforming Data(데이터 변환)

 

함수형 프로그래밍의 핵심 개념인

1. 불변성(immutability)

2. 순수성(purity)

3. 데이터 변환(transformation)

4. 고차 함수

5. 재귀(recursion)

중 3. 데이터 변환에 대해 설명하겠다.

 

데이터가 변경 불가능하다면 애플리케이션에서 데이터를 가지고 어떻게 뭘 할 수 있을까?

함수형 프로그래밍은 한 데이터를 다른 데이터로 변환하는 게 전부이다.

그래서, 함수를 사용해 원본을 변경한 복사본을 만들어내는 방식을 사용한다.

그렇게 순수 함수를 사용해 데이터를 변경하면, 덜 명령형인 코드가 되고 복잡도도 감소한다.

 

어떻게 데이터를 변환해서 다른 데이터를 만들어낼 수 있을까?

자바스크립트 언어 안에는 이미 그런 작업을 할 수 있는 도구가 있다.

함수형 자바스크립트를 유창하게 사용하기 위해 통달해야하는 핵심 함수가 2개 있다.

Array.map 과 Array.reduce 이다.

 

이 글에서는 이 두 함수와 다른 여러 핵심 함수를 사용해 한 유형의 데이터를 다른 유형으로 어떻게 변경할 수 있는지 살펴보겠다.

 

1. Joining Array Items

고등학교 명단이 들어 있는 배열을 생각해보자.

Array.join 함수를 사용하면 콤마(,)로 각 학교를 구분한 문자열을 얻을 수 있다.

const schools = [
  "Yorktown",
  "Washington & Lee",
  "Wakefield"
]

console.log( schools.join(", ") )

 

Array.join은 자바스크립트 내장 배열 메서드다. join은 배열의 모든 원소를 인자로 받은 구분자(delimiter)로 연결한 문자열을 반환한다. 원래의 배열은 그대로 남는다. join은 단지 배열에 대해 다른 해석을 제공할 뿐이다.

프로그래머가 join이 제공하는 추상화를 사용하면 문자열을 실제로 어떻게 만드는지에 대해서는 신경 쓰지 않아도 된다.

 

2. Filtering Arrays

'W'로 시작하는 학교만 들어 있는 새로운 배열을 만들고 싶다면 Array.filter 메서드를 사용하면 된다.

const schools = [
  "Yorktown",
  "Washington & Lee",
  "Wakefield"
]

const wSchools = schools.filter(school => school[0] === "W")

console.log( wSchools )

Array.filter는 원본 배열로부터 새로운 배열을 만들어내는 자바스크립트 배열 내장 함수이다.

이 함수는 술어(predicate)를 유일한 인자로 받는다.

술어는 불린(Boolean) 값, 즉 true나 false를 반환하는 함수를 뜻한다.

Array.filter는 배열에 있는 모든 원소를 하니씩 사용해 이 술어를 호출한다. filter는 술어에 배열의 원소를 인자로 전달하며, 술어가 반환하는 값이 true이면 해당 원소를 새 배열에 넣는다. 여기서 Array.filter는 각 학교의 이름이 'W'로 시작하는지 여부를 검사한다.

 

3. Filtering Array Function

배열에서 원소를 제거해야 할 필요가 있다면 Array.pop이나 Array.splice보다는 Array.filter를 사용하자.

Array.filter는 순수 함수다.

아래에서 cutSchool 함수는 특정 학교의 이름을 제외한 새로운 배열을 반환한다.

const schools = [
  "Yorktown",
  "Washington & Lee",
  "Wakefield"
]

const cutSchool = (cut, list) => 
    list.filter(school => school !== cut)

console.log(cutSchool("Washington & Lee", schools).join(" * "))
console.log(schools.join("\n"))

cutSchool 함수는 "Washington & Lee"가 들어 있지 않은 새로운 배열을 반환한다.

join 함수를 사용해 그 새 배열에 들어 있는 두 학교의 이름을 *로 구분한 문자열을 만든다.

cutSchool은 순수 함수다. 학교 목록(배열)과 학교 이름을 인자로 받아서 그 이름에 해당하는 학교를 제외한 나머지 학교로 이뤄진 새 배열을 반환한다.

 

4. Mapping Arrays

함수형 프로그래밍에 꼭 필요한 함수 중 Array.map이 있다.

Array.map은 술어가 아니라 변환 함수로 인자를 받는다. 그 함수를 배열의 모든 원소에 적용해서 반환받은 값으로 이뤄진 새 배열을 반환한다.

const schools = [
  "Yorktown",
  "Washington & Lee",
  "Wakefield"
]

const highSchools = schools.map(school => `${school} High School`)

console.log(highSchools.join("\n"))
console.log(schools.join("\n"))

이 경우 map 함수는 각 학교 이름 뒤에 'High School'을 추가한다. 이때 원본 schools 배열은 아무 변화가 없다.

 

5. Creating Objects with .map()

아래에서는 문자열의 배열에서 문자열의 배열을 만들었다.

map 함수는 객체, 값, 배열, 다른 함수 등 모든 자바스크립트 타입의 값으로 이뤄진 배열을 만들 수 있다.

아래는 학교가 담겨있는 객체의 배열을 반환하는 map 함수를 보여준다.

const schools = [
  "Yorktown",
  "Washington & Lee",
  "Wakefield"
]

const highSchools = schools.map(school => ({ name: school }))

console.log(highSchools)

이 예제는 문자열을 포함하는 배열로부터 객체를 포함하는 배열을 만든다. 

 

 

6. Updating Array of Objects

배열의 원소 중 하나만을 변경하는 순수 함수가 필요할 때도 map을 사용할 수 있다. 다음 예제는 원본 schools 배열을 변경하지 않으면서 'Stratford'라는 이름의 학교를 'HB Woodlawn'으로 바꾼다.

 

schools 배열은 객체의 배열이다.

updatedSchools 변수는 editName 함수에 대상 학교 이름, 그 학교의 새 이름, 그리고 schools 배열을 넘겨서 받은 결과를 저장한다. editName 함수는 원본 배열은 그대로 둔채로 학교 이름이 바뀐 새 배열을 반환한다.

    let schools = [
        { name: "Yorktown"},
        { name: "Stratford" },
        { name: "Washington & Lee"},
        { name: "Wakefield"}
    ]

    const editName = (oldName, name, arr) =>
        arr.map(item => {
            if (item.name === oldName) {
                return {
                    ...item,
                    name
                }
            } else {
                return item
            }
        })

    let updatedSchools = editName("Stratford", "HB Woodlawn", schools)

    console.log( updatedSchools[1] ) // { name: "HB Woodlawn" }
    console.log( schools[1] )	// { name: "Stratford" }

 

 

editName는 map 함수를 사용해서 원본 배열로부터 새로운 객체로 이뤄진 배열을 만든다.

editName이 Array.map에 전달하는 화살표 함수는 배열의 원소를 item 파라미터로 받으며 파라미터의 이름과 ildName이 같은지 비교해서 이름이 같은 경우에는 새 이름(name)을 객체에 넣어서 반환하고 이름이 다르면 예전 원소를 그대로 반환한다.

 

7. Editing Arrays of Objects

editName 함수를 한 줄로 쓸 수도 있다.

다음은 if/else 문장 대신 3항 연산자(? :)를 사용해 editName 함수를 짧게 쓴 코드다.

    let schools = [
        { name: "Yorktown"},
        { name: "Stratford" },
        { name: "Washington & Lee"},
        { name: "Wakefield"}
    ]

    // Edit Name with less syntax

    const editName = (oldName, name, arr) =>
        arr.map(item => (item.name === oldName) ?
            ({...item,name}) :
            item
        )

    let updatedSchools = editName("Stratford", "HB Woodlawn", schools)

    console.log( updatedSchools[1] )
    console.log( schools[1] )

 

 

8. Object.keys()

객체를 배열로 변환하고 싶을 때는 Array.map과 Object.keys를 함께 사용하면 된다.

Object.keys는 어떤 객체의 키로 이뤄진 배열을 반환하는 메서드다.

 

Schools 객체를 학교의 배열로 바꾸고 싶다고 치자.

    const schools = {
      "Yorktown": 10,
      "Washington & Lee": 2,
      "Wakefield": 5
    }

    const schoolArray = Object.keys(schools).map(key =>
        ({
            name: key,
            wins: schools[key]
        })
    )

    console.log(schoolArray)

 

 

 

9. Reducing Arrays

reduce와 reduceRight 함수를 사용하면 객체를 수, 문자열, 불린 값, 객체, 심지어 함수와 같은 값으로 변환할 수 있다.

수로 이뤄진 배열에서 최댓값을 찾을 필요가 있다고 하면 배열을 하나의 수로 변환해야 하므로 reduce를 사용할 수 있다.

 

const ages = [21,18,42,40,64,63,34];

const maxAge = ages.reduce((max, age) => {
    console.log(`${age} > ${max} = ${age > max}`)
    if (age > max) {
        return age
    } else {
        return max
    }
}, 0)

console.log('maxAge', maxAge)

 

 

 

10. Array.reduce() Shorter Syntax

앞의 함수에서 if/else를 짧게 변경하면 다음 코드를 사용해 배열의 최댓값을 계산할 수 있다.

 

const ages = [21,18,42,40,64,63,34];

// less syntax

const max = ages.reduce(
    (max, value) => (value > max) ? value : max, 
    0
)

console.log('max', max)
Array.reduceRight는 Array.reduce와 같은 방식으로 동작한다. 다만 Array.reduceRight는 배열의 첫 번째 원소부터가 아니라 맨 마지막 원소부터 축약을 시작한다는 점이 다르다.

 

 

11. Colors Hash

배열을 객체로 변환해야 할 때는 reduce를 사용해 값이 들어 있는 배열을 해시로 변환한다.

const colors = [
    { 
        id: '-xekare',
        title: "rad red", 
        rating: 3 
    }, 
    { 
        id: '-jbwsof',
        title: "big blue", 
        rating: 2 
    }, 
    { 
        id: '-prigbj',
        title: "grizzly grey", 
        rating: 5 
    },
    { 
        id: '-ryhbhsl',
        title: "banana", 
        rating: 1 
    }
]

const hashColors = colors.reduce(
    (hash, {id, title, rating}) => {
        hash[id] = {title, rating}
        return hash
    }, 
    {}
)

console.log(hashColors)

여기에서 reduce에 전달한 두 번째 인자는 빈 객체다.

 

12. distinctColors()

reduce를 사용해 배열을 전혀 다른 배열로 만들 수도 있다!

같은 값이 여럿 들어 있는 배열을 서로 다른 값이 한 번씩만 들어 있는 배열로 바꿀 수 있다.

const colors = ["red", "red", "green", "blue", "green"];

const distinctColors = colors.reduce(
    (distinct, color) => 
        (distinct.indexOf(color) !== -1) ? 
            distinct : 
            [...distinct, color],
    []
)

console.log(distinctColors)

여기에서는 color 배열을 중복값을 제거한 배열로 재탄생시킨다.

reduce 함수에 전달한 두 번째 인자는 빈 배열이다. 이 배열은 unique의 최초 값이다.

unique 배열에 어떤 색이 이미 들어있지 않다면 그 색을 추가한다. 만약 이미 들어있다면 아무 일도 하지 않고 넘어가기 위해 현재의 unique를 그대로 반환한다.

 

map과 reduce는 함수형 프로그래머가 주로 사용하는 무기이며 자바스크립트도 예외가 아니다.

자바스크립트를 잘 다루고 싶다면 반드시 이 두 함수에 능해야 한다.

한 데이터 집합에서 다른 데이터 집합을 만들어내는 능력은 꼭 필요한 기술이며 프로그래밍 패러다임과 관계없이 유용하다.

 

 

참고자료

러닝 리액트(Learning React), 알렉스 뱅크스, 한빛미디어 (2021)

728x90
반응형