Recoil이란?

React.js를 만든 Facebook에서 만든 전역상태관리 라이브러리. 이 이상의 설명은 필요없을 듯 하다.

 

전역 상태란?

컴포넌트에서 쓰이는 상태값(state)가 아닌, 이곳저것에서 전역적으로 쓰이는 상태(state)를 말한다.

 

전역 상태를 사용하는 이유

굳이 안 써도 되긴 한다. 하지만 그렇게 된다면 서로 다른 컴포넌트끼리 특정한 값(= 상태 = state)를 공유하려면 공통된 상위 컴포넌트로부터 prop을 계속 전달하는 방법을 써야 하는데,

 

  • 중간에 거치는 컴포넌트들마다 하나하나 prop을 전달하게끔 코드 작성하는거 귀찮음
  • 중간에 거치는 컴포넌트들은 전달받은 prop을 하위 컴포넌트로 전달만 하고 사용은 안 할 수도. 즉 비효율적
  • 중간에 거치는 컴포넌트들이 많아지면 나중엔 prop을 추적하기가 힘들어짐

 

이런 문제들이 있다. 이 때 전역적으로 쓰는 상태를 만든다면, 어느 컴포넌트에서나 전역 상태에 접근가능하므로 위에서 말한 불편들이 해소될 수 있다!

 

그럼 수많은 전역관리 툴 중 Recoil을 사용하는 이유?

일단 따지고보면 context API는 전역상태관리를 해주는 기능이라기보다는 단순히 전역적으로 상태를 공유하게끔 해주는 기능이라고 보는게 정확하다. context API를 사용해서도 전역상태값을 변경하는 건 물론 가능하지만, context API를 사용해 상태값을 변경하면 provider로 감싸져있는 컴포넌트들이 죄다 렌더링된다는 치명적 단점이 있다! (provider 하위에서 context를 구독하는 모든 컴포넌트는 provider의 value가 바뀔 때마다 다시 렌더링된다는 말)

 

// ContextTest.jsx

export const myContext = createContext("JOFE");

const ContextTest = (props) => {
    const [name, setName] = useState("JOFE");
    
    return (
        <myContext.Provider value={[name, setName]}>
            <Welcome />
            <Hello />
        </myContext.Provider>
    )
}

 

// Welcome.jsx

const Welcome = (props) => {
    const [name, setName] = useContext(myContext)
    return (
        <div>
            <button onClick={() => {setName("Nunu")}}>이름바꾸기</button>
            {name}
        </div>
    )
}

 

// Hello.jsx

const Hello = (props) => {
    useEffect(() => {
        console.log("Hello 렌더링")
    })

    return (
        <>
        </>
    )
}

 

myContext라는 context의 value로 name이란 상태와 name을 변경해주는 세터함수를 넣어줬다. 그리고 이 context를 Welcome컴포넌트에서 사용하는 모습이다. Hello컴포넌트는 useEffect를 사용해서 렌더링될때마다 console에 Hello 렌더링이란 문구를 찍어준다.

 

 

이름바꾸기를 하면?

 

 

myContext를 사용하지 않는 Hello컴포넌트도 단순히 provider 안에 있다는 죄(?)로 다시 렌더링이 되는 모습을 확인할 수 있다..!

 

바로 여기서 Recoil이 이런 문제들을 해결해줄 수 있기 때문에 사용한다. 하단에서 보여드림.

 

Recoil 기초적인 사용법

일단 상위 컴포넌트에서 RecoilRoot란 컴포넌트로 애들을 감싸주면 된다.

 

import { RecoilRoot } from 'recoil';

function App() {
  return (
    <RecoilRoot>
      <RecoilTest />
    </RecoilRoot>
  );
}

 

그리고 atoms.js파일을 만들어서 요로코롬 atom들을 만들어준다.

※ atom = Recoil에서 사용하는 상태들의 명칭이자 단위라고 생각하면 됨. 이 atom들의 key들은 각각 고유한 값이어야 함! 중복되면 안 된다는 뜻

 

// atoms.js

import { atom } from "recoil";

export const nameState = atom({
    key: "name",
    default: "JOFE"
});

 

참고로 굳이 atoms.js에 만들 필요는 없는데 이렇게 전역상태들을 한 파일에 관리해주는 게 더 편하다고 한다.

 

// RecoilTest.jsx

const RecoilTest = (props) => {
    return (
        <div>
            <Welcome />
            <Hello />
        </div>
    )
}

 

 

// Welcome.jsx

const Welcome = (props) => {
    const [name, setName] = useRecoilState(nameState)
    return (
        <div>
            <button onClick={() => {setName("Nunu")}}>이름바꾸기</button>
            {name}
        </div>
    )
}

 

요로코롬 useRecoilState를 사용해서 React의 hook마냥 쓸 수 있다! React스럽다는게 최고 장점인 듯. 참고로 세터함수는 필요없고 값만 읽고 싶다면 useRecoilValue를 쓰면 된다.

 

암튼, 이렇게 하면

 

 

 

요로코롬 Hello컴포넌트는 리렌더링되지 않는다! 오직 해당 atom을 사용하는 애들만 바뀐다.

 

 

※ 참고한 글https://hong-jh.tistory.com/entry/Context-API%EB%8A%94-%EC%99%9C-%EC%93%B0%EA%B3%A0-%EA%B7%B8%EB%A0%87%EB%8B%A4%EB%A9%B4-Redux%EB%8A%94-%ED%95%84%EC%9A%94%EC%97%86%EC%9D%84%EA%B9%8C

 

Context API는 왜 쓰고, 그렇다면 Redux는 필요없을까?

안녕하세요 오늘은 제가 평소에 궁금했던 주제로 글을 써보려고 합니다. Context API의 기능과 왜 사용하는지, Context API의 리렌더링 이슈란 무엇인지 그리고 Redux를 대체할 수 있는지. 위 내용을 설

hong-jh.tistory.com

 

sw마에스트로 14기를 준비하면서, 합격 후기들을 이곳저곳 긁어모으면서 많은 도움을 얻었다. 나도 면접과정을 다시 돌아보면서 부족했던 부분을 점검하고, 15기 + 를 준비하시는 분들에게 도움이 됐으면 좋겠다는 생각에 후기글을..써보기로 했다


2차 코테 복기 및 주저리주저리 ↓

https://jofestudio.tistory.com/98

 

[SW 마에스트로 14기] 코딩테스트 2차.. 주저리 주저리

3월 8일 오늘 14시, 저번 주 토요일에 본 sw마에스트로 2차 코테의 결과가 나왔다. 일단 결과는 합격이어서 다음 주 심층면접을 준비해야 하는 상황. 마음도 진정시키고, 어차피 심층면접 때 2차 코

jofestudio.tistory.com


포트폴리오 준비과정

자신이 했던 프로젝트를 2개까지 쓸 수 있고, 만약 프로젝트 경험이 없다면 sw역량을 기르기 위해 했던 노력들이나 소마에서 만들고 싶은 프로젝트, 그리고 소마에서의 각오 등을 포트폴리오에 자유롭게 기술하라고 제시해주셨다.

 

사실 나는 포트폴리오가 걱정됐던 게

 

  • 백엔드를 현재 지향하지만 백 쪽은 공부한지 별로 안 됐고 백 관련 프로젝트 경험 x (물론 멋사 9기 해커톤할 때 django써보긴 했지만 안 쓴지 오래 지났고 지금은 spring쪽 공부하니까 논외..)
  • 프론트 쪽으로 했던 팀 플젝이 1개 있지만 대중적인 커뮤니티 만드는 플젝이어서 그닥 매력이 없음
  • 멋사 9기 해커톤 때 했던 팀 플젝은 너무 허접해서(ㅋㅋㅋㅋ..) 얘도 매력없음

 

이런 상황이었다.. 백 쪽으로 준비하고 있던 상황인데 백 관련 경험이 없다는 것도 내 약점이라 생각했고, 그렇다고 프론트 쪽에서의 경험을 어필하자니 그 경험도 의미있던 경험은 아니라고 생각될 여지가 있다는 것이 내 입장이었다.

 

전체적인 포폴의 컨셉 즉 방향을 어떻게 해주지부터 생각했다. 일단 난 2년 반 넘는 시간동안 네이버를 거쳐 티스토리까지 개발 관련 블로그를 나름 꾸준하게 운영해오고 있다. 또한 코딩에 관련된 컨텐츠를 올리는 유튜브(물론 영상들을 많이 올리진 않고 중간중간 코딩이 아닌 영상도 있지만)도 운영하고 있다. 내 강점, 나만의 차별점이 될 수 있는 부분들을 어필해야 할 것 같다고 생각해 일단 블로그&유튜브 운영에 관한 얘기는 해주자고 생각했다.

 

뭐가 됐든 플젝경험이 빈약한 편인 만큼, 활동내역 쪽에서 내가 어떤 경험들을 했는지를 어필해주는 게 좋다고 생각했다. 2년 전에 했던 부스트코스 서포터즈 3기, 대학생 코딩 캠프 1기를 비롯해 멋사 9기에서 활동했던 걸 적기로 했다. 사실 부스트코스 서포터즈 3기와 대학생 코딩 캠프 1기는 뭐 엄청난 의미와 배움이 있다거나 밖에서 알아주는(?) 그런 활동은 아니었지만, 일단 싹싹 긁어모아서 나 이런 저런 것들 해봤다!라고 어필해주는 게 내 상황에선 더 좋다고 생각돼서 넣어줬다. 그리고 작년 여름방학 때 학교에서 Level DB라는 오픈소스를 분석하는 스터디를 했던 것도 넣어줬다. 스터디할 때 조교님이 "시스템 소프트웨어 분야로 진출하지 않으시는 분들도 이 스터디를 포폴 경험에 넣으실 수 있을 겁니다~"라고 해줬던 적이 있는데, 당시에는 별 생각 없었지만 이걸 진짜로 포폴에 넣게 될 줄은 몰랐다.. 그래도 나한테는 의미가 있던 활동이었기 때문에 넣어줬다. 그 후론 마지막으로 작년 하반기에 우테코 5기 프리코스에 참가했던 걸 적어줬다. 최종 탈락하긴 했지만 그래도 열심히 참가한 과정이었고 그 안에서 얻은 게 많았으니까.

 

이렇게 일단 긁어모아서 활동내역을 적고 나니, "나 그래도 나름 이것저것 해봤네?"라는 생각이 들었다. 나름 그래도 이것저것 해보고 싶은 것들 해보고 성장하려고 했었구나..라는 생각. 그러다 이 부분을 어필하면 되겠다는 생각이 들었다. 성장하기 위해서 이런저런 경험들을 쌓아보려고 했다!라는 부분! 웹 프론트, 시스템 소프트웨어, 백엔드라는 분야들을 접해봤다는 것을 이 활동내역을 통해서 보여줄 수 있기 때문이었다. 그래 그러면 "나는 성장하려고 노력하는 사람이고, 다양한 경험들을 쌓으며 성장하려고 노력해왔다. 그리고 소마를 통해서도 성장하고 싶다!"라는 컨셉을 잡기로 했고, 이 컨셉을 포폴에 녹여내려 애썼다.

 

이제 플젝 2개를 어떤 것들을 보여주냐가 남았다. 프론트쪽 담당해서 커뮤니티 만들어본거는 그닥 큰 매력이 없어도 일단 넣어주는 게 좋을 것 같다고 생각했다. 이제 남은 하나를 뭘 채워야하나..를 고민했는데, 멋사 9기 해커톤에서 했던 허접한 플젝을 넣을까 하다가 작년에 학교에서 ai수업을 진행하며 했던 팀 프로젝트를 넣기로 했다. 사실 ai프로젝트는 앵간하면 안 넣으려고 했던 이유가 해당 플젝에서 진행했던 부분들에 대한 것들은 당시에 빡세게 했던 만큼 빠삭하지만, 그 외에는 나도 ai쪽은 정말 모르기 때문에 관련된 질문이 들어오면 탈탈 털릴 수도 있다는 걱정 때문이었다. 그러나 내가 포폴 컨셉 자체를 다양한 경험들을 접해보려고 했다는 점을 어필하는 쪽으로 정한 만큼, ai플젝을 했던 걸 보여주며 ai분야도 접해보려 했다는 것을 보여주는게 좀 더 포폴의 결에 맞는다는 생각이 들었다. 뭐 실제로 내가 ai쪽을 접하려고 그 수업을 신청했었으니까. 대신, 이 플젝에 관련된 질문들에 대한 대비는 그만큼 빡세게 해야 한다는 각오를 했다. 그래도 ai쪽 플젝을 보여주는게 뭔가 겉보기에도 까리해보인다는 생각도 했던 듯 하다.

 

 

포폴 발표 연습

포트폴리오를 만들면서 중간중간에 대본 작성도 계속 해나갔다. 3분이 생각보다 길 줄 알았는데 엄청 타이트했다. 사실 내가 어필하기로 했던 부분들은 포폴에만 녹여내는게 아니라 발표때도 말하는 게 좋다고 생각했는데, 그러기엔 3분을 훌쩍 넘어버린다.. 그래서 최대한 각종 활동들과 포폴과 관련된 내용들은 시작한 동기와 느낀 점 위주로만 설명하기로 했다.

 

발표 연습은 틈틈이 했다. 틈날 때마다 방에서 연습했고, 학교 가는 길에도 했고, 샤워하면서도 했고.. 면접 보기 3일전부터는 학교에서 저녁에 빈 강의실에서 빔 프로젝터로 화면을 띄우고 발표하는 연습을 했다.

 

 

면접 질문 대비

나는 포폴 자체를 나름대로 질문들을 유도하는 식으로 만들었다. (ex: 소프트 스킬을 기르기 위해 노력해오고 있습니다 → 면접관이 "소프트 스킬을 기르기 위해 어떤 노력들을 했죠?"라고 물을 수 있도록)..그래서 내 포폴을 보면서 면접관 입장에서 무러볼 만한 것들을 최대한 다 뽑아내서 준비했다. 포폴에서 나올 수 있는 기술적인 질문들은 꼬리질문들까지 최대한 준비해갔다 (ex: React를 공부해보셨는데 React의 특징이 뭐죠 ? 방금 Virtual DOM을 말씀해주셨는데 Virtual DOM이 뭐죠?, Class Activation Map이 어떤 기술이죠 ? → Class Activation Map의 원리가 뭐죠? → CNN이 어떤 구조를 가지고 있죠 ? → Pooling이 어떤 걸 말하는 거죠 ? → Pooling의 종류가 뭐가 있죠 등등..)

 

 

이 외에도 이전 기수에서 나왔던 인성질문(특히 포폴 발표가 생긴 13기 때 나온 기출 질문들을 중점적으로)들을 긁어모아서 따로 준비했고, cs질문은 기초적인 내용들(프로세스와 스레드 차이, osi 7계층, http vs https 등..) 20개 정도만 긁어서 준비했다.

 

아 그리고 2차 합격자들만 남아있는 오픈톡방에서 따로 면접 스터디를 구했다. 강남에서 모이는 스터디, 그리고 내 동네 쪽에서 모이는 스터디에 참가하면서 서로 3분동안 포폴을 발표하고 각자 포폴에서 질문할 수 있겠는 내용들을 질문하는 스터디였다. 다른 사람들 앞에서 발표하는 것도 연습할 수 있고, 다른 사람들은 내 포폴을 보고 이런 것들을 궁금해하면서 물을 수 있음을 알게 되니까 예상질문들에 대한 대비도 어느 정도 할 수 있었다. 만약 15기 +를 준비하시는 분들이 있다면 이런 면접 스터디에 참가해보는 것도 추천합니다:)

 

 

나름대로의 면접 전 마인드세팅

면접 질문들에 대한 답변을 말하는 연습을 계속하면서, 내가 말을 좀 빨리 하게 된다는 걸 알게 돼서 최대한 천천히 말하려고 연습했었다. 아무래도 긴장하게 되면 말을 좀 빨리하면서 더듬는 버릇이 있어서 그런 듯 하다. 근데 이게 단기간 내에 몰라볼 정도로 고쳐지기는 어렵다고 생각했다. 그래서 최대한 천천히 말하려 하되, 더듬어도 괜찮아! 절어도 괜찮아! 라는 걸 스스로한테 주입시켰다. 모든 질문에 대한 답을 다 잘 말할 필요 없다, 몇 개는 잘 모른다고 말해도 괜찮고 대답을 이상하게 해도 괜찮다.. 내가 준비한 내용들은 그래도 다 대답할 수 있게끔 하자면서 마인드 세팅을 해나갔다. 준비하지 못한 질문이 분명 들어올 수 있지만, 내 임기응변 능력이 그렇게 후달리지는 않을 거다라는 생각도 한 것 같다.

 

 

면접 복기..

도착하고나서 내가 배정받은 번호가 1번인걸 알게 됐다. 당연히 1번이 처음 발표하는 사람이겠군이란 생각에 긴장됐지만, 미리 1번인 걸 알아서 다행이라고 생각했다. 처음으로 발표 시작하게 될거라는 생각으로 심호흡을 좀 했다. 최대한 대기실에서 "내가 제일 잘 한다. 다 찍어누르겠다(?)"라는 걸 스스로에게 세뇌할려고 했다. 어떻게든 자신감을 채우고 싶었던 듯. 마음 한구석에서는 살짝 쫄리긴 했지만, 그래도 겉으로 보이는 건 안 쫀 척 당당해보으려고 어깨 계속 펴주면서 으르렁댔다. ㅋㅋㅋㅋㅋ..

 

역시나 처음으로 포폴 발표를 하게 됐다. 연습을 많이 해서 그런지 막상 발표할 때는 엄청 긴장되고 그러진 않았다. 그냥 적당한 긴장감 속에서 진행됐다. 연습 때 보여줬던 베스트 퍼포먼스만큼 발표한 건 아니지만 그래도 만족할 정도로 발표한 듯 해서 다행이었다. 이후엔 최대한 허리를 펴고 앉으면서 두 손을 무릎에 두고 있었다. 다른 분들 포폴 발표하는 거 볼 때는 고개를 완전 90도로 오른쪽으로 꺾어야 해서 힘들었음.

 

 

질문들은 다음과 같은 질문들을 받았다.

 

공통질문

1. 코테 보면서 어떤 점이 가장 어려웠는지, 그런 부분들을 해결하기 위해 뭘 해오고 있는지

🧐 아무래도 경험이 부족하다보니 문제를 보고 어떤 유형인지를 파악하는게 어려웠다고 했고, 다양한 문제를 접하면서 문제를 딱 봤을 때 어떤 유형인지 알 수 있게끔 하려고 한다고 답했다

 

2. 자소서에서 각자가 만들기로 했던 서비스 한 번 더 설명해주고 비즈니스 모델은 어떤 거 생각햇는지

🧐 그냥 구상했던 서비스 다시 말씀드리면서 ~~기능 만들려고 한다고 답했다.. 부끄럽지만 솔직히 비즈니스 모델이 뭔지 잘 몰라서 최대한 자연스럽게(?) 비즈니스 모델에 대한 설명은 하지 않았는데 티가 났을 것 같다ㅋㅋㅋㅋ..근데 끝나고 비즈니스 모델이 뭔지에 대해 검색해봤는데, 다른사람들도 다 비즈니스 모델에 대한 답을 하진 않은 것 같아 다행이었다

 

3. 소마에서는 팀플을 하기 위해서 팀을 구해야 하는데, 본인의 어떤 점을 채워줄 수 있는 팀원을 원하며 그 팀원을 어떻게 설득할 건지

🧐 백엔드를 하고 싶은데 백엔드 관련된 경험은 부족하다보니 백엔드를 잘하는 사람과 팀을 하고 싶다고 했고, “나는 백엔드 경험은 부족해도 프론트랑 ai쪽 해봤다. 팀플에 잘 녹아들 수 있다”라고 설득해보겠다고 대답했다.

 

4. 소마가 널 왜 뽑아햐 하는지 (소마에 넌 뭘 줄 수 있는지..)

🧐 처음엔 질문 뉘앙스가 너희가 뭘 기여해줄 수 있냐를 묻는 줄 알았는데 마지막에는 뽑아야 하는 이유를 말해달라길래....준비했던 내용(나는 소마를 통해 성장하고 싶은 욕구가 정말 강하기 때문에 날 뽑아야 한다)를 말했다. 왜냐면 왜 너를 뽑아야하냐는 질문은 내가 준비를 했던 내용이었기 떄문! 근데 답하고 보니 뭔가 분위기가 띠용?이 되는 것 같았다. 사실 냉정하게 생각해보면 어떤 부분을 기여할 수 있는지를 말해달란거였는데, 이 부분은 준비를 못한 부분이라 당황해서 그런지 “왜 뽑아야 하는지”라는 질문에만 꽂혀서 그에 대해 준비했던 것만 말한 거였다.. 그래서 면접관님이 어떤 부분을 기여해줄 수 있는지에 대해서 되물으셔서 열정으로 분위기를 밝게 해보겠다는? 그런 답을 했다. 스스로도 부족한 답임을 알고 있어서 살짝 침울했지만, 다른 분들도 어떤 부분을 기여하겠다기보다는 스스로 성장하고 싶다는 점을 어필하셔서 다행이었다.

 

개인질문

1. ai관련 진행했던 플젝 간단하게 다시 설명해달라.

🧐 CAM이란 기술을 활용해 모델이 얼굴을 보고 감정을 분류할 때 얼굴의 어느 부분을 보고 분류하는지를 시각적으로 파악해보는 플젝이었다고 답했다.

 

2. CAM과 전이학습을 어떻게 활용한 거냐. 왜 전이학습 사용했냐.

🧐 처음에는 CAM과 전이학습이 서로 연관이 있는 거냐는 뉘앙스로 물어보셔서 서로 연관이 없다고 대답했는데, 거기에 대한 자세한 설명을 해달라면서 전이학습을 왜 했는지 물어보신 걸로 기억한다. 확보한 이미지 양이 적어서 새로운 모델을 만들어 학습시키기엔 효율이 안 좋을 것 같아 전이학습을 사용했다고 했고, 그 과정에서 전이학습을 위해 들고 온 모델에 CAM을 적용했다고 말했다.

 

 

3. 그럼 전이학습할 때 모델은 어떤 거 사용했고 어떤 식으로 학습시켰냐

🧐 VGG16 모델을 사용했고, 이 모델이 기존에 학습했던 이미지의 종류와 우리 플젝에서 새로 학습시키려는 이미지의 종류가 다르고 확보한 이미지가 적어서 모델에 새로 붙인 classifier layer뿐만 아니라 모델이 원래 가지고 있던 레이어들도 함께 학습시켜주는 식으로 했다고 말했다.

 

 

4. 결과를 잘 도출했고, 그래서 감정을 분류하는데 있어 모델이 중요시하는 부분이 어느 부분인지 볼 수 있게 된 것 같다. 도출된 결과는 어떤 특징이 있었냐

🧐 happy는 입 쪽을 보고 분류한다는 점을 볼 수 있었고 angry로 분류되는 애들은 코쪽, 주름쪽을 보고 분류한다는 점을 알 수 있었다고 답함.

 

그리고 공통적으로 마지막으로 하고 싶은 말들을 시켰다. 거기에 대해선

🧐 “소마 과정이 쉽게 잡을 수 있는게 아닌 소중한 기회라고 생각하고 있고, 소마를 통해 얻을 수 있는게 정말 많다고 생각한다. 처음 접하는 ai에 관련된 플젝을 할 때도, Level DB라는 규모있는 오픈소스를 분석할 때도, 우테코에서 처음 접하는 자바를 사용하며 미션을 수행할 때도 여러운 것들이 많앗지만 포기하기 않고 끝까지 해내왔다. 소마도 결코 쉽지 않겠지만 포기하지 않고 끝까지 해내서 꼭 스스로가 성장할 수 있는 기회로 반드시 만들어보겠다” 라고 답했다

 

 

음..포폴 발표할 때는 말을 그닥 빨리 하지 않았던 것 같은데 결국 질문같은 걸 받을 때 말을 빨리 하고 중간에는 말을 좀 더듬기도 한 것 같아 아쉬웠다. 이 부분은 진짜 고쳐나가야 할 듯 하다. 말을 왜 자꾸 빨리 하게 되는지..ㅜㅠ

 

일단 면접과정에 대한 후회는 없다. AI관련된 개인질문들은 바로바로 대답을 이어나갔기 때문에 꽤나 잘 대답했다고 생각하고, 꼬리질문을 이어가주시던 면접관 분도 시간이 있으면 더 물어보고 싶지만 그래도 플젝을 잘 진행해본 것 같다라는 뉘앙스로 말해주셔서 만족했다.

 

그 외 공통질문들에 대한 대답도 그 때의 내가 답할 수 있는 최선을 다한 대답이었다고 생각해서 후회가 들진 않는다(아 이렇게 답할걸.. 이런 생각 안 듦). 내가 준비했던 내용들에 대해선 다 답한 것 같아 과정에 대한 후회는 정말 남지 않을 것 같다.

 

다만 아쉬운 건, 다른 사람들한테는 질문들이 좀 많았는데(특히 블록체인 기술로 플젝한 분) 나한테는 그만큼의 질문이 들어오지 않았다는 거다. 나 진짜 내 포폴에 관련된 내용들, 포폴에 작성된 기술 관련된 내용들은 다 방어할 수 있을 정도로 꼼꼼히 준비했는데.. 나한테도 그 분만큼의 꼬리질문을 해주면 다 답해줄 수 있는데..  나 준비 많이 했다는거 보여줄 수 있는데.. 그만큼의 질문이 들어오지 않은 것에 대한 아쉬움. 준비한 것들을 다 못 보여준 느낌..이었다.

 

그래도 후회는 없다. 들어온 질문들에 대해서는 내가 할 수 있는 최대치 내에서 답변했다고 생각했다.

 

 

다른 분에게 들어온 개인질문 중 기억나는 것들

- 블록체인의 Finality가 뭐고 왜 중요한가요?

- 본인이 작성한 플젝에서 하신 역할이 뭔가요?

- OAuth를 사용해봤다고 했는데 OAuth가 뭔가요?

- 블록체인과 NFT의 차이점에 대해 말해주세요

 

우리 분과는 단순 프론트 / 백 프로젝트를 하신 분들에겐 그 플젝에서 다뤄봤다고 기재한 기술들에 대한 질문(ex: OAuth를 써봤다고 하는데 그게 뭔가요 등)을 하셨던 반면, AI나 블록체인같은 기술이 가미된 프로젝트를 한 분들에게는 그 기술들에 대한 질문들을 꼬리 질문 형식으로 계속 이어나가셨다. 그래서 단순 프론트/백 플젝을 하신 분들에겐 솔직히 질문이 별로 안 갔다. 물론 나에게도 질문이 많이 온 건 아닌 것 같다.. 아무래도 면접관님들 입장에서도 매력적으로 느껴지는 플젝은 AI나 블록체인을 활용한 플젝이기 때문일 것 같다. 그래서 어떻게보면 내가 AI플젝을 포폴에 넣었고, 그거에 대한 대비를 최대한 꼼꼼히 하려고 했어서 다행이라는 생각도 들었다.


그리고 합격..!

3월 23일 5시 경에 메일이 왔다.

 

 

일단 기쁘다. 마에스트로 과정을 옛날부터 한 4학년쯤 되면 해봐야지~ 했는데 정말 하게 돼서 너무 좋다. 글을 쓰고 있는 지금도 실감이 안 날 정도로 너무 좋다.

그리고 내가 공부해왔던 것들이 아무 의미 없던 게 아니었음을, 나 그래도 잘 하고 있었음을 인정받은 것 같아서 더욱 더 좋은 것 같다. 눈에 보이는 성취를 이루고 싶다는 생각을 했는데, 소마에 이렇게 합격하게 돼서 너무 다행이고 좋다. 

 

퀄리티있는 플젝을 하며 폭발적으로 성장할 기회. 다른 사람들과의 네트워크를 만들 기회, 생활비 벌자고 20살때부터 알바하던 것들 다 때려치고 지원금받으면서 오로지 성장에만 몰입할 수 있는 기회. 소마를 통해 내가 바라던 것들에 대한 기회를 잡게 됐다. 하지만 아직 잡기만 했을 뿐. 성장하지도, 다른 사람과의 네트워크를 만들지도, 몰입하지도 않았다. 단순히 기회만 얻게 됐을 뿐. 앞으로의 1년이 정말 중요할 거고, 소마에서 어떻게 지내가는지에 따라 내가 원하던 것들을 잡게 될지 안 될지가 나뉠 것이다. 이제부터가 중요하다.

 

잘..해보자!

 

https://www.acmicpc.net/problem/2504

 

2504번: 괄호의 값

4개의 기호 ‘(’, ‘)’, ‘[’, ‘]’를 이용해서 만들어지는 괄호열 중에서 올바른 괄호열이란 다음과 같이 정의된다. 한 쌍의 괄호로만 이루어진 ‘()’와 ‘[]’는 올바른 괄호열이다. 만일 X

www.acmicpc.net

 

구현방법에서 낑낑댔다. 괄호는 스택을 사용해서 매칭시킬 수 있다는 것은 스택을 공부해봤다면 알고 가게 되는 기본 지식이지만, 중간값들을 어떻게 처리할 것인지에 대해서 계속 고민이 됐다.

 

어떻게 처리할지 고민하다 분할정복 기법으로 풀어봤다. 다 풀고 다른 사람들 풀이를 봤는데, 음.. 내가 좀 어렵게 푼 편이구나 싶기도 하고. 그래도 문제 푸는 방법이 1가지만 있는 게 아니니까 뭐.

 

def solution(parenthesis, s, e):
	# base case : 바로 답을 구할 수 있는 경우
    if e == s + 1:
        if parenthesis[s] == "(":
            return 2
        else:
            return 3
	
    # recursive case : 문제가 너무 커서 바로 답 못 구하는 경우 -> 그룹별로 쪼갠다
    group, stack = [], []
    little_s, little_e = 0, 0

    for i in range(s + 1, e):
        if parenthesis[i] in ("(", "["):
            if not stack:
                little_s = i
            stack.append(parenthesis[i])
            continue
        if len(stack) == 1:
            little_e = i
            group.append([little_s, little_e])
        stack.pop()
        continue

    result = 0
    for ns, ne in group:
        result += solution(parenthesis, ns, ne)
    return 2 * result if parenthesis[s] == "(" else 3 * result


parenthesis = "(" + input().strip() + ")"
stack = []
for i in range(len(parenthesis)):
    if parenthesis[i] in ("(", "["):
        stack.append(parenthesis[i])
        continue
    if not stack:
        print(0)
        exit()
    if (parenthesis[i] == ")" and stack[-1] == "[") or (parenthesis[i] == "]" and stack[-1] == "("):
        print(0)
        exit()
    stack.pop()

print(solution(parenthesis, 0, len(parenthesis) - 1) // 2)

 

기본적인 컨셉은 괄호 문자열을 그룹별로 나눌 수 있다는 점을 이용한 거다. ([])[[]]라는 문자열은 ([])과 [[]]라는 두 개의 그룹으로 구성되는 것을 이용하는 것. 그룹별로 계속 쪼개주다가 ()나 []이라는 base case를 만나게 되는데, 그 때는 2나 3을 리턴하도록 만들었다. 각 그룹에 대해 solution메서드가 동작하며, 이 놈이 파라미터로 받은 그룹을 쪼개서 쪼개진 그룹의 결과들을 더해가도록 만들면 됐다.

 

분할정복은 다음 영상을 참고하면 된다(본인 유튜브 홍보..ㅋㅋㅋㅋ)

https://www.youtube.com/watch?v=qDEKiNzAH1U&t=144s 

 

3월 8일 오늘 14시, 저번 주 토요일에 본 sw마에스트로 2차 코테의 결과가 나왔다.

일단 결과는 합격이어서 다음 주 심층면접을 준비해야 하는 상황.

마음도 진정시키고, 어차피 심층면접 때 2차 코테 내용을 물을 수도 있다 하니 간단히 복기할 겸 짧은 글을 써보기로 한다.


1차 코딩테스트 

  유형 난이도(백준 기준) 예상
1번 단순 구현 실버 4
2번 라인스위핑 or 완전탐색 골드 5 ~ 골드 4
3번 조합 + BFS 실버 1 ~ 골드 5
4번 DFS(백트래킹) 골드 5 ~ 골드 4
5번(sql) 문자열 및 정규표현식  

※ 유형은 각자가 푼 방법이 다르기 때문에 꼭 저 유형이 나왔다고 할 수 없습니다. 난이도는 제가 체감한 난이도입니다. 시험 응시 당시 제 티어는 골드 1이었습니다.

 

우선 나는 sql을 공부한 적이 없기에 1차 코테 전에 1주일 정도 프로그래머스 고득점 kit을 풀며 sql을 준비했었다. 그래서 코테 응시할 때 염두한 전략이 sql을 먼저 푸는 거였는데, 고득점 kit에서는 보지 못한 유형이 나와 못 풀었다..

 

1, 3번 풀고 2번을 풀다가 끝이 났다. 즉 2솔로 제출..이 때 서버가 터져서 45분의 추가시간이 주어졌는데, 나는 스터디룸을 예약하여 응시하던 상태라 추가시간을 활용하지 못 했다. 그러나 마에스트로 측에서도 이런 불합리성 등을 감안했는지 1차 코테를 응시한 전 인원을 합격처리해줬다.

 

2차 코딩테스트

  유형 난이도(백준 기준)
1번 단순 구현 실버 3 ~ 실버 2
2번 그리디..? 골드 3 이상
3번 자료구조 + 빡구현 골드 4
4번 플로이드 워셜..? 골드 3 이상
5번(sql) LEFT JOIN  

※ 유형은 각자가 푼 방법이 다르기 때문에 꼭 저 유형이 나왔다고 할 수 없습니다. 난이도는 제가 체감한 난이도입니다. 시험 응시 당시 제 티어는 골드 1이었습니다.

 

1, 3, 5번 풀어서 냈는데, 5번 풀 때는 그냥 JOIN으로 했다가 다 끝나고 나서 LEFT JOIN으로 했어야 한다는 걸 알아버렸다. 1, 3번은 구현유형이었던 만큼 내 풀이에 대한 확신을 가진 상태. sql문제가 부분점수가 있는지는 모르겠지만 2솔 ~ 2.5솔 정도 된다고 생각하고 기다렸다. 아 그리고 2번 문제는 풀다가 끝났었다.

 

 

암튼, 결과는 합격. 기준은 모른다. 1.5솔 합격이신 분도 계시고, 2솔 탈락인 분도 계시고..

 

 

이제부턴 정말 주저리.

 

개인적으로 소마 자체에 합격하는 것을 바라지만, 코테 합격이라도 했으면 좋겠다는 생각을 했다.

 

 작년 12월에 우아한테크코스 프리코스에서 떨어진 적이 있다. 최종테스트에서 떨어진 것도 아니고, 최종테스트 인원 선발 과정에서 떨어졌었다. 적잖은 충격을 받았었다. 학교 과제까지 제껴두고 프리코스 과정에 올인했었고, 제시받은 모든 요구사항들을 잘 지켜나갔으며 매 주차마다 회고글을 작성하고 다른 사람들과 피어리뷰도 진행하는 등 내가 할 수 있는 모든 최선을 다해 프리코스에 임했던 만큼, 최소한 최종테스트를 응시할 기회는 받을 수 있으리라 예상했기 때문이다.

 어찌보면 내 자만이었을 것 같기도 하다. 왜냐하면 난 지금까지 내 스스로가 최선을 다했다고 느꼈던 부분들, 내가 봐도 나 정말 열심히 했다고 느꼈던 부분들에서는 비교적 좋은 결과를 얻어왔기 때문인 것 같다. 그러나 최종테스트 응시를 할 기회조차 주어지지 않았다. 답답한 것은, 어떤 부분이 부족해서 떨어졌는지를 모른다는 거였다.

 그래서였을까. 스스로에게 회의감이 들었다. 내가 해온 공부방식이 사실은 잘못된 방식이 아닐까라는 느낌. 매번 달리지는 않았어도 꾸준히 조금씩이라도 걸어가고 있다고 생각한 길이 사실 돌아보면 이미 한참 전에 잘못 들어선 길이 아닐까. 나는 제대로, 잘 해오고 있던 걸까. 내가 공부해왔던 것들이 사실 무의미했던 것들이 아닐까.

 

 사실 이번 마에스트로에서 응시한 코테가 내 인생 첫 코딩테스트다. 1월 말부터 시작해 약 1달 정도 강도있게 준비하기 시작했지만, 사실 이전에도 알고리즘 공부를 했던 적이 있다. 빡세게 하던 건 아니었지만 20년도 7월부터 21년도 2월 정도까지 기초적인 문제들을 풀었었고, 21년도 9월부터 22년도 4월정도까지는 멋사 동아리에서 만난 사람들과 스터디 형태로 일주일에 4문제씩은 풀었었다. 1월 말에 다시 알고리즘 공부를 시작한 시점에 이전에 공부했던 것들을 까먹기도 했고 감도 잃은 상태였지만, 어찌됐든 난 알고리즘 공부를 옛날부터 해왔던 사람인 거다.

 그래서 조금은 무서웠다. 만약 이번에 마에스트로 과정을 코테 단계에서 떨어진다면, 우아한테크코스 프리코스 과정에서 탈락했을 때와 마찬가지로 뭔가 스스로가 부정당할 것 같은 느낌이었다. "나 그래도 옛날부터 조금씩은 알고리즘 공부해온 사람인데, 사실 내가 옛날부터 해왔던 알고리즘 공부 역시 잘못된 게 아닐까" 라는 두려움.

 

 물론 내가 이번 코테를 잘 본 사람은 아니다. 난 딱 합격컷 위에 서있는 사람일거다. 그래도 다행인 건, 내가 틀리지 않았다는 느낌이 든다는 거다. 거의 다 까먹은 상태에서 1달동안 빡세게 준비하긴 했지만, 그 전에 알고리즘 공부를 했던 적이 없다면 아마 코테에서 떨어졌을 거다. 그 이전에 알고리즘 공부를 한 경험들이 있기 때문에 1달동안 준비가 나름대로 돼서 붙은 것 같다. 내가 공부해온 것들이 의미가 있었다 라는 걸 느껴서 스스로에게 정말 다행인 것 같다.

 

 암튼, 다음 주가 심층면접인 만큼 다시 한 번 최선을 다해 준비해야한다. 마지막 한 걸음 남았다고 생각한다. 열심히 준비해서 최선을 다해보자. 화이팅..!

 

 

 

 

https://www.acmicpc.net/problem/17142

 

17142번: 연구소 3

인체에 치명적인 바이러스를 연구하던 연구소에 승원이가 침입했고, 바이러스를 유출하려고 한다. 바이러스는 활성 상태와 비활성 상태가 있다. 가장 처음에 모든 바이러스는 비활성 상태이고

www.acmicpc.net


조합과 BFS가 짬뽕된 문제. 바이러스를 놓을 수 있는 위치들에 대한 m개의 조합들을 하나하나 살펴보며 BFS를 돌려 판별하면 된다. 

이 문제에선 고려해야 할 사항이 좀 더 있다. 바로 BFS의 종료조건.

 

https://www.acmicpc.net/problem/7576

 

7576번: 토마토

첫 줄에는 상자의 크기를 나타내는 두 정수 M,N이 주어진다. M은 상자의 가로 칸의 수, N은 상자의 세로 칸의 수를 나타낸다. 단, 2 ≤ M,N ≤ 1,000 이다. 둘째 줄부터는 하나의 상자에 저장된 토마토

www.acmicpc.net

 

위 문제 "토마토"는 본 문제인 "연구소 3"과 꽤나 유사하다. 익은 토마토가 매 페이즈마다 주변 토마토를 익게 하듯이, 본 문제에서의 바이러스도 매 페이즈마다 주변 칸들에 퍼져 나간다. 또한 모든 토마토가 익을 때까지 최소 며칠이 걸리는지 계산하는 것과 빈 칸이 모두 바이러스가 퍼질 때까지 최소 얼마나 걸리는지를 요구하는 것도 비슷하다.

 

그러나 디테일에 차이가 있다. 토마토 문제는 모든 토마토가 익을 때까지 BFS를 돌려야 한다면, 본 문제는 빈 칸에 바이러스가 채워질 때까지만 BFS를 돌려야 한다. 즉 벽을 제외한 모든 칸이 활성 바이러스로 채워질 때까지 돌리는 게 아니다. 비활성된 바이러스가 있어도 다른 빈 칸들이 남아있지 않다면 더 이상 BFS를 돌릴 필요가 없다.

 

따라서 안 익는 토마토가 있든 없든 일단 Queue가 빌 때까지 BFS를 돌려 가능한 모든 구역의 토마토가 익게 한 다음, Queue가 비고 나면 그제서야 모든 토마토가 익어도 되는지 판단해도 되는 토마토 문제와는 달리, 본 문제는 BFS를 돌릴 때마다 연구소 상황을 체크해야 한다. (무작정 큐가 빌 때까지 돌리면 빈 칸들이 다 찰 때까지가 아니라 벽을 제외한 모든 칸에 활성 바이러스가 퍼질 때까지를 보게 되는 문제로 바뀌니까)

 

즉 빈칸들을 채울 때마다 카운트해주고, 매 BFS마다 현재까지 채운 빈칸의 수와 처음에 셌던 빈칸의 수가 일치하는지를 판단해야 한다. 즉 큐에서 꺼낸 노드마다 상하좌우 네 방향에 대해 방문여부를 확인하고 방문여부를 안 했고 그 칸이 빈칸이면 채운 빈칸의 수를 하나 늘리면 된다.

 

내가 실수한 부분은, 빈칸을 채운 뒤 채운 빈칸의 수를 늘리는 작업과 빈 칸을 다 채웠는지 판단하는 작업을 동시에 안 했다는 거다. 나는 BFS를 할 때 보통 종료조건을 판단하는 작업을 큐에서 노드를 꺼내고 난 바로 다음에 한다. 얘를 들어 지금 꺼낸 노드가 도착지점이라면 지금까지 카운트한 수가 정답이다! 처럼 판단하는 것. 즉 이 문제는 이런 습관에 따라 처음에 다음과 같이 BFS코드를 짰다.

 

while q:
    1. 만약 지금까지 채운 빈칸의 수가 처음에 센 빈칸의 수와 같다면 종료하는 코드
    2. 인접한 칸들이 빈 칸이고 방문하지 않았다면 그 칸을 방문처리하고 큐에 추가한 다음
    지금까지 채운 빈칸 수를 += 1

 

빈칸을 채우고 채운 빈칸 수를 늘리는 작업(2)과 빈칸을 다 채웠는지 판단하는 작업(1)을 동시에 하고 있지 않는 모습이다. 이 때문에 오답이 날 수 있다. 큐에서 노드를 하나 꺼냈는데 x초에 방문한 노드였고, 이 노드의 인접한 빈칸을 채우고 채운 빈칸 수를 늘렸다. 큐에서 다음 노드를 빼냈고 똑같이 x초에 방문한 노드인데, (1)에 의해 지금껏 채운 빈칸 수가 처음에 센 빈칸의 수와 같아 x초에 모든 빈칸을 채웠다고 오답을 뱉게 된다. 사실 x + 1초에 모든 빈 칸이 채워지는 건데 말이다. 만약 임의의 시간 x초에 방문하는 모든 노드들이 방문하지 않은 인접한 빈 칸을 하나 이상씩 가지고 있는게 보장된다면 이런 일은 안 생기겠지만, x초에 방문하는 모든 노드가 방문하지 않은 인접한 빈칸을 가지고 있다는 보장이 없으니 이런 상황이 생긴다.

 

난 그래서 처음에 틀렸다..ㅋㅋㅋㅋㅋ

 

암튼..! 빈칸을 채우고 채운 빈칸 수를 늘리는 작업과 빈칸을 다 채웠는지 판단하는 작업을 동시에 해야 한다. 다음과 같이 바꿀 수 있을 것이다.

 

while q:
    1. 지금 꺼낸 노드가 빈 칸이면 지금껏 채운 빈 칸의 수 += 1
    2. 만약 지금까지 채운 빈칸의 수가 처음에 센 빈칸의 수와 같다면 종료
    3. 인접한 칸들이 빈 칸이고 방문하지 않았다면 그 칸을 방문처리하고 큐에 추가

 

또는 다음과 같이도 가능하다. 중요한 건 동시에 두 작업을 순서대로 해야 한다는 거.

 

while q:
    1. 꺼낸 노드에 인접한 칸들이 빈 칸이고 방문하지 않았다면 그 칸을 방문처리하고 큐에 추가한 다음
    지금까지 채운 빈칸 수를 += 1
    2. 지금까지 채운 빈칸 수가 처음에 센 빈칸 수와 같다면 종료. 단 이 작업은 1이 이루어지고 난 이후에만 진행

 

전체 코드는 다음과 같다.

 

from itertools import combinations
from collections import deque
import sys
input = sys.stdin.readline


def solution(n, m, board):
    directions = [(-1, 0), (0, 1), (1, 0), (0, -1)]

    empty_count = 0
    virus = []
    for r in range(n):
        for c in range(n):
            if board[r][c] == 2:
                virus.append((r, c))
            elif board[r][c] == 0:
                empty_count += 1

    answers = []

    for activate_viruses in combinations(virus, m):
        q = deque([])
        visited = set()
        for r, c in activate_viruses:
            q.append((r, c, 0))
            visited.add((r, c))

        empty_to_virus_count = 0

        while q:
            r, c, h = q.popleft()
            if board[r][c] == 0:
                empty_to_virus_count += 1
            if empty_to_virus_count == empty_count:
                answers.append(h)
                break
            for dr, dc in directions:
                nr, nc = r + dr, c + dc
                if nr < 0 or nc < 0 or nr >= n or nc >= n:
                    continue
                if board[nr][nc] != 1 and (nr, nc) not in visited:
                    visited.add((nr, nc))
                    q.append((nr, nc, h + 1))

    if answers:
        return min(answers)
    return -1


N, M = map(int, input().split())
_board = [list(map(int, input().split())) for _ in range(N)]

print(solution(N, M, _board))

 

이 문제를 통해 얻은 것.

"다음 노드들에 대한 작업과 종료여부를 판단하는 작업은 동시에 할 것"

 

 

+ Recent posts