ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • typescript - exercises 3일차
    Typescript 2023. 7. 12. 15:47

    🔔 서론

    어느덧 3일차에 접어든 typescript exercises 스터디, 아마 오늘이 가장 고비가 아닐까 싶다. 총 16문제를 5일에 걸쳐 4, 4, 4, 2, 2 문제씩 풀기로 했기때문에 난이도가 어느정도 높으면서 많은 문제를푸는 마지막 날이기 때문이다.

    문제를 접하고나니 역시 예상대로 난이도가 너무나도 높아서 좌절하고 싶었지만.. 그만큼 아직 배울게 많이 남아 있다는거니까 아무래도 좋다..

    중요한건 꺾이지 않는 마음일 것.

    https://typescript-exercises.github.io/

     

    TypeScript Exercises

    A set of interactive TypeScript exercises

    typescript-exercises.github.io

     

    이번 포스트는 위 사이트의 9~12번 문제를 풀며 각 문제 풀이에 해당하는 핵심 키워드 개념을 정리하는 글이다.


    🥁 9번

    Generic Type을 활용해 해결하는 문제다.

    /*
    Exercise:
        Remove UsersApiResponse and AdminsApiResponse types
        and use generic type ApiResponse in order to specify API
        response formats for each of the functions.
    */
    
    interface User {
        type: 'user';
        name: string;
        age: number;
        occupation: string;
    }
    
    interface Admin {
        type: 'admin';
        name: string;
        age: number;
        role: string;
    }
    
    type Person = User | Admin;
    
    const admins: Admin[] = [
        { type: 'admin', name: 'Jane Doe', age: 32, role: 'Administrator' },
        { type: 'admin', name: 'Bruce Willis', age: 64, role: 'World saver' }
    ];
    
    const users: User[] = [
        { type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep' },
        { type: 'user', name: 'Kate Müller', age: 23, occupation: 'Astronaut' }
    ];
    
    export type ApiResponse<T> = (
        {
            status: 'success';
            data: T;
        } |
        {
            status: 'error';
            error: string;
        }
    );
    export function requestAdmins(callback: (response: ApiResponse<Admin[]>) => void) {
        callback({
            status: 'success',
            data: admins
        });
    }
    
    export function requestUsers(callback: (response: ApiResponse<User[]>) => void) {
        callback({
            status: 'success',
            data: users
        });
    }
    
    export function requestCurrentServerTime(callback: (response: ApiResponse<number>) => void) {
        callback({
            status: 'success',
            data: Date.now()
        });
    }
    
    export function requestCoffeeMachineQueueLength(callback: (response: ApiResponse<number>) => void) {
        callback({
            status: 'error',
            error: 'Numeric value has exceeded Number.MAX_SAFE_INTEGER.'
        });
    }
    
    function logPerson(person: Person) {
        console.log(
            ` - ${person.name}, ${person.age}, ${person.type === 'admin' ? person.role : person.occupation}`
        );
    }
    
    function startTheApp(callback: (error: Error | null) => void) {
        requestAdmins((adminsResponse) => {
            console.log('Admins:');
            if (adminsResponse.status === 'success') {
                adminsResponse.data.forEach(logPerson);
            } else {
                return callback(new Error(adminsResponse.error));
            }
    
            console.log();
    
            requestUsers((usersResponse) => {
                console.log('Users:');
                if (usersResponse.status === 'success') {
                    usersResponse.data.forEach(logPerson);
                } else {
                    return callback(new Error(usersResponse.error));
                }
    
                console.log();
    
                requestCurrentServerTime((serverTimeResponse) => {
                    console.log('Server time:');
                    if (serverTimeResponse.status === 'success') {
                        console.log(`   ${new Date(serverTimeResponse.data).toLocaleString()}`);
                    } else {
                        return callback(new Error(serverTimeResponse.error));
                    }
    
                    console.log();
    
                    requestCoffeeMachineQueueLength((coffeeMachineQueueLengthResponse) => {
                        console.log('Coffee machine queue length:');
                        if (coffeeMachineQueueLengthResponse.status === 'success') {
                            console.log(`   ${coffeeMachineQueueLengthResponse.data}`);
                        } else {
                            return callback(new Error(coffeeMachineQueueLengthResponse.error));
                        }
    
                        callback(null);
                    });
                });
            });
        });
    }
    
    startTheApp((e: Error | null) => {
        console.log();
        if (e) {
            console.log(`Error: "${e.message}", but it's fine, sometimes errors are inevitable.`)
        } else {
            console.log('Success!');
        }
    });

    🥁 10번

    Generic Type을 활용해 해결하는 문제다.

    /*
    Exercise:
        We don't want to reimplement all the data-requesting
        functions. Let's decorate the old callback-based
        functions with the new Promise-compatible result.
        The final function should return a Promise which
        would resolve with the final data directly
        (i.e. users or admins) or would reject with an error
        (or type Error).
    
        The function should be named promisify.
    
    Higher difficulty bonus exercise:
        Create a function promisifyAll which accepts an object
        with functions and returns a new object where each of
        the function is promisified.
    
        Rewrite api creation accordingly:
            const api = promisifyAll(oldApi);
    */
    
    interface User {
        type: 'user';
        name: string;
        age: number;
        occupation: string;
    }
    
    interface Admin {
        type: 'admin';
        name: string;
        age: number;
        role: string;
    }
    
    type Person = User | Admin;
    
    const admins: Admin[] = [
        { type: 'admin', name: 'Jane Doe', age: 32, role: 'Administrator' },
        { type: 'admin', name: 'Bruce Willis', age: 64, role: 'World saver' }
    ];
    
    const users: User[] = [
        { type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep' },
        { type: 'user', name: 'Kate Müller', age: 23, occupation: 'Astronaut' }
    ];
    
    export type ApiResponse<T> = (
        {
            status: 'success';
            data: T;
        } |
        {
            status: 'error';
            error: string;
        }
    );
    
    export function promisify<T>(arg: (callback: (response: ApiResponse<T>) => void) => void): () => Promise<T> {
        return () => new Promise((resolve, reject) => {});
    }
    
    const oldApi = {
        requestAdmins(callback: (response: ApiResponse<Admin[]>) => void) {
            callback({
                status: 'success',
                data: admins
            });
        },
        requestUsers(callback: (response: ApiResponse<User[]>) => void) {
            callback({
                status: 'success',
                data: users
            });
        },
        requestCurrentServerTime(callback: (response: ApiResponse<number>) => void) {
            callback({
                status: 'success',
                data: Date.now()
            });
        },
        requestCoffeeMachineQueueLength(callback: (response: ApiResponse<number>) => void) {
            callback({
                status: 'error',
                error: 'Numeric value has exceeded Number.MAX_SAFE_INTEGER.'
            });
        }
    };
    
    export const api = {
        requestAdmins: promisify<Admin[]>(oldApi.requestAdmins),
        requestUsers: promisify<User[]>(oldApi.requestUsers),
        requestCurrentServerTime: promisify<number>(oldApi.requestCurrentServerTime),
        requestCoffeeMachineQueueLength: promisify<number>(oldApi.requestCoffeeMachineQueueLength)
    };
    
    function logPerson(person: Person) {
        console.log(
            ` - ${person.name}, ${person.age}, ${person.type === 'admin' ? person.role : person.occupation}`
        );
    }
    
    async function startTheApp() {
        console.log('Admins:');
        (await api.requestAdmins()).forEach(logPerson);
        console.log();
    
        console.log('Users:');
        (await api.requestUsers()).forEach(logPerson);
        console.log();
    
        console.log('Server time:');
        console.log(`   ${new Date(await api.requestCurrentServerTime()).toLocaleString()}`);
        console.log();
    
        console.log('Coffee machine queue length:');
        console.log(`   ${await api.requestCoffeeMachineQueueLength()}`);
    }
    
    startTheApp().then(
        () => {
            console.log('Success!');
        },
        (e: Error) => {
            console.log(`Error: "${e.message}", but it's fine, sometimes errors are inevitable.`);
        }
    );

    🥁 11번

    index.d.ts 파일을 완성시키는 문제이다.

    🌈 .d.ts

    .d.ts 파일은 type이 선언되지 않은 라이브러리를 사용하기 위해 type을 재정의하는 모듈을 만드는 것

     

    요구사항: node_modules/str-utils/index.js 의 모듈 구현을 확인하고, declarations/str-utils/index.d.ts에 타입을 선언하는데 단, type 선언의 반복을 피하고 type aliases를 사용

    /* 요구사항
    Exercise:
        Check str-utils module implementation at:
        node_modules/str-utils/index.js
        node_modules/str-utils/README.md
    
        Provide type declaration for that module in:
        declarations/str-utils/index.d.ts
    
        Try to avoid duplicates of type declarations,
        use type aliases.
    */

     

    모듈 구현에서 확인할 것 두 가지 : 함수들의 매개변수 type, 함수 리턴 type

    // node_modules/str-utils/index.js
    // implementation
    
    /**
     * Reverses a string.
     * @param {String} value
     * @return {String}
     */
    function strReverse(value) {
        return value.split('').reverse().join('');
    }
    
    /**
     * Converts a string to lower case.
     * @param {String} value
     * @return {String}
     */
    function strToLower(value) {
        return value.toLowerCase();
    }
    
    /**
     * Converts a string to upper case.
     * @param {String} value
     * @return {String}
     */
    function strToUpper(value) {
        return value.toUpperCase();
    }
    
    /**
     * Randomizes characters of a string.
     * @param {String} value
     * @return {String}
     */
    function strRandomize(value) {
        var array = value.split('');
        for (var i = array.length - 1; i > 0; i--) {
            var j = Math.floor(Math.random() * (i + 1));
            var temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }
        return array.join('');
    }
    
    /**
     * Inverts case of a string.
     * @param {String} value
     * @return {String}
     */
    function strInvertCase(value) {
        return value
            .split('')
            .map(function(c) {
                if (c === c.toLowerCase()) {
                    return c.toUpperCase();
                } else {
                    return c.toLowerCase();
                }
            })
            .join('');
    }
    
    module.exports = {
        strReverse,
        strToLower,
        strToUpper,
        strRandomize,
        strInvertCase
    };

    모든 함수의 매개변수 type과 리턴 type이 string

    따라서, 입력값이 string, 리턴값이 string인 함수 콜시그니처를 type aliases를 통해 만들면 된다.

    // declarations/str-utils/index.d.ts
    declare module 'str-utils' {
        type StrUtils = {
            (value: string): string
        }
    
        export const strReverse: StrUtils;
        export const strToLower: StrUtils;
        export const strToUpper: StrUtils;
        export const strRandomize: StrUtils;
        export const strInvertCase: StrUtils;
    }

    🥁 12번

    index.d.ts 파일을 완성시키는 문제이다.

    요구사항: node_modules/stats/index.js 의 모듈 구현을 확인하고, declarations/stats/index.d.ts에 타입을 선언하는데 단, type 선언의 반복을 피하고 type aliases를 사용

    /* 요구사항
    Exercise:
        Check stats module implementation at:
        node_modules/stats/index.js
        node_modules/stats/README.md
    
        Provide type declaration for that module in:
        declarations/stats/index.d.ts
    
    Higher difficulty bonus exercise:
        Avoid duplicates of type declarations.
    */

     

    - 함수 구현

    // node_modules/stats/index.js
    // implements
    
    function getMaxIndex(input, comparator) {
        if (input.length === 0) {
            return -1;
        }
        var maxIndex = 0;
        for (var i = 1; i < input.length; i++) {
            if (comparator(input[i], input[maxIndex]) > 0) {
                maxIndex = i;
            }
        }
        return maxIndex;
    }
    
    function getMaxElement(input, comparator) {
        var index = getMaxIndex(input, comparator);
        return index === -1 ? null : input[index];
    }
    
    function getMinIndex(input, comparator) {
        if (input.length === 0) {
            return -1;
        }
        var maxIndex = 0;
        for (var i = 1; i < input.length; i++) {
            if (comparator(input[maxIndex], input[i]) > 0) {
                maxIndex = i;
            }
        }
        return maxIndex;
    }
    
    function getMinElement(input, comparator) {
        var index = getMinIndex(input, comparator);
        return index === -1 ? null : input[index];
    }
    
    function getMedianIndex(input, comparator) {
        if (input.length === 0) {
            return -1;
        }
        var data = input.slice().sort(comparator);
        return input.indexOf(data[Math.floor(data.length / 2)]);
    }
    
    function getMedianElement(input, comparator) {
        var index = getMedianIndex(input, comparator);
        return index === -1 ? null : input[index];
    }
    
    function getAverageValue(input, getValue) {
        if (input.length === 0) {
            return null;
        }
        return input.reduce(
            function (result, item) {
                return result + getValue(item);
            },
            0
        ) / input.length;
    }
    
    module.exports = {
        getMaxIndex,
        getMaxElement,
        getMinIndex,
        getMinElement,
        getMedianIndex,
        getMedianElement,
        getAverageValue
    };

    발견된 공통점: 모든 함수가 input값으로 array type을받고, comparator 함수를 매개변수로 받고 있고, comparator 함수는 두 개의 변수를 받아서 number type을 리턴. 함수 명이 Index로 끝나는 함수는 number 타입을 리턴하고, Element로 끝나는 함수는 배열의 요소 또는 null값을 리턴.

    공통점을 Generic Type을 통해 선언해주면 된다.

    declare module 'stats' {
        type Comparator<T> = (a: T, b: T) => number;
        type GetIndex = <T>(input: T[], comparator: Comparator<T>) => number;
        type GetElement = <T>(input: T[], comparator: Comparator<T>) => T | null;
    
        export const getMaxIndex: GetIndex;
        export const getMinIndex: GetIndex;
        export const getMedianIndex: GetIndex;
        export const getMaxElement: GetElement;
        export const getMinElement: GetElement;
        export const getMedianElement: GetElement;
        export const getAverageValue: <T>(input: T[], getValue: (item: T) => number) => number | null;
    }

    💊 3일차 후기

    꺾여버린 마음.... 특히나 10번문제를 3시간정도를 들여다 보고 있을 때 너무나도 고통과 인내의 시간이었다. 하지만 이제 고비가 끝났고, 내일부터는 2문제씩 풀기 때문에 비교적 가벼운? 마음으로 임할 수 있을거라 생각한다.. 남은 문제들도 파이팅

Designed by Tistory.