MancityBallboy 흔적 남기기

제너레이터

제너레이터란 이터레이터를 사용해 자신의 실행을 제어하는 함수입니다. 일반적인 함수는 매개변수를 받고 값을 반환하지만, 호출자는 매개변수 외에는 함수의 실행을 제저할 방법이 전혀 없습니다. 함수를 호출하면 그 함수가 종료될 때까지 제어권을 완전히 넘기는 것입니다. 제너레이터에서는 그렇지 않습니다.

제너레이터에는 2가지 새로운 개념이 있습니다.

  • 함수의 실행을 개별적 단계로 나눔으로써 함수의 실행을 제어한다.
  • 실행 중인 함수와 통신한다.

제너레이터와 일반적인 함수는 2가지의 차이가 있습니다.

  • 제너레이터는 언제든 호줄자에게 제어권을 넘길 수 있다.
  • 제너레이터는 호출한 즉시 실행되지 않습니다. 대신 이터레이터를 반환하고, 이터레이터의 next 메소드를 호출함에 따라 실행 됩니다.

EX) 제너레이터를 호출하면 이터레이터를 얻게됩니다.

function* rainbow(){
	yield 'red'
    yield 'orange'
    yield 'yellow'
    yield 'green'
    yield 'blue'
    yield 'navy'
    yield 'violet'
}

let it = rainbow()
it.next() // {value:'red', done:false}
it.next() // {value:'orange', done:false}
it.next() // {value:'yellow', done:false}
it.next() // {value:'green', done:false}
it.next() // {value:'blue', done:false}
it.next() // {value:'navy', done:false}
it.next() // {value:'violet', done:false}
it.next() // {value:undefined, done:true}

제너레이터에서 중요한건 양방향 통신이 된다는 것 입니다.

function* interrogate() {
    const name = yield "What is your name"
    const food = yield "what is your favorite food"
    return `${name}'s favorite food is ${food}`
}

let it = interrogate()
it.next()            // {value: "what is your name", done:false}
it.next('Ethan')     // {value: "what is your favorite food", done:false}
it.next('orange')    // {value: "Ethan's favorite food is orange", done:true}

각 각의 단계에서 어떤일이 일어난건지에 대해 알아보겠습니다.

  1. let it = iterrogate()를 하면 일단 제너레이터는 이터레이터를 반환합니다.
  2. it.next() 이터레이터의 value는 what is your name이 되고 yield를 멈춥니다.
  3. it.next('Ethan')제너레이터에 Ethan이 넘겨지고 name에는 Ethan 저장되고 다시 what is your favorite food를 넘기고 다시 멈춥니다.
  4. it.next('orange')제너레이터에 orange를 넘기고 Ethan’s favorite food is orange를 리턴합니다. yield와 다르게 return을 이용하면 done은 true로 반환됩니다. 즉 return으로 반환하면 루프를 이용해 접근했을때 프로퍼티를 반환하지 않는 다는 것에 주의해야합니다.

출처

  • Learning JavaScript (한빛 미디어)

이터레이터 (Iterator)

ES6에서는 매우 중요한 새로운 개념 이터레이터 와 제너레이터를 도입했습니다. 제너레이터는 이터레이터에 의존하는 개념이니 이터레이터 먼저 설명하고 넘어 가도록 하겠습니다.

이터레이터

이터레이터는 ‘지금 어디 있는지’ 파악할 수 있도록 돕는다는 면에서 일종의 책갈피와 비슷한 개념입니다. 배열이 대표적인 이터러블 객체입니다.

책이 배열이고 각 배열의 값이 한페이지에 있는 책의 내용이라 했을때를 예로들어 보겠습니다.

const rabit_and_turtle = [
    "옛날 옛날에 바다 깊은 곳에는 용궁이 있고 그곳에는 용왕이 살았습니다.",
    "용왕님의 병세는 날이 갈수록 안좋아 지셨고 토끼의 간을 먹어야만이 회복할 수 있었습니다.",
    "거북이는 용왕님의 병을 고치기 위해 육지로 나와 토끼를 찾았습니다.",
    "거북이는 토끼를 만나자 간을 달라고 했지만, 토끼는 달리기를 이긴다면 준다고 약속했습니다.",
]
// 다음과 같은 책이 있습니다.
// values 라는 메소드를 이용해서 it이라는 이터레이터를 생성할 수 있습니다.
const it = rabit_and_turtle.values()

it.next() // {value : "옛날 옛날에 바다 깊은 곳에는 용궁이 있고 그곳에는 용왕이 살았습니다.", done: false}
it.next() // {value : "용왕님의 병세는 날이 갈수록 안좋아 지셨고 토끼의 간을 먹어야만이 회복할 수 있었습니다.", done: false}
it.next() // {value : "거북이는 용왕님의 병을 고치기 위해 육지로 나와 토끼를 찾았습니다.",, done: false}
it.next() // {value : "거북이는 토끼를 만나자 간을 달라고 했지만, 토끼는 달리기를 이긴다면 준다고 약속했습니다.", done: false}
it.next() // {value : undefined , done: true} 모든 페이지를 읽으면 done이 true로 바뀝니다.
it.next() // {value : undefined , done: true} 

여기서 중요한점이 몇가지 있습니다.

  • next에서 책의 마지막 페이지를 반환했다 해서 끝난 것은 아니라는 것입니다. 책과는 다르게 모든 페이지를 읽었다고 해서 그 다음 페이지를 읽을 수 없는 것은 아닙니다. 물론 그 다음 페이지도 다른 결과 값을 가지는 것은 아닙니다. 일단 이터레이터는 끝까지 진행을 하면 뒤로 돌아가서 다른 데이터를 제공할 수 없습니다.

이터레이션 프로토콜

이터레이터가 그 자체로 크게 쓸모있다기 보다는, 더 쓸모 있는 동작이 가능해지도록 한다는데 의미가 있습니다. 이터레이션 프로토콜을 모든 객체를 이터러블 객체로 바꿀수 있습니다. 메시지에 타임스탬프를 붙이는 로그 클래스가 필요하다고 생각해 봅시다. 내적으로 타임 스탬프가 붙은 메시지는 배열에 저장합니다.

로그를 기록한 항목을 순회하기 위해 이터레션 프로토콜을 사용할 수 있습니다. 이터레이션 프로토콜은 클래스에 심볼 메서드 Symbol.iterator가 있고 이 메서드가 valuedone 을 포함한 객체를 반환하는 메소드인 next()를 포함하고 있다면 그 클래스의 인스턴스는 이터러블 객체라는 뜻 입니다.

class Log {
    constructor() {
        this.messages = [];
    }
    add(message) {
        this.messages.push({message, timestamp : Data.now()})
    }
    [Symbol.iterator]() {
        return this.meaages.values();
    }
}

const log = new Log()
log.add("안녕하세요")
log.add("log는 이터러블할까요?")
log.add("확인해 보시죠")

for(let entry of log) {
    console.log(`${entry.message} @ ${entry.timestamp}`)
}

image

잘 작동하는것을 볼수 있다.

이제까지는 이터레이터가 한정된 객체에서만 사용해 보았습니다. 하지만 피보나치 수열과 같은 무한한 객체에서도 이터레이터를 사용할 수 있습니다.

class FibonacciSequence {
    [Symbol.iterator]() {
        let a = 0, b = 1
        return {
            next() {
                let currentVal = {value: b, done: false}
                b += a
                a = currentVal.value
                return currentVal
            }
        }
    }
}

// 1, 1, 2, 3, 5, 8, 13, 21, 34 ......... 무한히 나올 것이다.

참고 도서

Learning JavaScript (한빛 미디어)

2021 상반기 라인 인터쉽 코딩테스트 후기 및 복기

2021 상반기 라인 인턴쉽 코딩테스트 후기

오늘 라인 인턴쉽 코딩테스트를 쳤습니다.

2시간 동안 3문제를 풀어야 했고, 1번 문제와 3번 문제는 해결 했다고 생각하니 풀지 못한 2번만 복기 하도록 하겠습니다.

문제

배열이 주어지면 각 인덱스의 숫자를 기준으로 기준이 된 숫자 보다 크면서 가장 가까이 위치한 숫자의 인덱스를 기준이된 인덱스에 저장하여서 완성된 배열을 만드는 문제였습니다.

  • 가장 가까이 있는 인덱스가 2개라면 작은 인덱스를 저장하여야 합니다.
  • 배열 내에 자기보다 큰 수가 없다면 -1을 저장하여야 합니다.

EX)

input = [3,5,4,1,2]

output = [1, -1, 1, 2, 2]

3은 5가 가장 가까이 있으면서 큰 수 이기 때문에 5의 인덱스인 1을 저장합니다.

5는 본인 보다 큰 숫자가 없기에 -1을 저장합니다.

1은 자기보다 크면서 가장 가까운 수가 4, 2 가 있지만 4의 인덱스는 2, 2의 인덱스는 4 이므로 더 작은 인덱스인 2를 저장합니다.

나의 풀이

문제에 이미 O(n)이상의 효율성을 가진 알고리즘을 구현하여야 한다고 제시 되어있었지만, 그런 풀이를 생각해내지 못해 각 인덱스에서 좌우로 옮겨가면서 값을 구해내는 조금은 비효율적인 풀이를 제시 하였습니다.

더 좋은 풀이

비슷한 문제인 백준 사이트의 오큰수 문제를 참고하였습니다.

오큰수문제는 위의 문제가 거의 유사하며 위의 문제는 양쪽에서 큰 수를 찾는 문제지만 오큰수 문제는 오른쪽 큰 수에만 집중하고 있기 때문에 오큰수 문제를 해결한 알고리즘을 응용하여 왼큰수 알고리즘을 만들어 문제를 해결 하였습니다.

개략적 알고리즘

stack을 활용

설명을 용이하게 하기위해 오른쪽 큰수를 찾는과정을 오큰수 왼쪽 큰수를 찾는 과정을 왼큰수로 얘기하겠습니다.

  1. 위의 문제는 좌우 모두 포함된 문제 이므로 오른쪽과 왼쪽을 나누어서 해결 할 것입니다.
  2. 같은 거리에 있는 숫자라면 더 작은 인덱스를 저장할것 이기때문에 왼큰수 먼저 시행한후 오큰수를 시행 하겠습니다.

  3. stack에는 왼큰수를 저장하지 못한 숫자들을 저장할 것입니다.

  4. 숫자를 저장한 후 바로 앞 숫자와 stacktop를 비교하여 stacktop가 작다면 stacktop왼큰수는 뒷 숫자 이므로 배열에 저장합니다.

  5. stack이 비게되거나 stacktop의 값이 뒷 숫자보다 크거나 같아질때 까지 4번 순서를 반복합니다.

    설명 추가

    stack에는 왼쪽에서 자기 보다 큰 수를 만나지 못한 수들이 쌓이게 됩니다. 그리고 큰 수를 만나게 되면 stack에서 빠지게 되므로 자연스럽게 가장 가까이 있으면서 큰 수를 찾을 수 있게 됩니다. 하지만 그 큰 수가 한 숫자에 대한 큰 수 가 아니기에 stack top가 크거나 같은 수 이거나 stack이 비워질때까지 pop되는 모든 수에 대해 적용하여야 합니다.

  6. 각 인덱스 별로 3,4,5의 과정을 거치면 stack에 남아 있는 숫자는 더 큰 수가 존재하지 않는 수이므로 -1을 저장합니다.

  7. 오큰수도 똑같이 반대로 진행합니다.

Solution

def solution(list):
    answer = [[float('inf'), -1] for _ in range(len(list))]
    
    ## left_bigger_number
    stack = []
    for idx in range(len(list)-1, 0, -1):
        stack.append(idx)
        while stack and list[stack[-1]] < list[idx - 1]:
            new_idx = stack.pop()       
            answer[new_idx] = [abs(new_idx - (idx - 1)), idx - 1]  

    ## right_bigger_number
    stack = []
    for idx in range(len(list)-1):
        stack.append(idx)
        while stack and list[stack[-1]] < list[idx + 1]:
            new_idx = stack.pop()
            if abs(new_idx - (idx + 1)) < answer[new_idx][0]:           ## 같은 거리일 때는 인덱스가 작은것을 저장할 것
                answer[new_idx] = [abs(new_idx - (idx + 1)), idx + 1]
    
    
    print([x[1] for x in answer])

TDD 맛만 보기

TTD의 시작

TTD의 창시자 켄트백은 TTD의 진정한 가치는 어려운일을 작은 단계로 쪼갤 수 잇는 능력이라고 했습니다. 개발 요구 사항을 아주 작은 단계로 쪼개어서 하나씩 해나갈때 먼저 작성된 테스트가 올바른 길잡이가 되어준다고 말했습니다.

TTD의 장점을 올바른 테스트 코드라고 생각한다면 오산입니다. TTD의 장점은 어려운일을 작은 단계로 쪼갤수 있는 능력 이며 테스크 코드는 그런 일을 하기 위한 방식일 뿐입니다.

TDD의 과정

  • TEST FAIL
  • TEST SUCCESS
  • REFACTOR

TDD는 테스트 코드를 작성 한 후 통과할 수 있는 테스트 코드로 만든다. 그리고 그 테스트 코드가 통과 한다면 리팩토링을 하는 과정을 반복하면 된다. 중요한것은 항상 테스트 코드를 먼저 작성한 후 로직을 짜는 것이다. (사실 아직 이 말의 의미를 정확하게는 이해하지 못했다. 점점 TDD를 알아가면서 이해할 수 있을 것이라 믿고 있다.)

사용 툴

TDD를 위해서 어떤 툴을 이용할 것이냐하면 JEST를 이용할 것이다. JEST는 FE개발에 이용되는 많은 언어를 테스트 할 수 있는 툴이다.

일단 JEST를 가볍게 이용해 보도록 하겠습니다.

실습

일단 빈 폴더에 adder.js라는 파일을 만듭니다.

우리는 adder.js를 테스트 하기 위한 환경을 구성하겠습니다.

npm init
npm i --save-dev jest

package.json

...
"scripts": {
    "test": "jest"
  },
...

npm test를 이용하기 위해 package.json에 test를 위해 이용할 툴을 jest로 설정해 줍니다.

adder.js

module.export = null;

adder.test.js

const { expect } = require("@jest/globals")
const adder = require("./adder")

test("should add two numbers", () => {
    expect(adder(1, 2)).toBe(3) // 여기서 toBe는 matchers라고 하는데 이번에는 toBe많은 이용해서 jest 사용법을 맛보겠습니다.
    							// toBe()는 값을 확인하는 matchers입니다.
    							// 즉, adder(1,2)가 3을 리턴하는지 확인하는 테스트 코드라고 할 수 있습니다.
})

터미널에 npm test를 입력합니다.

image

adder.js의 함수를 완성하지 않았지 때문에 당연히 테스트 코드는 실패 합니다.

adder.js

const adder = (a, b) => {
    return a + b
}

module.exports = adder

adder.js를 다음과 같이 고치고 테스트를 실행해보면

image

테스트가 성공적으로 마무리 되었습니다.

그럼 조금 더 향상된 adder.js를 만들어 봅시다.

adder.test.js

test("should add three numbers",  () => {
    expect(adder(1, 2, 3)).toBe(6)
}
// adder.text.js에 다음과 같은 테스트를 추가한후 테스트 해봅시다.

image

역시나 실패입니다. 이 테스트 코드를 통과하기 위해 adder.js함수에 몇개의 수가 들어오던지 그 수의 총합을 리턴할 수 있게 향상 시켜봅시다.

adder.js

const adder = (...nums) => {
    return nums.reduce((total, num) => total, num, 0) 
}

module.exports = adder

image-20210423210500782

테스트 결과는 역시나 성공적입니다.

오늘은 jest를 이용해 간단하게 테스트 코드를 작성하고 테스트하는 법에 대해서 알아 보았습니다. 이 글을 시작하면서 얘기 했듯이 테스트는 단지 TDD를 위한 방식일뿐 TDD의 진정한 가치와는 거리가 있습니다. 하지만 테스트 하는 방법을 알았으니 이제 어떻게 테스트 코드를 작성하는지에 대해서만 알면 TDD에 한걸을 다가갈 수 있을 것입니다.

다음에 올릴 글은 FE개발에서 어떻게 테스트 코드를 작성하고 테스트 하는지 알아보도록 하겠습니다.

참고 사이트

https://jestjs.io/

git 주소

https://github.com/yhnb3/fe-with-tdd

웹팩의 이해(1)

웹팩이란?

웹팩이란 최신 프론트엔드 프레임워크에서 가장 많이 사용되는 모듈 번들러 입니다.

모듈번들러란 ?

웹 애플리케이션을 구성하는 자원(HTML, CSS, JavaScript, Images 등)을 모두 각각의 모듈로 보고 이를 조합해서 병합된 하나의 겨로가물을 만드는 도구를 의미합니다.

image

이미지 출처 : https://webpack.js.org/

웹팩에서의 모듈들은 이미지상의 오른쪽을 의미합니다. 오른쪽의 수 많은 모듈들을 이용하여 하나의 파일로 병합 및 압축해주는 것이 모듈 번들링이자 웹팩이 하는 일입니다.

웹팩이 필요한 이유

  1. 파일 단위의 자바스크립트 모듈 관리

    javaScript로 구현하는 로직과 그 코드의 양이 많아지면서 많은 사람들은 코드의 유지 및 보수를 쉽게 하기위해 모듈로 나누어 관리하는 모듈 시스템을 필요로하였고 webpack은 그러한 니즈를 만족 시켜주는 프로그램이다.

  2. 웹 개발 작업 자동화 도구

    이전부터 프런트엔드 개발 업무를 할 때 가장 많이 반복하는 작업은 텍스트 편집기에서 코드를 수정한후 브라우저에서 새로 고침을 누르는 것이 었습니다. 그래야 화면에 변경된 내용을 볼 수 있었습니다.

    이 외에도 웹 서비스를 개발하고 웹 서버에 배포할 때 다음과 같은 작업을 해야 했습니다.

    • HTML, CSS, JS 압축
    • 이미지 압축
    • CSS 전처리기 변환

    이러한 일들을 자동화 해주는 도구들이 필요했습니다. 그래서 Grunt와 Gulp 같은 도구들이 등장했습니다.

  3. 웹 애플리케이션의 빠른 로딩 속도와 높은 성능

    일반적으로 특정 웹사이트를 접근할 대 5초 이내로 웹 사이트가 표시되지 않으면 대부분의 사용자들은 해당 사이트를 벗어나거나 집중력을 잃게 됩니다.

    그래서 웹 사이트의 로딩 속도를 높이기 위해 많은 노력들이 있었습니다. 그 중 대표적인 노력이 브라우저에서 서버로 요청하는 파일수를 줄이는것이며 이는 웹팩이 크게 도움을 줄 수 있는 부분입니다.

참고 사이트

  • https://joshua1988.github.io/webpack-guide/concepts/loader.html#css-loader-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0
  • https://d2.naver.com/helloworld/0239818
  • https://webpack.js.org/