상권's

TIL 37 (assert, MVC, mocha)(2021.11.16) 본문

~2022 작성 글/TIL

TIL 37 (assert, MVC, mocha)(2021.11.16)

라마치 2021. 11. 16. 22:56
문제
세로와 가로의 길이가 각각 M, N인 마을지도가 배열로 주어졌을 때, '1'은 주민이 있는 집을 의미하고 '0'은 주민이 없는 땅을 의미합니다. 이 마을은 소문이 시작되면 하루에 상하좌우 한 칸 바로 옆에 있는 집으로 퍼집니다. 특정 주민의 집 (R, C)으로부터 어떤 소문이 시작될 경우, 마을 전체로 소문이 퍼지는데 걸리는 시간(일)을 리턴해야 합니다.

주의사항
M, N은 100 이하의 자연수입니다.row, col에는 항상 주민이 살고 있습니다.모든 집은 연결되어 있습니다. 즉, 한 집에서 다른 집으로 가는 경로가 항상 존재합니다.village를 그래프로 구현하는 함수가 주어집니다.
direction으로 상하좌우를 움직일 수 있는 방법을 제시해준다.
count라는 변수를 만들어서 일수를 체크한다.
row, col을 시작으로 해당 direction을 진행시킨다.
도착지점이 0이면 return;
도착지점이 village보다 크다면 return;
그래서 모든 것이 다 끝나고 나면은 count를 리턴한다.

첫 수도코드 => direction을 반복시켜서 진행을 하려고 했었으나 한 번에 네 방향으로 뻗어가는 게 구현이 쉽지 않았습니다. 다른 분들이 구현하신 코드를 보니 큐를 이용해서 풀 수 있는 전형적인 bfs 알고리즘 문제라고 하시던데, 아직 큐를 써야 되는 상황이나, 구현이 많이 부족해서 추가적인 학습을 해야 할 거 같습니다. 다음에는 이런 문제를 풀 수 있도록 열심히 학습해야 겠습니다.

const gossipProtocol = function (village, row, col) {
  let direction = [
    [0, 1], // 우
    [0, -1], // 좌
    [1, 0], // 하
    [-1, 0] // 상
  ]
  let count = 0;
  let queue = [[row, col]]; // [[1, 2]]
  let visit = Array(village.length).fill(0).map(() => Array()); // [ [], [], [], [] ]
  visit[row][col] = count;
  while(queue.length) {
    let now = queue.shift(); 
    let [y, x] = now; 
    if ( visit[y][x] > count ) count = visit[y][x]; // 일수를 나타내는 것으로 한 번 돌면, 사방에서 갈 수 있는 곳은 다 1이 된다. 그래서 count가 1이 오른다.
    for (let n = 0; n < direction.length; n++ ) {
      let dy = y + direction[n][0]; // 0 0 1 -1 상하좌우를 다 다닌다.
      let dx = x + direction[n][1]; // 1 -1 0 0
      if(dy < 0 || dx < 0 || dy >= village.length || dx >= village[0].length) continue; // 탈출 조건인 지 여부를 확인하고 true 면 다음 n으로 넘어가고, 아니면 다음 if문
      if(visit[dy][dx] || village[dy][dx] === '0' ) continue; 
      // undefined || true => true => 다음 n으로 넘어간다.
      // undefined || false => false => 밑의 코드를 진행한다.
      // 0 || false => false
      // 0 || true => true
      // 1 || false => 1 => 다음 for 문으로
      // 1 || true => 1 => 다음 for 문으로
      visit[dy][dx] = visit[y][x] + 1; // 한 칸 씩 더 나아가니깐 1이 더해진다.
      queue.push([dy, dx]) // 해당 부분을 큐에 넣고 다시 진행한다.
    }
  }
  console.log(visit)
  return count
};

mocha란. 출처

 

Mocha is a feature-rich JavaScript test framework running on Node.js and in the browser, making asynchronous testing simple and fun. Mocha tests run serially, allowing for flexible and accurate reporting, while mapping uncaught exceptions to the correct test cases. Hosted on GitHub.

 

Mocha는 Node.js와 브라우저에서 실행되는 기능이 풍부한 자바스크립트 테스트 프레임워크로, 비동기식 테스트를 간단하고 재미있게 만든다. Mocha 테스트는 연속적으로 실행되므로 유연하고 정확한 보고가 가능하며, 수집되지 않은 예외 사항을 올바른 테스트 케이스에 매핑할 수 있습니다. GitHub에서 호스팅됩니다.

 

다운로드

npm install mocha --save-dev

사용예제

const utils = require('./utils')
const assert  = require('assert')

describe('utils.js 모듈의 capitailize() 함수는, ', () => { // 테스트 수트 => 테스트 환경으로 모카에서는 describe()로 구현한다.
 it('문자열의 첫번째 문자를 대문자로 변환한다.', () => { // 테스트 케이스 => 실제 테스트를 말하며, 모카에서는 it()으로 구현한다.
    const result = utils.capitialize('hello');
    assert.equal(result, 'Hello')
 })
})

// mocha는 npm으로 다운받을 때, -dev라는 옵션을 넣어준다.
// 개발환경에서 필요한 환경임을 알려준다.
// 이 파일을 진행하려면, node_modules/.bin/mocha + 테스트할 파일 명
// 터미널에 입력하면 테스트를 진행할 수 있다.

오늘 강의를 통해서 mocha에 대해서 학습해보았습니다. 추가적으로 학습하면서 직접 테스트 코드를 작성해보고 블로그에 추가적으로 올려보도록 하겠습니다.

 


Assert

 

Definition and Usage 출처

The assert module provides a way of testing expressions. If the expression evaluates to 0, or false, an assertion failure is being caused, and the program is terminated.

This module was built to be used internally by Node.js.

 

assert 모듈은 표현식을 테스트하는 방법을 제공합니다. 식이 0 또는 거짓으로 평가되면 assert 오류가 발생하고 프로그램이 종료됩니다.
이 모듈은 Node.js에서 내부적으로 사용하도록 제작되었습니다.

 

모듈 로드 

const assertion = require('assert');

Assert 메소드 정리 출처

assertion.ok(object)
인자값이 true 아니면 에러
assertion.ifError(object)
인자값이 false 아니면 에러
assertion.equal(object, object) 
두 개의 인자를 == 으로 비교 
assertion.notEqual(object, object)
두 개의 인자를 != 으로 비교
assertion.staticEqual(object, object)
두 개의 인자를 === 으로 비교
assertion.notStaticEqual(object, object)
두 개의 인자를 !== 으로 비교
assertion.deepEqual(object, object)
객체를 그냥 equal 하게 되면 객체를 참조하고 있는 주소가 서로 다르기 때문에 무조건 false가 나온다. 그래서 해당 객체의 속성값을 == 으로 비교해주는 메소드
assertion.notDeepEqual(object, object)
deepEqual 메소드를 != 으로 비교하는 메서드
assertion.deepStrictEqual() 
deepEqual 메소드를 === 으로 비교하는 메서드
assertion.notDeepStrictEqual() 
deepEqual 메소드를 !== 으로 비교하는 메서드
assertion.throws(() => new Error(‘에러’), TypeError) 
인자로 넘어온 값이 함수이면서, 반환값이 에러인경우 ( 두 번째 인자는 오류의 유형인데 option)
assertion.doesNotThrow(() => new Error(‘에러’), TypeError)
인자로 넘어온 값이 함수이면서, 반환값이 에러가 아닌 경우 ( 두 번째 인자는 오류의 유형인데 option)
assertion.fail() 
무조건 실패

MVC 출처

MVC (모델-뷰-컨트롤러)는 사용자 인터페이스, 데이터 및 논리 제어를 구현하는데 널리 사용되는 소프트웨어 디자인 패턴입니다. 소프트웨어의 비즈니스 로직과 화면을 구분하는데 중점을 두고 있습니다. 이러한 "관심사 분리" 는 더나은 업무의 분리와 향상된 관리를 제공합니다. MVC 에 기반을 둔 몇 가지 다른 디자인 패턴으로 MVVM (모델-뷰-뷰모델), MVP (모델-뷰-프리젠터), MVW (모델-뷰-왓에버) 가 있습니다.

MVC 소프트웨어 디자인 패턴의 세 가지 부분은 다음과 같이 설명할 수 있습니다.

  1. 모델: 데이터와 비즈니스 로직을 관리합니다.
  2. 뷰: 레이아웃과 화면을 처리합니다.
  3. 컨트롤러: 명령을 모델과 뷰 부분으로 라우팅합니다.

모델

모델은 앱이 포함해야할 데이터가 무엇인지를 정의합니다. 데이터의 상태가 변경되면 모델을 일반적으로 뷰에게 알리며(따라서 필요한대로 화면을 변경할 수 있습니다) 가끔 컨트롤러에게 알리기도 합니다(업데이트된 뷰를 제거하기 위해 다른 로직이 필요한 경우).

뷰는 앱의 데이터를 보여주는 방식을 정의합니다.

컨트롤러

컨트롤러는 앱의 사용자로부터의 입력에 대한 응답으로 모델 및/또는 뷰를 업데이트하는 로직을 포함합니다.

예를 들어보면, 쇼핑 리스트는 항목을 추가하거나 제거할 수 있게 해주는 입력 폼과 버튼을 갖습니다. 이러한 액션들은 모델이 업데이트되는 것이므로 입력이 컨트롤러에게 전송되고, 모델을 적당하게 처리한다음, 업데이트된 데이터를 뷰로 전송합니다.

 

단순히 데이터를 다른 형태로 나타내기 위해 뷰를 업데이트하고 싶을 수도 있습니다(예를 들면, 항목을 알파벳순서로 정렬한다거나, 가격이 낮은 순서 또는 높은 순서로 정렬). 이런 경우에 컨트롤러는 모델을 업데이트할 필요 없이 바로 처리할 수 있습니다.


문제

말썽꾸러기 김코딩은 오늘도 장난을 치다가 조별 발표 순서가 담긴 통을 쏟고 말았습니다.
선생님께서는 미리 모든 발표 순서의 경우의 수를 저장해 놓았지만 김코딩의 버릇을 고치기 위해 문제를 내겠다고 말씀하셨습니다.

김코딩은 모든 조별 발표 순서에 대한 경우의 수를 차례대로 구한 뒤 발표 순서를 말하면 이 발표 순서가 몇 번째 경우의 수인지를 대답해야 합니다.

총 조의 수 N과 선생님이 말씀하시는 발표 순서 k가 주어질 때, 김코딩이 정답을 말 할 수 있게 올바른 리턴 값을 구하세요.
모든 경우의 수가 담긴 배열은 번호가 작을수록 앞에 위치한다고 가정합니다.
ex) N = 3일경우, [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

입력
인자 1: N
Number 타입의 1 <= N <= 12인 조의 개수

인자 2: K
Number타입의 Array (0 <= index)
ex) n이 3이고 k가 [2, 3, 1]일 경우
모든 경우의 수를 2차원 배열에 담는다면 [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]이 되고,
반환하는 값은 3이 됩니다.

주의사항
k내에 중복되는 요소는 없다고 가정합니다.

입출력 예시
let output = orderOfPresentation(3, [2, 3, 1]);
console.log(output); // 3
output = orderOfPresentation(5, [1, 3, 2, 4, 5])
console.log(output); // 6
function orderOfPresentation (N, K) {
  // TODO: 여기에 코드를 작성합니다.
  let factorial = function(n) {
    if(n < 2) return 1
    return n * factorial(n - 1)
  }

  // 모든 경우의 수가 담긴 배열은 번호가 작을수록 앞에 위치한다고 가정합니다.

  //  n! (엔팩토리얼)의 뜻은,서로 다른 n개를 나열하는(줄세우는) 경우의 수를 의미합니다.
  //  1, 2, 3 이라는 숫자가 주어지고 각각의 숫자를 나열해서 만드는 경우라고 생각을 해보면, 3개를 나열하기 때문에 3!로 6이 된다.
  // ex) N = 3일경우, [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

  // 이렇게 배열을 만들 경우에,
  // 1로 시작하는 배열은 가장 먼저 만들어지게 된다(1보다 작은 숫자는 없다고 본다)
  // 첫번째부터 몇번째까지가 1** 으로 이루어지는 지를 알아볼 경우,
  // 이는 그 아랫 자릿수를 생각해본다면, 서로 다른 2, 3으로 나열을 하는 것으로, 2! 즉 2개의 경우가 생길 수 있다.
  // 이러한 결과를 통해서 우리는 1**은 해당 배열에서 2개가 존재한다는 것을 알 수 있다.

  // 위의 배열에서 3** 으로 시작하는 수는 1과 2로 시작하는 배열을 먼저 만들고 난 다음에 만들 수 있다.
  // 1, 2, 3으로 각각의 숫자들을 나열해서 배열로 만들 때, 1과 2로 만들 수 있는 배열의 수를 더해주고, 10의 자릿수가 나오는 경우의 수를 더해서 3** 이라는 숫자가
  // 몇 번째에서 위치하는 지 알 수 있다.

  // 1과 2로 시작하는 수는 위의 경우처럼 계산을 했을 때, 각각 2!씩이다.
  // 해당 숫자들이 먼저 만들어지고 나서야 3**으로 시작하는 수가 만들어질 수 있다.
  // 그렇기 때문에 2! * 2 = 4 즉, 1, 2, 3으로 배열을 만들 때, 4번째 이후에 3** 으로 구성된 숫자들이 위치한다.
  // 이는 해당 자릿수에서 우리가 찾길 위하는 숫자보다 더 작은 숫자들의 갯수(1, 2로 총 2개)와 아랫 자릿수의 갯수를 팩토리얼로 계산한 것(10의 자릿수이기 때문에 2!)에 곱한 값이 된다.

  let arr = [];
  for (let n = 1; n <= N; n++) {
    arr.push(n) // 인자로 주어지는 N까지의 숫자들을 배열로 만들어준다.
  }
  let result = 0;
  for (let i = 0; i < K.length; i++ ) { // 우리가 찾길 원하는 배열을 순회한다.
    let num = K[i]
    arr = arr.filter((el) => el !== num) // 첫번째 자릿수와 다른 숫자들로 배열을 재구성한다.=> 다음 자릿수들을 구성하는 수가 된다. 팩토리얼로 계산이 된다.
    let candidate = arr.filter((e) => e < num).length; // 첫번째 숫자보다 작은 수들을 찾는다 => 작은 수들이 먼저 배열로 만들어지기 때문 팩토리얼로 계산이 된 수를
    result += factorial(arr.length) * candidate; // 해당 숫자보다 작은 수들의 갯수로 곱한다.
  }
  return result;
}
Comments