기억의 실마리
2023. 11. 8. 00:07

1. Three.js

WebGL엔진을 기반으로 브라우저에서 3D 그래픽 구현

보다 쉽고 직관적으로 생성, 관리할 수 있도록 제공해주는

라이브러리 이다. 기본적인 WebGL은 점, 선, 삼각형을 그리는

단순한 시스템을 가지고 있으며 Three.js를 사용하지 않아도

모든 그래픽 구현이 가능하지만 코드의 복잡도는 비교 조차

안될만큼 복잡해지고 관리 또한 난이도가 올라가기 때문

아직 시도할 엄두가 나지 않지만 추 후 시간을 내어 다뤄봐도

좋은 경험이 될 것 같다.

 

2. Three.js 구조

 

Renderer

Scene, Camera 객체 데이터를 전달 받아 화면 내부 3D Scene의

일부 객체들을 2차원 평면 이미지로 렌더링해주는 핵심기능을 수행한다.

 

 

Scene

다수의 Mesh, Light, Group, Object3D, Camera로 이루어진 트리 구조이다.

배경색, 안개, 다수의 오브젝트, 빛, 질감 등을 표현하는 화면이다.

Scene은 최상위 노드이기 때문에 객체의 위치와 방향이 부모(Scene)기준이다.

 

 

Camera

Camera는 다른 구성 객체와는 달리 Scene 그래프에 포함되지 않으며

Scene객체를 촬영하여 어떻게 보여줄 것인가를 결정하는 역할을 한다.

 

 

Light

여러 종류의 광원을 말하며 이는 즉 조명을 뜻한다.

AmbientLight, SpotLight, DirectionalLight 등 다양한 광원을 활용하여

Scene에 존재하는 다양한 3D요소들을 3D 공간 공간에서 볼 수 있다.

 

 

Geometry

3D요소의 모양을 정의할 수 있으며 내장객체 또는 파일을 통해 형상을 만들 수 있다.

 

 

Material

질감을 뜻하며 표면의 색상, 투명도, 질감을 나타낼 수 있다.

 

 

Mesh(Object3D)

Mesh는 특정 Material의 속성을 가진 Geometry를 그리는 객체이다.

Mesh는 3D공간 상의 위치와 특정 기준 축 회전 등을 결정할 수 있으며

Material과 Geometry는 재사용이 가능하여 여러 Mesh가 특정

Material, Geometry를 동시 참조 가능한 특징이 있다.

 

 

마치며...

오늘은 Three.js의 기본 구성과 개념에 대한 포스팅만을 진행했지만 현재 Three.js를 사용한 프로젝트가 완성에 가까워졌고 완성된 이후에 Three.js에 대한 추가 포스팅을 진행할 예정이다. 언제나 개발을 통해 기능을 구현하는 것도 중요하지만 가장 중요한 것은 사용한 기술에 대한 개념, 원리를 명확히 이해하고 있는 것이라 생각하고 있고 나에게는 하나의 원칙이기도 하다. 앞으로도 근거 있는 기술의 사용과 검증 가능한 코드를 지속하기 위해 더 나은 노력할 것이다.

2023. 10. 29. 12:16

개발자가 되기위한 여정이 어느덧 1년 6개월이 되어간다.

개발을 처음으로 경험하고, 적성을 알게되어 시작하였고 직장을 다니며 약 8개월 공부를 하고

더 많은 시간을 투자하여 개발에 집중하고 싶어 3년간 종사했던 청원경찰을 그만두고

남은 퇴직금으로 어떻게든 버티며 개발자가 되기 위해 계속해서 달려온 것 같다.

 

시작은 너무나도 어렵고 작은 문제를 해결하기 위해 많은 시간을 쏟게 되었지만

시간이 흐를수록 사고하는 방식이 달라지고 스스로가 얼마나 부족한 사람이었는지

깨닫는 시간의 연속이었다.

 

개발은 나를 돌아보게 하였고 생각하는 폭을 넓혀주었으며 다른 방향의 시야를 밝혀주었다.

말 그대로 인생이 바뀌었다고 생각이 든다.

 

꾸준하게 개발을 학습하고 결과물을 만들어내는 것은 나에게 있어 기쁘고 가치있는 일이 되었다.

어느새 퇴직금은 바닥이 보이니 마음이 조급해졌고 개발에 몰두하게 된 것 같다.

 

깃허브의 잔디는 꾸준히 심을 수 있게 되었지만 블로그에 소홀해져버렸다.

그리고 잔디라는 것이 깃허브의 커밋내역을 하루 단위로 지정되어 녹색으로 표기되는 증명가능한

활동내역이라는 것을 4주 전에 알게되어 특정 분기마다 잊지않고 커밋하고 있다.

좀 더 일찍 알았다면... 분명 녹색으로 가득 차 있었을텐데 좀 아쉽다는 생각이 들었다.

 

아직 포스팅되지 않고 GoogleKeep에 메모되어있기만 한 기록들이 너무너무 많다.

분명 내가 사용하고 내가 기록하는 것은 좋지만 개발자로서 진로를 희망한다면 기업에게

내가 무엇을 했는지 보여 줄 수 있는 결과물을 내어야한다는 것은 너무 당연했다.

나의 기록을 보여주기 위해서 구글계정을 공유할 순 없으니...

 

다시 한번 증명할 수 있는 결과만들자는 결심이 생겼고 좀 더 마음의 여유를 갖고자

Daily카테고리를 만들게 되었다.

 

개발을 진행하며 구직활동을 시작한 것은 2개월이 되었지만 약 70개의 기업에게서

긍정적인 연락은 받지 못했다.

 

그래도 언제나 그래왔듯이 나는 하려는 일을 할 것이고 더욱 본질에 다가가기 위해 노력하며

중꺾마(중요한 건 꺾이지 않는 마음!!)의 의지를 스스로 꺾지 않을 것이다!

 

개발 근황은 객체 지향 패턴을 활용하여 three.js를 사용해서 포트폴리오를 새로 만들고있다.

학습에 어려움이 있었지만 관심이 많았던 기술이라 즐겁게 배우며 만들고 있다.

 

그리고 이 글을 보는 취준생분들께 같이 힘내자고, 화이팅하자고 말씀 전해드리고 싶습니다.

연속되는 실패에도 좌절하지 않고 나의 길을 꾸준히 다지며 성장하는 것은 절대로

허무한 일이 아니라고 믿습니다. 아직 빛을 발하지 못했을 뿐이고 빛을 내기위한 여정이라고

생각하기 때문입니다. 그리고 그 끝에는 분명 우리가 바라던 모습에 가장 가까운 모습을 한

나 자신이 기다리고 있다고 생각합니다.

 

모두 화이팅입니다!

 

 

Github: https://github.com/zeriong

2023. 7. 10. 11:40

Vercel

버셀은 Next.js 개발팀이 만든 호스팅사이트이다.

데이터를 저장할 필요가 없으며 별도의 절차 없이 호스팅을 해준다.

 

배포하기

vercel에서 배포하는 것은 어렵지 않고 아주 쉬운편에 속한다.

가장 먼저 깃허브 레포지토리에 프로젝트를 만들었다고 가정하고

편의를 위해서 vercel에 github계정으로 회원가입을 한다.

 

  1. 로그인진행 후 Add New...을 눌러 프로젝트를 생성한다.
  2. Import Git Repository에서 git계정을 연결하여 저장소의 프로젝트들을 가져온다.
  3. Import를 눌러 deploy(publishing)를 진행한다.

 

위와 같이 프로젝트 이름을 설정하고 프레임워크를 동일하게 설정해준다.

 

유의사항

프로젝트를 진행할때 특정 API-key를 사용해서 통신을 해야하는 경우가 아주 많다.

그러한 API-key는 분명 유출시 위험성이 있기 때문에 .env파일에 환경변수로 사용했을 것이고

Github에도 업로드 되지 않게 했을 것이다. vercel에서 파일을 배포할때 Github의 레포지토리를

기준으로 배포하기 때문에 vercel은 환경변수를 알 수 있는 방법이 없다. 그렇기 때문에 반드시

Environment Variables 텝을 눌러 개발환경과 동일한 변수명과 동일한 API-key를 넣어주어야 한다.

만약 API-key와 같은 환경변수의 값이 입력되지 않는다면 배포가 성공해도 status 500 에러가 발생한다.

 

환경변수를 모두 Add를 눌러 추가하고 마지막으로 Deploy를 눌러주면 배포가 완료된다.

 

 

마치며...

유의사항이라고 큼직하게 환경변수 적용에 대해 적어 두었지만 사실 너무나 당연한 것이다. 나는 환경변수에 대해서 떠올리지 못했고 500에러를 마주하며 좌절했었다. 너무 당연하게 개발환경에서 많은 시간을 투자해서 개발을 하다보니 익숙하지 않은 환경에서 무언가를 시도한다는 것은 그런 당연한 것을 쉽게 망각하기도 하는 것 같다. 역시 경험은 성장에서 빼놓을 수 없는 중요한 요소인 것을 다시금 깨닫는 시간이었다.

2023. 7. 9. 18:18

getServerSideProps

주요 기능은 특정 페이지의 index에서 해당 컴포넌트가 렌더링 되기 전에

Pre-Rendering(SSR)에 필요한 데이터를 해당 컴포넌트의 props를 통해서

데이터를 전달해 줄 수 있는 기능이다.

 

언제 사용하면 좋을까?

많은 경우가 있겠지만 가장 적합하다고 생각하는 상황은 응답받은 데이터를

html에 모두 포함된 상태로 seo최적화되어 렌더링되어야하고 동적으로 데이터가

자주 변경될 여지가 있고 반드시 최신화된 데이터를 보여주어야 하는 경우이다.

 

예시 소스코드

// pages/video/index.tsx

export interface VideoList {
    id: string;
    title: string;
    publishedAt: string;
    thumbnail: string;
    channelId: string;
    channelTitle: string;
    channelThumbnail: string;
    free: boolean;
    category: string;
}

export const getServerSideProps: GetServerSideProps<{ data: VideoList[] }> = async () => {
    const data: VideoList[] = [];

    const res = await getFirebaseData(); // 파이어베이스 데이터로드
    for (const row of res.data) {
        const youtube: any = await getYoutubeVideoData(row.id); // argument = videoId in FB
        const channelData: any = await getYoutubeChannelData(youtube.snippet.channelId);
        const newData = {
            id: youtube.id,
            title: youtube.snippet.title,
            publishedAt: youtube.snippet.publishedAt,
            thumbnail: youtube.snippet.thumbnails?.medium.url, // data = youtube "video" api
            channelId: youtube.snippet.channelId,
            channelTitle: youtube.snippet.channelTitle,
            channelThumbnail: channelData?.snippet.thumbnails.default?.url, // channelData = youtube "channel" api
            free: row.free, // from firebase
            category: row.category, // from firebase
        }
        data.push(newData);
    }

    return { props: { data } }
}

export default function Home({ data } : InferGetServerSidePropsType<typeof getServerSideProps>) {
    const element = useRef<CustomScroller>(null);

    return (
        <div className='relative w-full h-full overflow-hidden box-border bg-primary-dark-400 '>
            <header className='w-[375px] max-mobile-md:w-full fixed max-mobile-md:top-0 bg-primary-dark-400 z-10'>
                <NavBar />
                <CategoriesList />
            </header>
            <main className='absolute bottom-0 w-full h-[calc(100%-135px)] overflow-hidden'>
                <div className="relative w-full h-full">
                    <CustomScroller ref={element} universal={true}>
                        <MediaList data={data}/>
                        <button
                            type='button'
                            className="fixed bottom-16px right-16px"
                            onClick={() => {
                                if (!element.current) return
                                element.current.scrollTop();
                            }}
                        >
                            <ScrollTopIcon/>
                        </button>
                    </CustomScroller>
                </div>
            </main>
        </div>
    )
}

직접 진행했던 프로젝트에서 사용했던 코드이다. 통신해야 할 부분을 getServerSideProps에서 통신을 모두 마친 후에 메인 컴포넌트인 Home컴포넌트의 props로 데이터를 보내주었고 MediaList컴포넌트의 props로 통신완료된 데이터를 전달해주고있다.

 

Github: https://github.com/zeriong/side-project

 

유의할 점

getServerSideProps를 사용할땐 반드시 해당페이지의 메인 컴포넌트 파일(index.tsx)에서만 사용가능하다.

 

 

마치며...

처음에 ssr기능이 그저 next.js프레임워크를 사용하기만하면 되는 줄 알았는데 전혀 아니였다. 항상 새로운 프레임워크를 사용할땐 공식페이지에서 Document를 꼭 정독하고 프레임워크가 지향하는 개발의 자연스러운 방향과 기능들에 대해 인지하고 적재적소에 사용할 수 있도록 준비해두는 것이 개발자로서 더 올바른 자세라는 것을 깨달았다.

 

2023. 6. 29. 17:50

누적합 알고리즘

누적합 알고리즘은 어원 그대로 특정 값을 누적해서 더해줄때 사용되는 알고리즘이다.

코딩테스트에서 구간의 합을 구하는 문제에서 주로 사용된다.

 

누적합 알고리즘 동작방식

  • 접두사 합(Prefix Sum): 배열 가장 앞부터 특정하는 위치까지 각 누적합을 미리 구해놓은 것이다.
  • 접두사 합을 활용하여 알고리즘을 을 아래와 같이 만들 수 있다.
    위치 각각에 대한 접두사 합을 계산하여 P에 저장한다.
    매번 특정 수 만큼의 쿼리정보를 확인할때 구간 합은 P[right] - P[left - 1] 이다.

 

  • 주어진 배열
Index 1 2 3 4 5 6 7 8
Value 3 2 4 1 2 2 1 5

 

  • 접두사 합 배열
Index 0 1 2 3 4 5 6 7 8
Value 0 3 5 9 10 12 14 15 20

 

이에 [주어진 배열]에서 각 인덱스별로 누적합을 구한 것이 [접두사 합 배열 (p)]이다.

누적합 알고리즘에서 주어지는 공식 P[right] - P[left - 1] 를 생각했을때 아래의 예시를 확인해보자.

 

[주어진 배열]에서 1부터 3번째 인덱스가 가진 value의 누적합을 구해야 하는 문제이다.

[1, 3] => p[3] - p[0] = 9

< [주어진 배열]을 확인하면 3 + 2 + 4 이기 때문에 결과는 9가 되는 것을 확인 할 수 있다. >

 

[주어진 배열]에서 1부터 3번째 인덱스가 가진 value의 누적합을 구해야 하는 문제이다.

[3, 5] => p[5] - p[2] = 7

< [주어진 배열]을 확인하면 4 + 1 + 2 이기 때문에 결과는 7이 되는 것을 확인 할 수 있다. >

 

누적합 알고리즘을 활용한 예시문제

  • 데이터의 개수 N이 8로 주어진다.
  • 찾고자 하는 데이터 4번째 부터 8번째의 값의 누적합을 구해야 한다. (left = 4, right = 8)
let n = 8;
let arr = [3, 2, 4, 1, 2, 2, 1, 5];

// 접두사 합 배열
let sumValue = 0;
let prefixSum = [0];

for (let i of arr) {
  sumValue += i;
  prefixSum.push(sumValue);
}

// 네 번째 수부터 여덟 번째 수까지
let left = 4;
let right = 8;

console.log(prefixSum[right] - prefixSum[left - 1]);

 

마치며...

누적합 알고리즘은 내가 생각했던 것 보다 훨씬 효율적으로 특정 범위의 누적합을 구하는 방식이었던 것 같다. 알고리즘을 공부하면서 컴퓨터 공학과 수학은 뗄레야 뗄 수가 없는 관계인 것을 다시금 느꼈다.

2023. 6. 29. 15:18

투 포인터 (Two Pointer)

리스트에서 순차적으로 기록하며 처리할 때 두가지 위치(점: point)를지정하여 처리하는 알고리즘이다.

예를 들어 [1, 2, 3, 4, 5, 6, 7, 8, 9] 리스트에서 4, 5, 6, 7, 8 을 말할때 "4에서 8까지의 수" 라고 말한다.

위 예시에서 처럼 4~8까지의 수를 "시작점 4"와 "끝점 8"로 2개의 점으로 데이터의 범위를 특정하여

문제를 처리하는 알고리즘을 뜻한다.

 

투 포인터를 활용하기

부분 연속 수열찾기 문제에서 투 포인터 알고리즘을 활용하여 문제를 해결할 수 있다.

  1. 시작점과 끝점이 첫 인덱스 0을 가리키도록 만든다.
  2. [현재 부분의 합 S]가 [찾아야할 값 M]과 같다면 카운트에 추가해준다.
  3. S가 M보다 작거나 같으면 끝점을 1 증가시켜준다.
  4. S가 M보다 크면 시작점을 1증가시킨다.
  5. 모든 경우를 2번~ 5번까지의 과정을 반복하며 값을 얻는다.

 

투 포인터를 활용한 예시문제

  • 데이터의 갯수 N이 8로 주어진다.
  • 찾고자하는 부분 합 M이 5로 주어진다.
  • 주어지는 수열은 [3, 2, 1, 4, 2, 1, 1, 5] 이다.
  • 부분 수열의 합이 M이되는 경우의 개수를 출력하시오.
let n = 8;
let m = 5;
let data = [3, 2, 1, 4, 2, 1, 1, 5];
let cnt = 0;
let intervalSum = 0;
let end = 0;
// start를 순차적으로 증가시키며 반복한다.
for (let start = 0; start < n; start++) {
  // end를 조건에 맞는경우 가능한 만큼 이동시키기
  while (intervalSum < m && end < n) {
    intervalSum += data[end];
    end += 1;
  }
  // 부분합이 m일 때 카운트++
  if (intervalSum == m) cnt += 1;
  intervalSum -= data[start];
}
console.log(cnt); // output: 3

 

마치며...

평소에 코딩테스트를 한번씩 풀어보는데 나도 모르는 사이에 투포인터 알고리즘을 사용하는 경우가 종종 있었던 것 같다. 확실히 용어를 알고 원리를 제대로 파악하니 좀 더 전략적으로 코드를 작성할 수 있게 된 것 같아 뿌듯했다.

2023. 6. 28. 23:00

최단경로 알고리즘, 다익스트라(Dijkstra)

음의 가중치가 없는 그래프에서 한 정점에서 모든 정점까지의 최단거리를 각각 구하는

알고리즘을 에츠허르 "다익스트라(Edsger Wybe Dijkstra)"라는 컴퓨터 과학자가 만들었다.

그렇기 때문에 음의 간선이 존재하지 않는 최단경로 알고리즘을

"다익스트라" 알고리즘이라고 부른다.

 

최단경로 알고리즘 동작과정

최단거리 테이블은 각 노드에서 현재노드까지의 최단 거리 정보를 가지며

처리 과정에서 더 짧은 경로를 찾아다니며 더 짧은 경로로 값을 갱신한다.

  1. 출발 노드를 설정
  2. 최단거리 테이블을 초기화
  3. 방문하지 않은 노드 중 최단거리 비용이 가장 적은 노드를 선택한다.
  4. 해당 노드를 거쳐 다른 노드로 가는 비용을 최단거리 테이블에 갱신한다.
    < 우선순위 큐에 삽입하는 방식으로 사용가능 >
  5. 3번, 4번 과정을 끝날때까지 반복한다.

 

플로이드 알고리즘

전산학자 로버트 플로이드(Robert W Floyd)가 만든 알고리즘이다.

모든 노드에서 다른 모든 노드까지의 최단경로를 계산하는 알고리즘으로

다익스트라 알고리즘과 마찬가지로 단계별로 거쳐가는 노드를 기준으로 알고리즘을 수행한다.

플로이드 워셜은 2차원 테이블을 통해서 최단거리 정보를 저장하며

다이나믹프로그래밍에 속하기도 한다.

< 단계별로 점화되기 때문에 점화식이기도 하다. >

  • 플로이드 워셜의 점화식
    [ A에서 B로가는 거리보다 K를 거쳐가는 A - K - B 의 거리가 더짧은지 체크 ]
    D[A][B] = Math.min(D[A][B], D[A][K] + D[K][B])

 

벨만포드 알고리즘

미국의 두 수학자 "리처드 벨만(Richard Bellman)"과 "카탈리스트인 포드(Alfonso Shimbel)"에 의해

독립적으로 개발된 알고리즘이다. 리처드 벨만의 "벨만"과 카탈리스트인 포드의 "포드"를 따서

"Bellman-Ford Algorithm"으로 불리게 되었다. 벨만포드 알고리즘은 음수의 간선이 포함되어도

최단거리를 계산할 수 있는 최단경로 알고리즘이다. 하지만 경로를 탐색하다 음수순환이 존재하면

무한루프에 빠져 값을 반환할 수 없게된다

 

음수순환이란?

알고리즘을 통해 경로를 탐색하면서 계속해서 가장 짧은 거리를 찾아야하는 갱신해주어야 하지만

특정 경로에서 사이클이 생겼을때 모두 거쳐 갔을때 음수가 되는 경우 사이클을 계속해서 돌면서

값이 작아지기 때문에 음수의 순환이되고 음의 무한인 노드가 되어버린다. 그렇기 때문에

음수순환이 발생하는지에 대해 검사를 해주어야 한다.

 

벨만포드 알고리즘 동작 과정

  1. 출발 노드 설정
  2. 최단거리 테이블 초기화
  3. 아래 과정을 N - 1만큼 반복해준다.
    3-1. 모든 간선 E개를 하나씩 체크한다.
    3-2. 각 간선을 거쳐서 다른 노드로 가는 최단거리의 비용을 테이블에 갱신한다.

  4. < 음수 순환을 체크하기 위해서는 3번 과정을 한 번 더 수행한다. >
    최단거리 테이블이 갱신되다면 음수순환이 존재

 

마치며...

최단경로 알고리즘을 학습할때 벨만포드 알고리즘이 가장 어려웠다. 특히 음의 순환의 경우 이해하는데 꽤 애를 먹었는데 잘 생각해보면... 최소값(최단경로)을 찾는 문제에서 사용했기 때문에 탐색을 할때 계속해서 작은 수가 발견되면 자연스럽게 무한으로 감소한다는 것은 당연한 것이었는데 말이다. 항상 차근차근 발단부터 연관성을 가지고 이해해야 한다는 것을 다시금 느끼게되는 학습이었다.

2023. 6. 28. 21:39

다이나믹 프로그래밍 (Dynamic Programming)

다이나믹 프로그래밍은 문제에서 "최적 부분 구조" 또는 "반복되는 부분 문제"에 해당하는 경우

사용할 수 있는 알고리즘이다.

 

  1. 최적 부분 구조
    => 커다란 문제를 작은 문제로 나눌 수 있고 그 형태가 모두 유사하다면 이를 모아서 큰 문제를 해결하는 것
  2. 반복되는 부분 문제
    => 형식이 동일한 작은 문제를 반복적으로 해결해야 할때의 문제

 

점화식과 최적 부분 구조

점화식은 인접한 항으로서 현재 값을 결정하는 관계식을 말하며

점화식은 주로 최적 부분 구조를 만족한다는 특징이 있다.

  • 피보나치 수열 (점화식): a(n) = a(n -1) + a(n-2), [ (a(1) = 1, a(2) = 1 ]

 

점화식의 구성요소

"초기항"과 "인접한 항과의 관계"가 필요하다.

점화식은 재귀함수로 표현이 가능하며 재귀함수의 경우 종료조건이 반드시 있어야 하는데

종료조건이 점화식의 초기항 과 같은 역할을 수행한다.

function fibo(n) {
	if (n == 1 || n == 2) return 1; // 종료조건이 없으면 무한루프된다.
    return fibo(n - 1) + fibo(n - 2); // 점화식
}

 

다이나믹 프로그래밍의 형태 예시

function dp() { /*
    1. 종료하는 조건
    2. 이미 해결한 문제 = 정답을 return
    3. 점화식 계산
*/ }

다이나믹 프로그래밍 문제를 해결하는 과정은

  1. 점화식을 찾아내고
  2. 구현방식을 상향식, 하양식을 선택한 후에
    상향식: 반복문을 통해 초기항부터 계산 방법
    하양식: 재귀함수로 큰 항을 구하기 위해 작은 항을 호출하는 계산 방법
  3. 점화식을 코드로 구현해주면 된다.
    < 계산이 완료된 값을 담는 테이블을 주로 DP테이블이라고 부른다. >

 

마치며...

다이나믹프로그래밍에 대해서 혼란스러울땐 피보나치 수열을 떠올리는 것이 가장 좋은 것 같다.