JS에서 출력은 쉽다. console.log
문제는 입력. 개인적으로는 이것을 활용하는게 제일 좋아보인다.
const stdin = require("fs")
.readFileSync("/dev/stdin")
.toString()
.trimEnd()
.split("\n");
const input = (() => {
let line = 0;
return () => stdin[line++];
})();
자 stdin
은… 이제 하나의 array다.
input
은, 함수인데, lexical environment를 활용해 (closure) line
에 접근이 가능한 함수이며, 호출할때마다 첫번째 → 두번째 줄 이렇게 계속해서 읽어나가는 것이 가능하다. 오호.
구체적인 원리는 다음과 같다.
require
: 어느 모듈을 사용할지 명시하는데 쓰임. 간단하게 얘기하자면, 외부에서 사용할 것을 허용한걸 모은 module.exports
이라는 것을 반환하는 녀석이다. 이게 뭔가 특별한 자료구조인가? 라고 생각할 수 있는데 사실 해당 모듈의 attribute, 정확히는 hash라고 생각을 하면 된다. 원본 모듈이 exports
에다가 이것 저것을 넣을 경우, require
은 이를 인식해서 실제로 해당 module의 exports
라는 해시를 반환하게 된다. 그래서 만약 딱 하나의 기능만 사용할거면 저렇게 require
에서 반환된 것을 그대로 chaining에서 사용하는 것도 가능하다. 다만 일반적으론 해당 모듈의 여러 공유하고 싶은 요소들을 사용하고 싶을테니 따로 저장을 한다음에 그걸 계속 참조하지요. 좀 자세히 설명한 글은 이거 참고 참고로 여기서 사용한 fs
모듈은 **node.js에서 제공하는 모듈이지 JS의 기본 모듈 같은 것이 아니다.** 그래서 node로 실행을 해야만 제대로 인식이 가능.readFileSync
: 파일을 읽는건데 동기 작업을 거치는 것이다. 사실 대응되는 것으로 readFile
이 있는데 얘는 비동기다. 그러면 정확히 무슨 차이가 있는건가? 비동기인 readFile
을 호출시 File IO가 진행되는 동안 코드가 멈추지 않고 그대로 계속 진행을 한다. 그러다가 File IO가 완료가 되면, readFile
에서 parameter 형태로 준 callback function이 그제서야 실행이 된다. 반면 readFileSync
는 File IO가 완료될 때까지 코드가 멈춘다. 그 외 기능은 둘이 별 차이가 없는데 자세한건 documentation을 봐도 된다. 다만 여기선 별로 의미는 없음.toString()
: String
type으로 변환을 한다. 사실 doc을 보면 String
이나 Buffer
을 반환한다고 하는데… 기본이 Buffer object이라고 한다. 왜냐하면 인코딩을 읽은 사람이 모를 수 있기 때문에 일단 binary data 형태로 읽는 것이라고. 하지만 우리는 인코딩이 utf-8
인것을 아니까 toString()
을 사용하면 된다.trimEnd
: String의 끝부분 공백을 trimming. trim
이랑 다르게 양측이 아닌 끝부분만 한다.split
: 뭐… 알다시피 해당 delimiter을 기준으로 나눠서 배열을 형성하는 것이다.input
의 경우 원리는 다음과 같다.
일단 앞에서 봤듯이 stdin
은 결국 각 줄에 해당하는 입력을 가지는 배열이 된다.
그럼 input
자체는 무엇을 나타내는가? 일단 안의 expression이 뭐냐면 Immediately Invoked Function Expression, 줄여서 IIFE라고 한다.
저게 뭐냐면 정의가 된 직후, 즉시 실행이 되는 function expression을 말한다.
MDN documentation을 보면 알겠지만 해당 함수의 정의 방법은 여러가지가 있다. 하지만 공통점이 있는데, 바로 함수 정의 부분이 ()
로 둘러싸여 있다는 점, 그리고 맨 뒤에 ()
이 있다는 점이다.
마지막 괄호 부분은 해당 함수를 호출하겠다는 것을 뜻한다. 그러면 처음의 괄호는? 해당 함수 정의 부분에 외부 변수가 사용되는 것을 방지하고, 본인이 외부 변수의 정의를 수정하는 것도 방지하는데 사용된다. 이것을 grouping operator이라고 하는데… 사실 그냥 우선순위를 부여하는 그 괄호를 간지나게 하는 말이다.
그래서 input
은 도대체 뭘 나타내냐고요? 뭐 IIFE가 실행이 되었으니, 해당 FE의 반환값을 보유하게 되며, 이는 위에 보시다시피 하나의 함수다.
lexical environment에 대해 이미 알고 있을 것이다. 이 때문에 input
이라는 녀석이 보유하는 함수는 본인만의 변수, line
이라는 녀석을 가진다.
이제 input
을 함수 형태로 호출하면 저 return
부분이 실행이 되어가지고 stdin
의 line
번째 항목을 읽고 line
을 increment한다.
이 line은 함수에 종속되어 있는 변수이다 보니까 (outer 형태로) 호출 후 line
이 늘어난 것이 유지되고, 그래서 순차적으로 읽는 것이 가능해짐 (호출마다)
첫번째 방식처럼 작성을 하면 input
이라는 것을 호출할 때마다, 입력값 줄 하나를 읽는 것이 가능하다.
다른 방법들도 많이 검색해서 알아봤는데 이 방법이 제일 편한 것 같다. 다만 이건 node.js
가 필수라...
다른 방법들은 어떻게 동작하는지에 대한 글은... 생각나면 쓸 생각이다.