0%

기본 설정

eslint 설치

아래 패키지들을 설치한다.

1
2
$ yarn add -D eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin
$ npm install --save-dev eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin
  • eslint: Javascript Linter
  • typescript: Typescript… 설치 안 했다면 하자 ^-^
  • @typescript-eslint/parser: 타입스크립트에서 ESLint를 사용할 수 있게 하는 파서
  • @typescript-eslint/eslint-plugin: 코드베이스에서 ESLint 규칙을 적용하는 플러그인

설정

프로젝트 루트에 .eslintrc.js 파일을 생성한 다음 아래 코드를 복붙한다.

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint',
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
],
};
  • root: true로 설정하지 않으면 파일 시스템의 상위 폴더까지 탐색하기 때문에 설정해준다.
  • parser: 위에서 설치한 parser를 적용한다.
  • plugin: 위에서 설치한 plugin을 적용한다.
  • extends
    • eslint:recommended을 적용하면 ESLint의 내장되어있는 추천 설정을 사용할 수 있다.
    • @typescript-eslint/recommendedeslint:recommended 와 비슷하다. 이걸 적용하면 타입스크립트 전용 규칙을 추가적으로 사용할 수 있다.

env

여기에 추가적으로 env를 설정한다. 미리 정의된 전역 변수를 정의해준다.
브라우저 환경에서 lint를 돌릴 경우 envbrowser를 추가해준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint',
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
],
env: {
browser: true,
}
};

불필요한 파일 무시

.eslintignore 파일 만들고 무시할 파일/폴더를 추가해준다.

1
2
node_modules
dist

실행

1
2
$ yarn eslint . --ext .js,.jsx,.ts,.tsx
$ npx eslint . --ext .js,.jsx,.ts,.tsx

여기서 --ext는 javascript file extensions을 설정하는 옵션이다. 기본값은 .js이다.

Airbnb/Standard 규칙 적용하기

  • Airbnb’s ESLint config - eslint-config-airbnb-typescript.
  • Standard - eslint-config-standard-with-typescript.

둘 중 원하는 거 설치해서 적용하면 된다. 나는 Standard 설정을 적용해보았다.
extends의 eslint 추천 설정들을 삭제하고 원하는 스타일을 적용하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint',
],
extends: ['standard-with-typescript'],
parserOptions: {
project: './tsconfig.js'
},
ignorePatterns: ['.eslintrc.js'],
};

이때 parserOptions.project을 설정해줘야 된다. 없으면 에러가 발생한다.
왜 에러가 발생하나 했더니… 타입 명시가 필요한 규칙의 경우 이 설정이 필요하다고 나와있다. (문서 참고)
parserOptions.project 을 설정하면 ESLint가 eslintrc.js 파일도 lint를 시도하기 때문에 ignorePatterns을 추가한다.

Prettier와 함께 사용하기

의존성 설치

1
2
$ yarn add -D  --save-dev prettier eslint-plugin-prettier eslint-config-prettier
$ npm install --save-dev prettier eslint-plugin-prettier eslint-config-prettier
  • prettier: prettier
  • eslint-config-prettier: 불필요하거나 Prettier와 충돌할 수 있는 모든 규칙을 비활성화
  • eslint-plugin-prettier: 코드 포매팅할 때 Prettier를 사용하게 만드는 규칙을 추가

.eslintrc에 prettier 설정 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'prettier'],
extends: ['standard-with-typescript', 'prettier', 'plugin:prettier/recommended'],
parserOptions: {
project: './tsconfig.json',
},
rules: {
'@typescript-eslint/semi': 'off',
'@typescript-eslint/space-before-function-paren': 'off',
},
};

eslint-config-prettier 추가한다고 해도 충돌이 나는 경우가 있는데, 이 경우 rules 옵션 설정해주면 된다.

.prettierrc 생성

1
2
3
4
5
6
7
8
{
"trailingComma": "all",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"printWidth": 160,
"parser": "typescript"
}

참고 문서

typescript-eslint/typescript-eslint

Next.js에 Styled Components를 적용하고 새로고침을 하면 콘솔에 아래와 같이 나온다.

/blog/images/2020-07-26-next-styled-components/2020-07-11__12.06.58.png

왜일까? 🤔
server와 client 어쩌고 나오는 것을 보니 ssr 문제처럼 보인다.
구글링을 해보자.

/blog/images/2020-07-26-next-styled-components/2020-07-11__1.23.42.png

https://github.com/vercel/next.js/tree/canary/examples/with-styled-components

관련 예제가 있다. (살았다.)
예제의 목적은 요약하면 다음과 같다.

  • 를 확장하고 에 서버 사이드 렌더링된 스타일을 주입
  • 서버 사이드 렌더링에 필요한 babel-plugin-styled-components 플러그인을 추가

적용 방법

1. babel 플러그인 설치 및 설정

babel-plugin-styled-components를 설치한다.

1
npm install --save-dev babel-plugin-styled-components

그리고 다음과 같이 .babelrc 설정을 해주자.

1
2
3
4
{
"presets": ["next/babel"],
"plugins": [["styled-components", { "ssr": true }]]
}

2. _document.js 파일 생성

pages 폴더에 _document.js 파일을 생성한다. 이 파일에서 를 확장해 에 서버 사이드 렌더링된 스타일을 주입한다.
한마디로 next.js의 index.html head에 서버 사이드 렌더링 스타일을 주입시켜 주는 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import Document from "next/document";
import { ServerStyleSheet } from "styled-components";

export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;

try {
// sheet을 사용해 정의된 모든 스타일을 수집
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
});

// Documents의 initial props
const initialProps = await Document.getInitialProps(ctx);

// props와 styles를 반환
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
} finally {
sheet.seal();
}
}
}

Styled component에서 제공하는 ServerStyleSheet를 활용해 정의된 모든 스타일을 수집하고, 페이지가 렌더링되기 전에 props와 styles를 채워준다. 수집된 스타일은 getStyleElement 메소드로 <style> 태그에 감싸져 head에 뿌려지게 된다.

클로저의 대표적인 개념은 다음과 같습니다.

  1. 클로저는 이미 생명 주기가 끝난 외부 함수의 변수를 참조하는 현상이다.
  2. 클로저는 선언될 당시의 정보를 담는다.
  3. 선언될 당시의 정보를 외부로 노출시킬 수 있는 유일한 방법은 함수 내부에서 return하는 것이다.

처음 자바스크립트를 배울 때 이 개념이 너무 어려웠었던 기억이 납니다. (물론 지금도 어렵…)
그래서 클로저 예제를 크롬 개발자 도구로 디버깅해서 어떻게 동작하는지 확인해봅시다.

1
2
3
4
5
6
7
8
9
10
var outer = function () {
var count = 1;
var inner = function () {
return ++count;
};
return inner;
};
var outer2 = outer();
console.log(outer2());
console.log(outer2());

검색하면 흔하게 볼 수 있는 클로저 예제입니다.
outer 함수에서 inner 함수를 반환하고, 그걸 outer2 변수에 담아 실행하는 예제입니다. 선언된 당시의 정보인 inner를 함수 내부에서 return 하여 외부로 노출(개념 3번)시키고 있습니다.

이때 outer() 함수의 실행은 끝났다고 볼 수 있습니다. 하지만 outer2 변수가 참조하고 있죠.
따라서 outer2는 실행이 끝나버린 outer() 함수의 inner에 접근할 수 있습니다.
그런데 어떻게 실행이 끝나버린 함수의 내부에 접근할 수 있을까요?

이는 실행이 끝났어도 그것을 참조하는 대상이 하나라도 있는 경우 가비지 컬렉션의 대상이 되지 않아서입니다.
그래서 클로저를 더이상 실행하지 않을 경우 참조를 다 끊어버려 가비지 컬렉터가 수거해가게 만들면 메모리 관리를 효율적으로 할 수 있겠죠.
아무튼 이렇게 글로만 봐서는 잘 안 와닿을 수 있습니다.

2020-07-26-debug-closure/2020-07-26_1.58.25.png

해당 부분에 breakpoint를 지정합니다. breakpoint를 지정하면 해당 파일을 실행할 때 breakpoint에서 실행이 멈추게 됩니다.
breakpoint를 지정하면 우측 breakpoints 패널에 현재 지정한 breakpoint들이 나옵니다.
지정이 끝났으면 새로고침을 해봅시다.

2020-07-26-debug-closure/2020-07-26_1.59.00.png

8번 라인이 실행된 상태로 실행이 중단됩니다.
우선 콜 스택 부분을 봅시다. 처음에는 전역 컨텍스트가 실행되기 때문에 anoymous 함수가 쌓입니다. 실행 컨텍스트를 설명하는 다른 글에서는 main 함수가 쌓인다고도 하는데 anoymous 함수나 main 함수나 동일합니다. 단지 표현의 차이일 뿐입니다. (크롬은 anoymous로 표현)
전역 컨텍스트이기 때문에 스코프는 글로벌이 됩니다.

2020-07-26-debug-closure/2020-07-26_2.00.20.png

Step(빨간 박스) 버튼을 눌러 다음 실행으로 이동합니다.

2020-07-26-debug-closure/2020-07-26_2.01.52.png

outer 함수가 실행되면서 콜스택에 쌓입니다.

이때 함수가 호출된 상태이기 때문에 자바스크립트 엔진이 outer에 대한 환경 정보를 수집해서 실행 컨텍스트를 생성합니다. 환경 정보는 함수 내에 있는 모든 것(변수, 함수 선언, 스코프, this)이라 생각하면 편합니다. 그래서 scope에 count, inner, this가 생성되는 것입니다. 위에서 클로저는 선언된 당시의 정보를 담는다고 그랬죠?(개념 2번) outer 스코프 내에 있는 count와 inner, this가 바로 outer 함수의 정보라 할 수 있습니다.

참고로 count와 inner의 값이 undefined인 이유는 아직 할당 단계에 다다르지 않았기 때문입니다. 실행 컨텍스트에서 변수는 선언 단계 → 초기화 단계 → 할당 단계를 거쳐 값이 할당되게 되는데, var 키워드로 선언된 변수의 경우 선언 단계와 초기화 단계가 한번에 이루어지게 됩니다. 이 상태에서 count 변수나 inner 함수에 접근하면 undefined를 반환하는데요, 이게 그 유명한 호이스팅입니다.

잠시 딴 길로 샜는데요. Step 버튼을 눌러 다음 라인으로 가봅시다.

2020-07-26-debug-closure/2020-07-26_2.18.18.png

변수 count는 할당 단계를 거쳐 1을 할당받았습니다.

한번 더 Step 버튼을 눌러봅시다.

2020-07-26-debug-closure/2020-07-26_2.19.55.png

inner는 할당 단계를 거쳐 익명 함수(function() {return ++count;})를 할당받았습니다.
이때 outer 함수의 스코프에는 return value가 생깁니다. 이때의 return value는 inner겠죠?
return value가 함수로 나오는 이유는 inner가 익명 함수를 할당받았기 때문입니다.
다음 Step으로 가봅시다.

2020-07-26-debug-closure/2020-07-26_2.25.15.png

outer 함수의 실행이 종료되었습니다. 우측 콜 스택 패널을 보면 outer 함수가 사라진 것을 확인할 수 있습니다.

2020-07-26-debug-closure/2020-07-26_2.26.10.png

9번 라인에서 outer2()가 실행된 모습입니다. 위에서 클로저는 이미 생명 주기가 끝난 외부 함수의 변수를 참조하는 현상(개념 1번)이라고 했었습니다. outer2()는 이미 생명 주기가 끝난 outer() 함수의 변수 inner를 참조하고 있기 때문에 4번 라인으로 이동하게 됩니다.

outer2로 inner 함수를 실행한 상태이기 때문에 콜 스택에는 inner가 쌓이게 됩니다.
우측 스코프 패널을 보시면 스코프에 outer 클로저가 보입니다. 이때 count의 값은 1입니다.

2020-07-26-debug-closure/2020-07-26_2.35.36.png

전위 연산을 하고 있기 때문에 count는 1이 증가된 2로 반환됩니다.

2020-07-26-debug-closure/2020-07-26_2.36.58.png

inner() 함수가 종료되며 콜 스택에서 사라졌습니다.

2020-07-26-debug-closure/2020-07-26_2.38.10.png

그리고 콘솔에는 2가 출력이 됩니다.
10번째 줄을 실행할 차례입니다. 9번 라인을 실행했을 때와 동일합니다.

2020-07-26-debug-closure/2020-07-26_2.39.16.png

여기서 한가지 주의깊게 보아야 할 것은, 10번 라인을 실행할 때 9번 라인에서 실행한 값을 기억하고 있다는 것입니다. outer2에서 값을 참조하고 있기 때문이죠.
우측 scope 패널의 count를 보면 값이 2인 것을 확인할 수 있습니다.

2020-07-26-debug-closure/2020-07-26_2.40.36.png

전위 연산이 되어 count의 return value는 3이 됩니다.

2020-07-26-debug-closure/2020-07-26_2.41.25.png

inner() 함수가 종료되며 콜 스택에서 사라졌습니다.

2020-07-26-debug-closure/2020-07-26_2.42.14.png

마지막으로 3을 출력한 모습입니다.

이전을 하는 이유

Jekyll에서 Hexo로 블로그를 이전했다.
이전을 마음먹은 이유는 다음과 같다.

  1. Ruby가 아닌 Node 기반의 Static Site Generator를 사용하고 싶어서.
  2. Jekyll 테마들이 마음에 안 들어서. (그래서 TIL과 기술 블로그를 분리했으나… 귀찮음만 더할 뿐이었다.)
  3. 셋업이 귀찮아서.

더 자세히 말하자면, TIL 블로그에서 사용하고 있던 Just the Docs 테마가 자잘한 오류들을 뿜어내고 있었다. 메뉴들이 사라진다거나, 순서가 뒤죽박죽이 되거나, 한국어 검색 기능 등이 제대로 동작하지 않았다. Jekyll의 초기 세팅도 까다롭게 느껴졌다. 어느새부턴가 macOS에서 루비를 사용하면 permission 문제가 발생해 가상 환경에 루비를 설치해야 했다.
이런 상황이 지속되니 기술 블로그를 등한시하고 Notion에 모든 것을 기록하기 시작했다. Notion은 다 좋지만, 배출의 욕구가 해소되지 않았다. 나는 세미(?) 관종이고 검색 엔진에 내 글이 조금이라도 노출됐으면 했다.

고로 블로그 프레임워크를 바꾸기로 마음먹었다. 프레임워크를 Hexo로 결정한 건 순전히 테마가 마음에 들어서다.

Github 프로젝트 리포지토리에 Hexo 배포하기

Hexo는 문서화가 굉장히 잘 되어있으므로 공식 문서만 보면 뚝딱 블로그를 만들 수 있다. Jekyll에서 Hexo로 마이그레이션도 금방 한다. 문제는 배포인데, Jekyll과 달리 정적 파일을 generate 해주어야 한다.
그런데 배포 시 <username>.github.io로 블로그를 만들면 원본 소스 파일들을 올리지 못하게 된다.

github 문서를 보면 다음과 같은 내용이 있다.

The default publishing source for user and organization sites is the master branch. If the repository for your user or organization site has a master branch, your site will publish automatically from that branch. You cannot choose a different publishing source for user or organization sites.

<username>.github.io 같은 리포지토리는 위에서 말한 user site 에 해당된다.
<username>.github.io로 페이지를 퍼블리싱 하는 경우, 반드시 master 브랜치에 index.html 파일이 있어야 한다. 따라서 hexo generate로 생성한 배포 파일들을 master 브랜치로 푸시해야 하는데, 이렇게 하니 원본 소스의 버전 관리가 안 되는 단점이 생긴다.

하지만, 프로젝트 리포지토리에 배포를 하는 경우 docs 폴더나 gh-pages 브랜치에 배포를 하면 정적 사이트를 생성할 수 있다. 그래서 프로젝트 리포지토리를 활용하기로 했다. 프로젝트 리포지토리의 master 브랜치에는 버전 관리가 필요한 원본 소스를 푸시하고, gh-pages 브랜치에는 hexo generate 명령어로 생성한 파일들을 푸시하면 되는 것이다.
우선 github에서 blog라는 리포지토리를 생성 후 hexo 블로그를 연동한 다음, _config.yml 파일을 열어 다음과 같이 설정을 해주었다.

1
2
3
4
5
6
7
8
9
10
11
12
# url은 https://seonghui.github.io/<리포지토리 이름>으로 설정
url: https://seonghui.github.io/blog

# 루트를 '/<리포지토리 이름>/'으로 설정
root: /blog/

deploy:
type: git
# 리포지토리 연결
repo: https://github.com/Seonghui/blog
# 배포할 브랜치를 gh-pages로 설정
branch: gh-pages

이후 hexo clean && hexo deploy 명령어로 gh-pages 브랜치에에 배포를 하고, 변경된 파일을 master 브랜치에 푸시하면 끝이다. 배포가 잘 되었는지는 https://<username>.github.io/<리포지토리>에서 확인하면 된다.

만약 404 에러가 뜨는 경우, 해당 리포지토리의 settings 메뉴의 GitHub Pages의 Source를 gh-pages branch로 변경하면 된다.

example
example

Leetcode - 20

Code 1 (Javascript)

  1. ({[ 은 배열에 push 한다.
  2. ]}) 은 짝이 맞을 경우 pop을 한다. 이때 배열의 가장 마지막 요소를 체크하면 된다. 만약 짝이 맞지 않을 경우 false를 반환한다.
  3. 배열에 아무것도 없으면 짝이 맞는 거니까 true를 반환한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var isValid = function(s) {
let array = [];
for (char of s) {
switch (char) {
case ")":
if (array[array.length - 1] === "(") {
array.pop();
} else {
return false;
}
break;
case "}":
if (array[array.length - 1] === "{") {
array.pop();
} else {
return false;
}
break;
case "]":
if (array[array.length - 1] === "[") {
array.pop();
} else {
return false;
}
break;
default:
array.push(char);
}
}
return array.length ? false : true;
};

Code 2 (Javascript)

  • 미리 괄호들을 object로 선언. ([{을 키로, }])을 값으로 지정 후 짝이 맞는지를 판단하면 된다.
  • 이때 배열의 마지막 값은 stack.pop()으로 알아낸다. 키와 값이 맞지 않을 경우 false를 반환한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var isValid = function(s) {
let stack = [];
const pair = {
"(": ")",
"[": "]",
"{": "}"
};

for (let char of s) {
if (char in pair) stack.push(char);
else if (pair[stack.pop()] !== char) return false;
}

return stack.length ? false : true;
};

create-react-app으로 리액트 프로젝트를 생성해 보자. create-react-app은 리액트의 보일러 플레이트 코드를 생성하기 때문에 직접 개발 환경을 구성하지 않더라도 별도의 설정 없이 프로젝트를 바로 시작할 수 있다.

package.json 내의 script 명령을 보면 이런 게 있다.

"start": "react-scripts start",

이 코드는 node_modules./bin 폴더에 있는 react-scripts.js 파일을 실행시킨다.

react-scripts는 create-react-app의 스크립트를 모아놓은 곳이다. react-scripts start는 개발 환경을 셋업하고, 핫 리로딩이 적용된 서버를 실행시킨다.

react-scripts.js 파일을 열어보면 대략 다음과 같이 되어있다.

...생략
if (['build', 'eject', 'start', 'test'].includes(script)) {
  const result = spawn.sync(
    'node',
    nodeArgs
      .concat(require.resolve('../scripts/' + script))
      .concat(args.slice(scriptIndex + 1)),
    { stdio: 'inherit' }
  );
  ...생략
  process.exit(result.status);
}
...생략

build, eject, start, test 키워드가 있을 경우, ../scripts/ 폴더 내의 파일을 찾게 된다.

example

react-scripts start 명령어의 경우, 키워드는 start니까 ../scripts/ 폴더 내에 있는 start.js 파일을 열어보자.

...생략
const paths = require('../config/paths');

// Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
  process.exit(1);
}
...생략

checkRequiredFiles() 로 배열 내의 파일 경로가 유효한 경로인지 확인한다. checkRequiredFiles() 는 react-dev-utill 내에 있다. 만약 파일이 없거나 경로가 유효하지 확인하고, 전부 존재한다면 if문 아래에 있는 개발 서버 실행 코드를 실행시킨다.

../config/paths 내에 있는 paths.appHtml, paths.appIndexJs을 확인해보자.

paths.js 파일은 eject 이전의 경로, eject 이후의 경로 그리고 publish 이전의 경로를 나눠서 관리한다.

const moduleFileExtensions = [
  'web.mjs',
  'mjs',
  'web.js',
  'js',
  'web.ts',
  'ts',
  'web.tsx',
  'tsx',
  'json',
  'web.jsx',
  'jsx',
];

const resolveModule = (resolveFn, filePath) => {
  const extension = moduleFileExtensions.find(extension =>
    fs.existsSync(resolveFn(`${filePath}.${extension}`))
  );

  if (extension) {
    return resolveFn(`${filePath}.${extension}`);
  }

  return resolveFn(`${filePath}.js`);
};

const resolveOwn = relativePath => path.resolve(__dirname, '..', relativePath);

module.exports = {
    appHtml: resolveOwn('template/public/index.html'),
    appIndexJs: resolveModule(resolveOwn, 'template/src/index'),
};

resolveModule(resolveOwn, 'template/src/index') 인 이유는 appIndexJs가 자바스크립트 파일이 아닐 수도 있기 때문이다. (ts, jsx 등등…)

resolveOwn()을 호출하면 현재 모듈의 디렉토리 이름을 포함한 경로를 반환한다.

resolveModule()에 resolveOwn()함수와 template/src/index 를 넘기면 현재 모듈의 디렉토리 + 파일 경로 + 확장자를 합쳐 경로를 만들고 resolveOwn()를 호출하게 된다.

결론적으로는 현재 프로젝트에 있는 index.js와 index.html가 존재하는지 파악해, 경로에 파일이 존재하는 경우 개발 서버를 실행시키는 거라 할 수 있겠다.

References

  • https://stackoverflow.com/questions/41738421/how-react-js-index-js-file-contacting-index-html-for-id-references
  • https://stackoverflow.com/questions/50722133/what-exactly-is-the-react-scripts-start-command

프로그래머스 - 탐욕법(Greedy)

풀이 과정

이 문제는 O(n^2)로 풀면 시간 초과가 나오는 문제이다.

나는 이 문제를 처음 풀 때, 오름차순으로 정렬한 다음 앞에서부터 차례대로 더해갔는데 이렇게 풀면 무조건 틀린다.
아래 케이스를 보자.

1
[10,20,30,40,50,60,70,80,90], 100

제한 사항에 각 사람의 몸무게는 40kg 이상 240kg으로 되어있지만, 저건 문제를 이해하는데 사용하는 예시이기 때문에 지금은 중요하지 않다.

앞에서부터 차례대로 계산하면

1
2
3
4
5
6
7
10, 20, 30, 40
50
60
70
80
90
-> 6

6이 나오지만, 이 예제는 5가 반환되어야 한다.

1
2
3
4
5
6
10, 90
20, 80
30, 70
40, 60
50
-> 5

이렇게 가장 큰 수와 가장 작은 수를 서로 더해주면 최솟값을 구할 수 있다.

이를 구현하려면, 일단 people 배열을 오름차순으로 정렬한다.
그리고 가장 큰 수와 가장 작은 수를 더한 값이 limit보다 작거나 같으면 start와 count를 증가시킨다.

이런 식으로 start와 end가 만나는 지점까지 반복한다.
start와 end가 만나는 상황은 비교할 대상이 한개(자기자신)밖에는 없을 때이다.
그런 경우에는 무조건 혼자 타야하기 때문에 count를 증가시킨다.

가장 작은 수와 가장 큰 수를 더했을 때 limit보다 크면 가장 큰 수는 혼자 타야하기 때문에 count를 증가시킨다.

이렇게 구현하는 경우 반복문 한 번만 사용하기 때문에 시간복잡도는 O(n)이 된다.

Code (Javascript)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function solution(people, limit) {
var count = 0;
var start = 0;
var end = people.length - 1;

// 오름차순으로 정렬
people.sort(function(a, b) {
return a - b;
});

// start와 end가 만나는 지점까지 반복
// 뒤에서부터 앞으로 반복한다
for (end; end >= start; end--) {
// 가장 큰 수와 가장 작은 수를 더한 값이 limit보다 작거나 같으면
if (people[end] + people[start] <= limit) {
// start 증가
start++;
}
count++;
}

return count;
}

만만하게 보고 풀었다가 정확도 60점 나와서 당황했고, 고쳐서 풀었더니 시간 초과를 보고, 시간복잡도를 줄일 방법이 도저히 생각나지 않아 결국 구글링을 했다.. 눈물.. ㅋ

프로그래머스 - 연습 문제

풀이과정

  • progresses[0]이 100 이상일 때, 다음 원소가 100 이상일 경우 count 증감
  • count는 array에 push하고, count만큼 배열을 잘라버린다
  • 이렇게 배열에 원소가 없을 때까지 반복한다
  • count가 0 이상일 때만 array에 push해야 하는데 나는 그냥 필터로 함…

Code (Javascript)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function solution(progresses, speeds) {
var array = [];
while (progresses.length > 0) {
var count = 0;
// 개발
if (progresses[0] <= 100) {
for (var i = 0; i < progresses.length; i++) {
progresses[i] = progresses[i] + speeds[i];
}
}

// 배포
if (progresses[0] >= 100) {
for (var progress of progresses) {
if (progress >= 100) {
count += 1;
} else {
break;
}
}
}
progresses.splice(0, count);
speeds.splice(0, count);
array.push(count);
}
var answer = array.filter(el => el > 0);
return answer;
}

다른 사람의 코드

progresses[0] >= 100일 때 배열 첫 번째 요소 제거 및 count 증감, 그리고 count가 0 이상일 때만 정답 배열에 삽입했다. 깔끔하다…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function solution(progresses, speeds) {
var answer = [];

while (speeds.length > 0) {
// 개발
for (let i in speeds) {
if (progresses[i] < 100) {
progresses[i] += speeds[i];
}
}

// 배포
let deploy_count = 0;
while (progresses[0] >= 100) {
progresses.shift();
speeds.shift();
deploy_count++;
}
if (deploy_count > 0) {
answer.push(deploy_count);
}
}

return answer;
}

프로그래머스 - 연습 문제

풀이과정

  • ‘(‘ 면 count를 1 증가시키고 ‘)’면 1을 감소시킨다.
  • 만약 count가 0보다 작으면 중간에 ‘)’이 나온 경우이니 반복문을 멈춘다.
  • count가 0인 경우에는 올바른 괄호이니 true 반환.
  • count가 0이 아니고, 시작이 ‘)’이거나 끝이 ‘(‘라면 올바른 괄호가 아니니 false 반환.

Code (Python)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def solution(s):
count = 0;

for string in s:
if string == '(':
count += 1
else:
count -= 1
if count < 0:
break
if count != 0 or s[0] == ')' or s[len(s) - 1] == '(':
return False
else:
return True

어디서 많이 봤던 문제다 했더니 백준 괄호랑 똑같은 문제였네… 그때는 왜 이렇게 힘들게 풀었을까.

프로그래머스 - 스택/큐

풀이과정

  • 가격이 오르든 안 오르든 일단 1을 증가시킨다.
  • 현재 시점의 가격이 이후 가격보다 클 경우 break를 걸면 된다.
  • 마지막 시점은 무조건 0이니 정답 리스트에 0을 추가하면 된다.

Code (Python)

1
2
3
4
5
6
7
8
9
10
11
12
13
def solution(prices):
answer = []
for i in range(len(prices) - 1):
count = 0
for j in range(i + 1, len(prices)):
count += 1
if (prices[i] > prices[j]):
break

answer.append(count)

answer.append(0)
return answer