Memoir (3)
삼성전자 SW 테스트 B형 가이드

삼성전자 SW 테스트 B형 (공채 추가시험) 가이드

Disclaimer

채용 진행과정에서 작성한 기밀유지서약서가 있기 때문에 채용 프로세스 및 정보(SW테스트 제약사항, 기출문제 등)는 이미 인터넷에 공개적으로 알려진 사항에 대해서만 정리하여 작성한 것이며, 기밀유지 하기로 한 부분에 대해서 문제시될 부분은 없습니다.
SW Test에 대한 안내사항은 swexpertacademy 공식 사이트에 안내된 부분을 다시 정리한 것일 뿐이며, 기출문제는 유출하지 않습니다.
타 사이트(백준저지 등)에 올라온 문제를 기반으로 저의 해설을 추가했을 뿐, 참가했던 SW테스트와는 무관합니다.

그래도 만약 문제가 되는 부분이 있을 경우 통보해주시면 해당 내용은 삭제조치하도록 하겠습니다.

또한, 본 가이드는 알고리즘 학습에 도움을 주기 위함일 뿐이며, 미래 정확성은 책임지지 않습니다.
본 가이드 내 정보를 참고한 모든 결과에 대한 책임은 독자에게 있습니다.

1. 개요

삼성전자에서는 2016년쯤부터 S직군(소프트웨어) 신입사원 및 인턴사원 공채 전형에서 SW 테스트를 실시하고 있습니다. 서류전형 -> SW테스트 (GSAT/SSAT 면제) -> 최종면접(인성, 직무, 창의)으로 진행됩니다.

이 때 보는 SW테스트가 바로 SW Test A형입니다.
이렇게 기존에는 사내 직원들에게만 실시하던 테스트를 삼성전자가 얼마 전 코드그라운드를 통해 일반 대학생/졸업생에게 오픈하였습니다.
링크 : https://www.swexpertacademy.com/main/main.do

참고로 B/C형은 취득시 인사평가/배치시 우대사항이 있는 것으로 공지되어 있는데요,
A형에 대해서는 이미 글(링크)을 썼었으니 이번에는 B형에 대해 써보려고 합니다. (C형은 한번도 시험을 안 봤기 때문에 쓸 일이 없을 것 같네요)

2. B형 대비방법

먼저, 삼성에서 공개적으로 밝힌 안내사항입니다.


위와 같이 B형은 4시간이 주어지며 한 문제를 풀어야 합니다. 언어는 C/C++/JAVA이며 stdio/iostream을 제외한 라이브러리 사용 불가합니다.
즉, C++ 기준으로 STL 모두 사용할 수 없고, C++ 11 및 14까지도 지원은 될겁니다만 의미가 없습니다(STL이 안되니). auto, 람다 등은 아마도 모두 사용 가능할텐데, 다만 문제 구성이 이와 같은 기능을 필요로하는 형태가 아닙니다.

B형은 A형에 비해 꽤나 까다로운 편인데요, A형과 비교하자면...

A B
라이브러리 STL 등 라이브러리 사용 자유 표준라이브러리 등 일체 사용금지(stdio 등도 사용하지 않음)
특징 input을 받아 expected output 출력 출력 필요없음 - API 사용하여 문제해결
제약사항 모든 코드 자유수정 가능 바꿀 수 없는 코드 존재
제한시간 2문제 3시간 1문제 4시간
유형 시뮬레이션 최적 솔루션 도출
중요 포인트 O(N^2) 따위만 되지 않게 구현 API 호환되게 로직 구현, 최적화 매우 중요

3. 문제풀이 예시

주의 : 벼락치기로 통과하기에는 다소 무리가 있는 시험입니다. 예를 들어, GUI 부분은 건너뛰더라도 테트리스 기본 로직 정도를 짤 자신이 없다면 기초를 먼저 닦는 시간을 가지는 걸 추천합니다.
운 좋게 실력보다 높은 수준을 요구하는 자리에 합격했다 하더라도, 실제 회사생활 중에 받게 될 스트레스는 훨씬 더 고통스럽습니다. 지금은 취업이 더 급한 것 같지만, 내실을 다지는 것이 더 중요합니다.

3-1. Prerequisites (기본 요구사항)

A형 해설에서 이미 많은 부분을 짚어두었습니다.
B형에 추가적으로 요구될 만한 것들은 다음과 같습니다.

  • 라이브러리를 사용해봤을 것

    • 여기서 라이브러리라 함은, STL이나 java.util 같은 게 아닙니다. 다른 사람들이 만든 라이브러리를 써봤어야 합니다. 예를 들어 게임엔진처럼, 내가 main함수를 다 짜는 게 아니라 이미 있는 메인에 리소스를 스폰시키고 카메라를 이동시키고... 이런 모든 것들이 하나의 추상화된 단계를 거친달까요?
  • 협업 프로젝트를 해보았을 것

    • 다른 사람의 코드를 보고 이해할 수 있어야 합니다. 내가 생각한 로직으로 내가 구현하는 거에만 익숙한 사람은 B형에서 상당한 어려움을 겪을겁니다.

... 이 외에는, 평범한 요구사항들입니다(알고리즘 기초지식 등).

3-2. 문제 형식 요약 및 간단 예시

B형은 주로 API를 만들거나 사용하는 방식을 따릅니다.
UserCode.cpp(혹은 .java 혹은 .c인데, 본 글은 c++를 기준으로 작성됩니다.)와 Main.cpp가 나뉘어져 있습니다.
Main.cpp는 읽기전용이기 때문에 열람만 가능하고, 수험자가 수정할 수 없습니다. 이 코드에서는 테스트케이스를 별도 파일에서 읽어오고, 이를 이용하여 UserCode.cpp의 함수들을 통한 결과값을 산출한 뒤 expectedoutput.txt의 결과와 비교하고 수험자의 성적을 출력합니다. 완벽하게 모두 분석할 필요도, 시간도 없지만 구체적인 testcase만 별도 파일로 숨겨져있을 뿐 수험자의 성적 채점코드 자체가 모두 공개되어있는 것이기 때문에 문제풀이의 방향을 이 코드를 통해서 알 수 있습니다.
수험자는 UserCode.cpp에 요구사항대로 함수들을 구현하여(혹은 Main.cpp에 제공되어 수정할 수는 없지만 사용할 수는 있는 함수들을 사용하여) 최대 성적을 내는 것이 목표입니다.

  • 예시 1 - Main API 사용) 연결리스트를 별도로 구현한 코드가 있다. newList()는 새 리스트를 동적할당으로 생성하여 반환하고, append()는 새 엘리먼트를 연결시키며...(생략)
    이 코드를 활용하여 정렬 알고리즘을 구현하시오.

위와 같은 방식의 API사용은 대부분 익숙할테니 부연설명은 하지 않도록 하겠습니다.

  • 예시 2 - Main에게 API 제공 초급) 테트리스 코드가 있다.
    현재 보드상태는 10x30 배열 형태로 표현되며, 새로운 블록은 5가지 모양을 가지고 있다.
    vector< vector<int> > board에 0은 빈자리, 1은 채워진공간(이미 고정된 블록), 2는 현재 내려오고 있는 새블록을 뜻한다.
    mvRight()은 떨어지는 블록을 오른쪽으로,
    mvLeft()는 왼쪽으로 한칸 움직이며,
    rotate()는 블록을 시계방향으로 회전시키고,
    checkStatus()는 현재 보드의 상태를 보고 테트리스 규칙에 따라 삭제되어야 할 줄이 있으면 삭제시킨 뒤 중력에 맞게 위에 있는 블록들을 아래로 내려주는 일을 해야한다.
    수험자는 mvRight(), mvLeft(), rotate(), checkStatus()를 구현하면 된다.
    메인 프로그램은 수험자가 작성한 함수들을 호출하게 되며, 이를 활용한 결과가 정확하면 점수를 얻을 수 있고, 틀리면 감점된다.

  • 예시 3 - Main에게 API 제공 고급) 테트리스 코드가 있다.
    getBoard()는 호출시 현재 보드상태를 10x30 배열 형태로 반환한다.
    tick()은 내부 로직으로, 시간을 1초 가게 한다. 이 때 위에서 떨어지는 블록은 중력에 따라 한칸 아래로 내려오게 되며, 내려온 뒤 테트리스 규칙에 따라 삭제되어야 할 줄이 있으면 바로 삭제시키고 보드 상태에 반영한다.
    mvRight()은 떨어지는 블록을 오른쪽으로,
    mvLeft()는 왼쪽으로 한칸 움직이며,
    rotate()는 블록을 시계방향으로 회전시킨다.
    이것들 중 외부에서(수험자가) 부를 수 있도록 extern 처리된 함수는 mvRight(), mvLeft(), rotate()이다.
    수험자는 단 하나의 함수를 구현하면 된다.

    void nextCommand(vector< vector<int> > board) {
      ...
    }
    

    메인 로직(수험자가 수정할 수 없고, 열람만 가능)의 구현체 요약은 다음과 같다.

    for(..생략..){
      nextCommand(getBoard());
      tick();
    }
    int score = compareWithExpectedResult();
    

    주의점

    • 새로운 블록이 소환되었을 때(항상 중앙에서 시작), 바로 아래에 채워진 자리가 있다면 게임이 즉시 종료된다.

    • 블록을 rotate()할 수 없는데 nextCommand()에서 이를 호출할 경우 역시 게임이 즉시 종료된다.

    • nextCommand()에서는 한번에 단 하나의 함수밖에 호출할 수 없다. 즉, tick();이 실행될 때 int time을 1 증가시키는데, 동일한 time값일 때 mvRight(), mvLeft(), rotate()이 모두 합쳐서 1회 초과하여 호출될 경우 이 역시 게임이 종료된다.

    • 테트리스 점수는 한 줄이 삭제될 때 +1점이 된다.

    • 게임이 종료된 후 점수가 높을수록 좋다(당연히...).

    테트리스의 점수가 최대가 되도록 nextCommand()를 구현하시오.

3-3. 문제풀이 예시

문제 설명

  • 개발자 관리 시뮬레이터

  • 개요:
    FourStarsCompany에는 N명의 개발자들이 일을 하고 있다. 이 개발자들은 능력이 동일하여, 어떤 일을 하든 동일한 효율을 낸다.
    하지만 이 회사에는 Git과 같은 소스제어/협업툴이 존재하지 않아서, 압축파일로 매번 작업물을 공유해야 한다. 따라서 동시에 두 개발자 이상이 동일한 프로젝트를 맡는 것은 불가능하다.
    그러나 하루는 한 개발자가 일하고 다음 날은 다른 개발자에게 프로젝트를 물려주는 것은 가능하며, 이 때 인수인계 시간 등은 0이고 효율 또한 변동되지 않는다.
    각 프로젝트는 끝내기 위해 필요한 workload값이 있으며, 하루 개발자가 일했을 때 1 증가한다. 그리고 각 프로젝트에는 deadline이 존재한다. 이 값은 이 프로젝트가 주어진 날로부터(!= 개발자가 배정된 날) ${deadline}일수만큼 시간이 주어진다는 뜻이며, 데드라인을 넘으면 어떠한 보상도 받지 못한다.
    각 프로젝트는 끝냈을 때 받는 payment가 있다. 이는 회사 매출에 합산된다.

  • 목표:
    프로젝트 성과를 가능한 최대로 만들어라. 해당 test case의 이론적 최대치에 가까울수록 점수를 높게 받을 수 있다.

  • 문제상세:
    이 회사는 T일의 시간동안 영업한다. T일 이후에는 즉시 모든 업무를 중단한다.
    수험자가 사용할 수 없는 메인 함수들은 다음과 같다.

    void tick(); //currentTime을 하루 가게 한다. 또한, 이 함수에선 getNewProject(), nextCommand() 등을 호출한다.
    

    tick() 함수는 중요하므로 의사코드로 아래에 부연설명을 진행한다.
    (실제 시험에서는 당연히 온전한 코드로 존재합니다. 제가 실제코드 짜기 귀찮아서 의사코드로 제공합니다.)

    def tick():
      global currentTime
      global pID
    
      currentTime += 1
      for item in [i for i in newProjectList if i.time == time]:
        getNewProject(pId, item.payment, item.workload, item.deadline)
        pID += 1
      
      nextCommand(time)
    

    수험자가 사용할 수 있는 메인 함수들은 다음과 같다.

    void allocProject(int pID, int wID); //wID 개발자에게 pID 프로젝트를 배정한다.
    void finishProject(int pID); //프로젝트를 종료시키고, payment가 매출에 합산된다.
    

    수험자가 제공해야 하는 함수들은 다음과 같다.

    void init(int N, int T); //각 테스트케이스 시작 전에 N과 T 전달. 수험자가 만든 전역변수 등이 있으면 이를 알아서 초기화해줘야 오답 방지 가능.
    void getNewProject(int pID, int payment, int workload, int deadline);
    void nextCommand(int currentTime); //여기에서 allocProject(), finishProject() 함수들을 이용하여 최적의 의사결정을 한다. 
    

    수험자는 위 3개 함수를 구현하여 회사의 매출을 최대로 올려야 한다.
    주의점

    • 만약 프로젝트가 요구로 하는 인시(workload)를 채우지 못했거나, 제한시간(deadline)을 넘었거나, 요구시간보다 많이 투입된 상태에서 finishProject()가 호출될 경우 수험자는 0점처리된다.

    • stopProject()라는 함수는 없고, 당연히 구현할 수도 없다(지정된 사용자함수만 Main에서 호출해가기 때문에, 구현해도 사용할 수가 없다.). Main함수의 모든 변수들은 접근불가능하고, 접근할 수 있도록 열어준 것은 allocProject()finishProject() 함수 두개 뿐이다.

    • allocProject()를 동일한 프로젝트ID(pID)에 대해 다른 개발자ID(wID)에게 배정하면, 기존 개발자는 해당 프로젝트에서 제외되고 해당 신규 개발자에게 배정된다.
      달리 말하면, 1이라는 프로젝트를 1이라는 개발자가 하고 있었을 때, allocProject(1, 2)를 호출시 1프로젝트는 2개발자가 맡게 되며, 1개발자는 아무 프로젝트에도 배정받지 않은 상태가 된다.

      또한, 이후 allocProject(2, 2)를 호출시 2개발자는 2프로젝트를 맡게 되고, 1프로젝트 연계는 해제된다. 따라서 1프로젝트는 아무도 하고 있지 않게 된다.

해설

  • 참고: 일단은 정보가 급한 분들이 계실 것 같아서, 문제를 완전히 만들지는 않은 상태에서 얼개만 짜놓고 바로 포스팅하게 되었습니다. 나중에 모든 채점코드(Main)와 모범답안(UserCode)을 만들게 된다면 구체적인 풀이코드를 포스팅하겠습니다.

  • 처음 저런 형태의 문제를 접하면, 기존의 "input.txt만 제공할테니 알아서 이거 입력 받고, 우리가 원하는 출력만 해줘. 그럼 점수 줄게" 같은 형식에만 익숙했던 분들은 충공깽 상태에 빠집니다. 대체 뭘 짜라는지도 모르겠고, 어떻게 접근해야 할지도 당황스럽죠.
    하지만 알고보면 단순한 문제입니다.

    일단, Main::allocProject()Main::finishProject()을 사용하는 것 이외에 우리가 메인 코드에 영향을 줄 수 있는건 없습니다. 따라서, 메인 코드에 시뮬레이션되어있는 문제 현황을 우리 또한 동일하게 파악하고 있을 수 있으려면, 우리가 별도로 시뮬레이션 환경을 똑같이 만들어줘야 합니다.
    즉, 메인 함수에도 당연히 현재 프로그래머들이 어디 프로젝트에 배치되었는지, 각각 진행상황은 어떻고 매출은 어떤지 등등을 산정하고 있을텐데요...
    이걸 우리가 접근해서 확인할 방법은 없으니 우리가 똑같이 구현해줘야 합니다. 다행스럽게도 UserCode::nextCommand()Main::tick()함수를 보면 알 수 있듯이 각 유니크한 currentTime에 단 한번만 불립니다.

    즉, UserCode::getNewProject()가 틱함수에서 불릴 때 우린 새로운 프로젝트들에 대한 정보를 받아올 수 있고, 이걸 UserCode.cpp파일에 만들어놓은 전역변수 배열에 저장해둬야 하며, currentTime 또한 처음에 0으로 셋팅한 뒤 UserCode::nextCommand()가 불릴 때 직접 1씩 더해줘야 합니다. 알다시피 Main::getCurrentTime()이란 함수가 제공되지 않았기 때문에 Main::currentTime 변수의 접근이 불가하기 때문이죠.

    우리가 해야 하는 걸 정리하면...

    • init()이 불릴 때, 전역변수로 만들어놓은 현재시간, 프로젝트 리스트, 개발자-프로젝트 배정 현황 정보 등 모든 것들을 초기화해줍니다.
    • getNewProject()가 불릴 때 현재 할 수 있는 프로젝트 리스트에 workload와 payment 정보를 추가합니다.
    • nextCommand()가 실행될 때, 현재 시간과 프로젝트들의 가치를 고려하여 최적으로 인력을 배정합니다.

    여기에서 nextCommand()가 제일 중요해보이죠? 맞습니다. 여기에 모든 코어 로직이 들어가야 하죠. Main.cpp에서는 매 tick마다 이 함수를 불러주는게 끝입니다. 그리곤 UserCode.cpp에서 알아서 필요한 Main::functions를 불렀을거라고 가정하고 바로 다음 날로 넘어가고, 영업일이 끝나면 종료한뒤 성적을 계산합니다. 따라서 우린 매번 nextCommand가 불릴 때, 적합한 의사결정을 해줘야 합니다.
    여기서 어떤 로직을 쓰면 될까요? 조금 생각해보면 답이 나오는데요...
    생각할 시간을 드리겠습니다. 답을 보려면 내려주세요.
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    각 프로젝트는 다른 payment를 제공하죠. 하지만 무조건 높다고 좋은건 아닙니다. 100일이 걸리고 10원을 주는 프로젝트와 10일이 걸리고 9원을 주는 프로젝트중엔 당연히 후자가 좋겠지요.
    따라서 payment/workload이 우선순위값이라고 생각해볼 수 있습니다.

    하지만 이렇게 되면 문제가 생기는데요... 개발자 한명만 있는 회사를 생각해봅시다.

    0일차에 10일 걸리고 50원 주는 프로젝트1을 받아서, 개발자1을 배치시켰습니다.
    그리고 5일동안 아무일이없었는데(==계속 배정된대로 일을 진행),
    5일차에 6일 걸리고 50일 걸리는 프로젝트2가 생겼습니다.

    그럼 이 때 우린 프로젝트2로 재배치해야 할까요? 프로젝트150/10 == 5의 우선순위고, 프로젝트250/6 == 8.333의 우선순위인데 말이죠.
    그런데 잘 생각해보면 프로젝트1은 앞으로 5일만 하면 50원을 받는데, 프로젝트2는 6일을 해야 50원을 받습니다.
    즉, 우리 공식에 조정이 필요하겠군요. payment/(workload - currentInvestedTime)으로 바꿔서, 실제로 보상을 수령하기까지 추가로 투입해야 되는 시간을 payment에서 나눠야 매몰비용을 제거할 수 있을 것입니다. 그리고 당연하지만, 이미 더 투입해야 하는시간(workload - currentInvestedTime)이 데드라인까지 남은시간(currentTime - deadline)보다 클 경우 프로젝트를 마칠 수 없는게 당연하니까 우선순위를 0으로 설정해주는 로직이 있어야겠습니다.

    그리고 우선순위를 알았으니, 우선순위로 내림차순 정렬한 뒤 N명의 개발자가 있으니 N명에게 상위 N개 프로젝트를 배분하면 됩니다(다행히도 모든 개발자는 동일한 1workload/day의 효율을 가지니까 고민할 게 없습니다).

    자, 이거면 문제 끝입니다. 구현만 조심히 잘 하면 되겠네요.

    하지만 함정이 두가지 있습니다.
    첫번째, 우선순위 순으로 프로젝트를 정렬해야 하는데 이걸 어떻게 정렬할 수 있을까요? 우선순위큐를 쓰면 편하겠지만, 위에서 말했다시피 STL 모두 쓸 수 없습니다(아.. 써놓고 보니 저도 실수한 부분이 있는데요, std::vector 또한 사용 불가능합니다. 인자로 전달될 때도 배열로 와요. 위에 vector<int> 등 써둔건 이해만 그렇게 하시고, 실제 시험에선 int[]라고 생각하면 되겠습니다...).
    priority_queue를 쓸 수 없으니, 결국 정렬을 구현해야 합니다. 이 때 버블소트같은 거 쓰면, 문제 없을거같지만 이건 매우 엄격한 상대평가라서 시간 때문에 탈락합니다. 퀵소트는 바로 그 자리에서 구현해서 쓸 수 있어야 합니다.
    두번째, 개발자들을 적절하게 배치해야 하는데, 매일매일 하루가 지날 때마다 프로젝트 우선순위 리스트가 바뀔텐데요, 이 때 이미 배정된 개발자가 있을때 이걸 처음부터 죄다 재배치할 수는 없습니다(그렇게 되면 실행시간이 무지막지하게 길어져서 탈락하게 됩니다). 따라서 이미 배정된 적이 있는 pID와 그에 상응하는 wID, 그리고 배정되지 않은 pID와 wID를 계속 트래킹할 수 있어야 하며, 다음 날 currentTime이 +1되어서 우선순위 리스트들이 갱신되었을 때 우선순위에서 밀려서 dealloc될 프로젝트를 파악하고 그 프로젝트를 맡고있는 wID의 개발자를, 어제까진 배정되지 않았지만 오늘부터 우선순위(상위 N where N is number of developers) 안에 들게 된 pID의 프로젝트에 배정(allocProject())해야 할 것입니다. 이 모든걸 구현하려면 역시 해시맵이나 스택, 큐 등 본인 구현방법에 필요한 자료구조들을 손수 만들어 써야 합니다.
    이 모든걸 4시간 안에 해야 하는 것이죠.

    깔끔하게 글을 쓰고 싶었는데 주저리주저리하다보니 길어지고 난잡해졌네요. B형에 대한 글들은 인터넷에 눈을 씻고 찾아봐도 없던데, 아마 다들 S사에서 열심히 일하느라 무서워서(?) 글을 못 쓰나 봅니다.
    전 이제 다른 회사에서 일하고 있으니 무서울게 없...어서요 하..하하(농담)

    어쨌든, 이 문제 설명대로 문제를 풀어보시기에는 어려움이 있을 겁니다. 채점코드도 없고 메인코드도 없는 상황에서 함수만 짜면 확인이 안되니까요.
    대신, 시간이 좀 되신다면 직접 채점코드 Main 구조부터 수험자가 짜야 할 UserCode.cpp까지 직접 모두 만들어보시는걸 추천합니다. 그렇게 하고 나면 많은 걸 배운 자신을 돌아볼 수 있지 않을까 싶습니다.

    덧. 오늘 이 글을 쓰고 swacademy 한번 들어가보니 이제 아예 샘플 문제를 제공하네요 -_-;; 괜한 고생을 해서 글을 썼나 싶긴 합니다만, 도움이 되었으면 좋겠네요. 아직 블로그 글 제대로 쓰고있지는 않아서 상관은 없는데, 도움됐으면 ♥공감 눌러주고 가세요~


  Comments,     Trackbacks