삽입 정렬(insertion sort), 자바스크립트로 작성하기

앞에서부터 순서대로 정렬하여 자신의 위치에 삽입하는 방법

삽입 정렬은 배열의 앞에서부터 시작하여 자신의 앞에 있는 모든 요소와 크기 비교를 통해 자신에게 맞는 위치에 요소를 삽입한다.

다음 그림과 같은 방식이다.

3을 [6]의 적절한 위치에 삽입하고 다음으로는 5를 [3, 6]의 적절한 위치에 삽입한다. 다음으로는 1을 [3, 5, 6]의 적절한 위치에 삽입하고 이 방식으로 삽입 작업이 반복된다.

코드는 다음과 같다.

const insertionSort = (arr) => {

  for(let i=1;i<arr.length;i++){
    const checkValue = arr[i];
    let left = i-1;

    while(left>=0&&arr[left]>checkValue){
      arr[left+1] = arr[left];
      left--;
    }
    arr[left+1] = checkValue;
  }
  return arr;
}

console.log(insertionSort([6,3,5,1,9,4,8,2,7]));

다른 메모리 공간이 필요하지 않고 안정 정렬(stable sort)이며 시간 복잡도는 O(N)이다. 단점으로 최악의 시간 복잡도는 O(N^2)가 될 수 있으며 배열이 커질수록 비효율적이다.

퀵 정렬(quick sort), 자바스크립트로 구현하기

문제를 세분화하는 분할 정복(divide and conquer)과 퀵 정렬

퀵 정렬은 하나의 중심(pivot) 값을 기준으로 큰 값과 작은 값을 분류하고 이 작업을 반복하여 정렬하는 방법이다.

그림으로 보면 다음과 같다.

첫 요소인 6을 기준으로 잡아서 이보다 작은 수를 left, 큰 수를 right로 분류하고 이 작업을 세분화해서 반복 진행하면 최종적으로 정렬된 배열을 반환할 수 있다.

const quickSort = (arr) => {
  if(arr.length<=1){
    return arr;
  }

  const pivot = arr[0];
  const left = [];
  const right = [];

  for(let i=1; i<arr.length; i++){
    if(arr[i]<pivot){
      left.push(arr[i]);
    }else{
      right.push(arr[i]);
    }
  }
  return [...quick(left), pivot, ...quick(right)]
}

console.log(quickSort([6,3,5,1,9,4,8,2,7]));

퀵 정렬은 최악의 경우를 제외하면 O(NlogN)의 시간 복잡도를 갖는 속도가 장점이지만 단점은 최악의 경우(이미 정렬된 배열) 시간 복잡도가 O(N^2)로 대폭 증가하고 불안정 정렬(unstable sort, 동일한 값에 대해서는 순서가 바뀔 수 있음)이라는 점이다.

for문에서 setTimeout과 console 사용하기(var,let)

for문에서 setTimeout을 의도대로 사용하기

다음 두 실행문은 각각 변수로 let과 var를 사용한 점이 다릅니다. 그렇다면 출력되는 결과는 어떨까요?

for (let i=0; i<5; i++){
  setTimeout(()=>{console.log(i)},1000);
} // 0, 1, 2, 3, 4

for(var i=0; i<5; i++){
  setTimeout(()=>{console.log(i)},1000);
} // 5, 5, 5, 5, 5

let을 사용하면 0, 1, 2, 3, 4가 약 1초 뒤 한번에 출력되는 것을 확인할 수 있고 var를 사용하면 5가 다섯 번 출력되는 것을 확인할 수 있습니다.

우선 var는 왜 5를 다섯 번 출력할까요?

이는 이벤트 루프의 처리에 따라 setTimeout 메서드가 호출 스택에서 백그라운드를 거쳐 태스크 큐로 이동했다가 다시 콜 스택으로 돌아오는 동안 for 문이 모두 종료(콜 스택이 비어 있어야 태스크 큐에서 콜 스택으로 이동)되어 출력할 데이터는 i=5인 클로저를 참조하기 때문입니다.

그렇다면 var와 달리 let은 왜 순서대로 숫자가 출력될까요?

이는 스코프와 관련이 있습니다.

var는 함수 스코프를 가지므로 for 루프마다 같은 참조를 바인딩하고 let은 블록 스코프이므로 for 루프마다 새로운 참조를 바인딩하게 됩니다.

그렇다면 var를 사용하더라도 for 루프마다 새로운 참조를 바인딩하면 결과가 달라지지 않을까요?

for(var i=0; i<5; i++){
  setTimeout(console.log.bind(console,i), 1000);
}

for(var i=0; i<5; i++){
  setTimeout(console.log, 1000,i);
}

또는 즉시실행함수(IIFE)사용해도 됩니다.

for (var i=0; i<5; i++) {
  (i => setTimeout(() => console.log(i), 1000))(i);
}

그러나 var는 이제 사용을 권장하지 않으며 let으로 같은 효과를 낼 수 있으므로 구조와 작동 원리 차원에서만 알아두면 좋을 것 같습니다.


그렇다면 잠시 앞으로 돌아가서 왜 setTimeout은 먼 길을 돌아서 실행되는 걸까요?

이벤트 루프가 비동기나 콜백 함수를 모두 백그라운드로 전달하여 작업 효율을 높이려고 하기 때문입니다.

이 흐름을 확인하는 방법은 다음 코드를 통해 확인할 수 있습니다.

for (let i=0; i<3; i++){
  console.log('start');
  setTimeout(()=>{console.log(i)},0);
  console.log('end');
}

이처럼 setTimeout에 0초를 설정해도 작업이 뒤로 밀리는 것을 알 수 있습니다(0이라고 해도 실제로는 4ms 소요).

그럼 i초마다 i를 반환하는 함수를 생성해보겠습니다.

//for 문 사용
for (let i=0; i<5; i++) {
  setTimeout(() => console.log(i), i*1000);
}

// async, await 사용
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
(async function loop() {
    for (let i=0; i<5; i++) {
        await delay(1000);
        console.log(i);
    }
})();

//promise 사용
for(let i=0; i<5; i++){
   new Promise((resolve, reject)=>{
      setTimeout(()=>{     
       	resolve(i);
      }, i*1000);
   }).then((data)=>{
      console.log(data);
   })
}

정규표현식 \b \B의 단어 경계(word boundary)

단어 경계는 \w와 \W의 그 사이를 찾는 작업

정규표현식(regular expression)을 사용해 일치(match)하는 단어나 기호를 찾는 것은 매우 간단합니다.

My name is ABC.

ABC는 정규표현식 /ABC/를 사용하면 간단하게 찾을 수 있습니다.

그러나 다음 문자열에서 to를 찾기 위해 /to/를 사용하면 의도한 결과를 벗어납니다.

She told me not to go.

to가 포함된 모든 단어를 검색하는데요.

이 때 /b를 사용해 경계를 지정하면 원하는 부분만 찾을 수 있습니다.

그렇다면 /b의 정확한 기능은 무엇일까요?


1. /b의 기능

/b는 문자(\w에 해당하는 문자, a-z,A-Z,0-9,_)와 비(非)문자(\W에 해당하는 경우) 부분의 경계를 의미합니다.

이 부분의 이해가 어려울 수도 있는데요.

예를 들어 보겠습니다.

문자(\w로 표현): abcdefzhijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_
비문자(W로 표현) : 위 문자를 제외한 모든 기호

위와 같은 정의를 갖는 문자와 비문자 사이의 경계를 나타냅니다.

참고로 문자의 \w는 w가 소문자, 비문자의 \W는 대문자입니다.

띄워쓰기는 비문자에 해당하므로 /b를 사용해 단어의 경계를 정하고 원하는 결과를 만들 수 있는 것입니다.

그럼 다음 상황을 확인해보죠.

/\ba/g

위 표현식을 사용했을 때 다음 문자열에서 찾을 수 있는 결과는 무엇일까요?

-a

‘ a’

_a

aa

star

결과는 다음과 같으며 한 줄로 표현하겠습니다.

aa‘ _a aa star

-a에서 a가 선택되는 이유는 -가 비문자이며 a가 문자이므로 \b는 두 사이를 가리키기 때문입니다.

‘ a’에서도 a 앞의 띄워쓰기는 비문자, a는 문자이므로 \b는 두 사이를 가리킵니다.

_a에서 a가 선택되지 않는 이유는 _ 기호는 문자로 취급하며 a도 문자이기 때문입니다.(\w의 정의)

aa에서 a가 선택된 이유는 문자열 제일 앞 또는 제일 뒤가 문자면 \b의 조건에 해당한다고 정의하기 때문입니다.

따라서 위의 문장에서 to만 찾으려면 다음과 같은 표현식을 사용하면 됩니다.

/\bto\b/g

She told me not to go.

참고로 \b, \B는 경계만을 가리킬 뿐 실제 문자와 일치하는 것은 아니므로 결과에는 포함되지 않습니다.


2. /B의 기능

그렇다면 대문자로 표현되는 \b의 반대 기능 \B의 정의는 무엇일까요?

바로 비문자와 비문자 사이의 경계, 또는 문자와 문자 사이의 경계를 의미합니다.

!-! abc와 같은 비문자 사이에서 비문자(-)를 찾거나 문자 사이에서 문자(b)를 찾을 수 있습니다.

/\B-\B/g

!! ‘


3. 활용

\b, \B는 주로 단어 검색에 유용합니다.

이 기능의 명칭이 단어 경계(word boundary)이므로 용도는 명확합니다.

문자열 내 의도하지 않은 기호의 탐색이나 카멜케이스(CamelCase)등과 관련된 작업에도 사용할 수 있습니다.



CSS, ::before, ::after 간단하게 알아보기

가상 요소(pseudo element) ::before, ::after의 역할

pseudo(슈도)는 [가짜의, 허위의]라는 뜻을 갖고 있습니다.

그렇다면 이 가상 요소가 존재하는 의미는 무엇일까요?

가장 큰 목적은 HTML에 존재하지 않는 요소를 CSS가 임의로 생성해서 스타일링하는 것입니다.

모양은 ::before 또는 ::after이며, 콜론이 두 개(!)가 붙는 것이 가상 요소, 콜론 하나가 가상 클래스입니다(최신 명세).

before와 after의 의미에서 알 수 있듯이 요소의 앞과 뒤에 가상 요소를 생성하여 HTML에 존재하지 않는 요소를 CSS로 생성합니다.

예를 들면 다음과 같습니다.

<h3> - 오늘 할 일</h3> 

위 태그는 다음과 같은 화면을 렌더링합니다.

텍스트에 ‘-‘가 포함되어 있으므로 마우스로 드래그하면 ‘-‘도 함께 텍스트로 선택이 됩니다.

그러나 가상 요소를 사용하면 다음과 같습니다.

‘-‘는 텍스트가 아닌 가상 요소에서 다루고 있으므로 텍스트로 선택되지 않습니다.

// css
.list::before{
  content:'-'
}


// html
<h3 class="list">오늘 할 일</h3>

‘-‘가 h3 태그에는 텍스트로 존재하지 않지만 ::before를 통해 content로 추가해 주었으므로 외형은 위와 같지만 속은 다른 방식이 됩니다.

이 가상 요소를 사용하면 세부적인 스타일링이 가능해집니다.

// css
.list::before{
  content:'';
  display:inline-block;
  width:20px;
  height:20px;
  background:gold;
}

.list::after{
  content:'?'
}


// html
<h3 class="list">
 오늘 할 일
</h3>

<h3 class="list">
내일 할 일
</h3>

before로 요소의 앞, after로 요소의 뒤를 설정했으며 content에는 문자 이외에도 이모지나 이미지(url(주소)) 등을 사용할 수도 있습니다.

이외에도 ::first-line, ::selection, ::placeholder 등이 있습니다.

추가로 form과 이미지에는 가상 요소가 적용되지 않습니다.

타입스크립트와 제네릭의 이해(Typescript and Generics)

클래스, 함수, 인터페이스, 타입의 재활용을 가능하게 만드는 제네릭

타입스크립트는 변수나 파라미터 등에 데이터 타입을 명시하여 코드 작성 단계에서 오류를 확인할 수 있고 미리 타입을 결정하여 실행 속도가 빠르다는 장점이 있습니다.

그러나 매번 타입을 지정하는 것은 분명 번거로운 일이므로 타입스크립트의 단점으로 꼽힙니다.

더욱이 똑같은 기능을 하는 함수지만 상황에 따라 다른 데이터 타입을 전달받는 함수는 매번 타입에 맞는 함수를 생성해야 합니다.

그렇다면 데이터를 전달할 때 데이터 타입 정보도 함께 전달해서 함수에게 알려주면 어떨까요?

전달하는 데이터의 타입 정보를 함께 전달하여 선언 시점이 아닌 생성 시점에 타입 정보도 변수처럼 변경할 수 있도록 하는 기능이 바로 제네릭입니다.

제네릭은 오래 전 C++부터 사용되어 왔는데요.

제네릭 함수의 모양새는 다음과 같습니다.

//함수형
const thisGeneric = <T>(value: T):T => {
     return value;
}

//클래스형
function thisGeneric2<T>(value: T) : T {
     return value;
}

const myVariable = thisGeneric<string>('my generic');
const myVariable2 = thisGeneric2('my generic2');  //< >생략 시 타입 추론

< >를 사용해 제네릭을 표현하고 안의 T는 정해진 것이 아니므로 다른 글자를 사용해도 되지만 관습적으로 T(type의 t)를 많이 사용합니다. 만약 두 개의 제네릭 정보를 받는 경우에는 <T, U>와 같이 표기합니다.

많이 사용되는 알파벳의 의미는 다음과 같습니다.

T : type
E : element
K : key
V : value

어떤 타입을 전달해도 사용이 가능한 any도 제네릭으로 볼 수 있지만 any는 타입을 체크하지 않으므로 전달받은 데이터의 타입을 알 수 없고 리턴 시에도 타입 정보를 반환하지 않습니다. 그러나 제네릭은 전달받은 타입을 확인하고 정보를 함께 반환할 수 있으며 세부적인 제한을 둘 수도 있습니다.

제네릭 인터페이스도 사용 방법은 제네릭 함수와 동일합니다.

interface ValueInterface<T> { 
   value : T
}

let myValue : ValueInterface<string> {
  value : 'data'
}

그렇다면 or의 연산자 | 를 사용하여 여러 타입을 지정할 수 있는 유니온 타입과는 어떤 차이가 있을까?

유니온은 | 로 지정한 여러 타입의 공통 메서드만 사용할 수 있으며 리턴 데이터의 타입도 하나가 아닌 유니온 타입이 반환됩니다.

function myUnion(val:number|string){
   return val;
}

const test = myUnion('string');
console.log(test.length); //에러 (length는 number와 string의 공통 메서드가 아니므로)

유사한 기능이 존재하지만 제네릭이 갖는 장점이 명확합니다.

제네릭을 사용하면 깔끔하게 코드를 줄이면서도 코드의 재활용이 가능해 매우 유용합니다.

제네릭을 사용한 오픈 소스들이 매우 많으므로 제네릭을 익혀두면 다른 고급 개발자들의 코드를 살펴볼 때도 큰 도움이 될 것 같습니다.

옵셔널 체이닝(optional chaining)의 유용함(javascript)

ES2020, typescript3.7, Babel 7.8.0, CRA 3.3.0 Node.js 14 지원

옵셔널 체이닝의 연산자는 ?. 입니다.

최신 버전(ES2020)에서 사용 가능한 기능으로 객체 체인 속성의 존재 여부를 확인합니다.

애플워치 구매를 고민하는 친구에게 하던 말처럼 ‘있으면 모르겠지만 없으면 너무 불편한’ (결론은 익숙해지면 아주 유용한) 기능의 연산자입니다.

자바스크립트로 객체 내부의 속성에 접근할 때, 존재하지 않는 객체(undefined)의 내부 속성에 접근하면 에러가 발생합니다.

const myObject = {
   myOne:{
      name:'one'
   }
}

console.log(myObject.myTwo); // undefined
console.log(myObject.myTwo.name); // error

위와 같이 존재하는 객체의 하부 속성이 존재하지 않으면 undefined를 반환하지만 존재하지 않는 객체의 하부 속성에 접근하면 에러가 발생합니다.

객체와 속성이 존재하지 않는 상황을 가정하여 코드를 보수적으로 짜는 것은 습관화되어 있지 않으면 놓치기 쉽습니다.


1. 옵셔널 체이닝 사용

앞의 에러를 방지하기 위해서는 다음과 같이 먼저 객체의 존재 여부를 확인 후 존재하는 경우에만 속성에 접근하는 방법을 사용할텐데요.

const myObject = {
   myOne:{
      name:'one'
   }
}

console.log(myObject.myTwo&&myObject.myTwo.name); // undefined

&&를 사용해 존재 여부를 먼저 체크합니다.

A && B를 예로 들어,

A = true면 return B,
A = false면 return A

와 같습니다.

false의 조건은 다음과 같습니다.

  • undefined
  • null
  • NaN
  • 0
  • empty string
  • “”

위 코드는 존재 시 name 속성을 반환하지만 속성이 존재하지 않더라도 undefined가 반환되므로 에러가 발생하지 않습니다.

이 체크 부분의 코드를 간략하게 만들어 코드의 가독성을 높일 수 있는 것이 옵셔널 체이닝입니다.

const myObject = {
   myOne:{
      name:'one'
   }
}
console.log(myObject.myTwo?.name); // undefined

앞의 코드와 완전히 동일한 결과가 발생합니다.

굉장히 단순하고 직관적인 것이 매력적입니다.

물론 속성 이외에 메서드나 함수 호출에도 사용할 수 있습니다.

const myObject = {
   show(){
     console.log('show');
   }
}
console.log(myObject.show?.()); // show

console.log(myObject.exist?.()); // undefined
console.log(myObject.exist()); // error

호출한 메서드가 존재하지 않으면 옵셔널 체이닝은 undefined를 반환하지만 사용하지 않으면 에러가 발생합니다.

이와 같이 옵셔널 체이닝을 사용하면 null, undefined로 발생하는 에러를 많이 때려 잡을 수 있습니다.

그러나 과유불급을 잊지 말아야 합니다.

여기 저기 남발하면 에러 발생은 현저하게 줄어들겠지만 정작 문제가 발생해야 하는 부분에서도 문제가 발생하지 않아

디버깅이 어려워지는 문제가 발생하므로 꼭 필요한 부분에만 사용하는 것이 좋습니다.

map, reduce, filter 서로 대체가 가능할까 (javascript)

자바스크립트 배열을 처리하는 메서드 map, reduce, filter

자바스크립트에서 배열의 요소에 접근하는 대표 메서드인 map, reduce, filter는 개발자라면 필수적으로 그리고 좀 더 상세히 알고 가면 좋을 것 같습니다.

각각의 특징에 대해 이야기하면 대부분은 아마도

  • map -> 모든 요소에 한 번씩 접근하기
  • reduce -> 모든 요소를 하나로 합치기
  • filter -> 모든 요소에서 원하는 것만 골라내기

정도의 개념을 갖고 있을 것 같습니다.

굳이 위 메서드가 아니라 forEach 문 등 다른 방식을 사용해도 같은 로직의 구현이 가능한데요.

그렇다면 위 메서드끼리도 서로 변경하여 같은 결과를 반환하도록 사용이 가능할까요?

각 메서드의 기능을 살펴보면서 알아보도록 하겠습니다.


1. map

먼저 메서드는 위와 같으며 *가 붙어있는 파라미터는 생략할 수 있습니다.

인덱스는 현재값의 인덱스, 배열은 map이 순회하는 배열 array를 의미합니다.

이제 map을 사용하는 가장 기본적인 코드를 확인해 보겠습니다.

const myArray = [10, 20, 30, 40, 50];

const newArray = myArray.map((value) => { return value });

console.log(newArray);  // [10, 20, 30, 40, 50]
console.log(myArray===newArray); //false

map은 해당 배열 자체를 바꾸는 것이 아니므로 리턴값을 사용하기 위해서는 다른 변수에 넣어 주어야 합니다.

파라미터에서 index는 현재 접근한 요소의 index이며 array는 순회하는 배열 전체를 전달합니다.

const myArray = [10, 20, 30, 40, 50];

const newArray = myArray.map((value, index, array) => {
   if(index%2){
     return value-array[1];   
   }else{
     return value-array[0];
   }                              
});

console.log(newArray);

다음 코드는 자주 접하는 실수 중 하나로 코드의 동작 여부를 생각해 볼 필요가 있습니다.

const myData = {10, 20, 30, 40, 50};

const newData = myData.map((value) => {return value+1});

console.log(newData);

위 코드는 에러가 발생합니다.

바로 map의 특성 때문인데요. map은 배열에서 동작하는 메서드이며 객체 object에서는 동작하지 않습니다.

하지만 다음과 같이 배열 내부의 object는 접근 가능합니다.

const myData = [{name:'chamchi', age:3},{name:'ggongchi', age:2},{name:'myulchi', age:1}];

const newData = myData.map((value) => value.name );

console.log(newData); //['chamchi', 'ggongchi', 'myulchi']

그렇다면 map을 사용해 reduce나 filter 메서드와 같은 로직을 구현할 수 있을까요?

다음 코드를 통해 확인할 수 있을 것 같습니다.

const myArray = [1, 2, 3, 4, 5];

const newArray = myArray.map((value) => {
   if(value%2){
     return value;   
   }                            
});

console.log(newArray); //[1, undefined, 3, undefined, 5]

홀수의 요소만 반환하고자 위와 같이 작성하더라도 map은 모든 요소를 반환합니다.

짝수 부분은 리턴값이 정의되지 않아 undefined로 반환된 것을 확인할 수 있습니다.

따라서 map은 reduce, filter의 기능은 구현할 수 없을 것 같습니다.


2. reduce

먼저 메서드는 위와 같으며 *가 붙어있는 파라미터는 생략할 수 있습니다.

누적값은 배열을 순회하면서 작업의 처리 결과를 누적하는 값으로 순회가 종료되면 최종 리턴값이 됩니다.

reduce의 예시로 모든 요소를 더하는 예시가 많다보니 용도는 전체 요소 더하거나 빼서 하나로 만들기라고

알고 있을 수 있지만 누적값 설정을 통해 다양한 방식으로 활용이 가능합니다.

인덱스는 현재값의 인덱스, 배열은 reduce가 순회하는 array 배열을 의미합니다.

마지막에 있는 시작값은 누적값의 처음 기본값을 전달합니다.

생략하면 0이 기본값이 됩니다.

const myArray = [10, 20, 30, 40, 50];
const newArray = myArray.reduce((acc, value) => { return acc + value });

console.log(newArray); // 150

시작값이 생략되었으므로 acc의 기본값은 0이 됩니다.

그럼 초기값을 설정한 샘플을 보겠습니다.

const myArray = [10, 20, 30, 40, 50];
const newArray = myArray.reduce((acc, value) => { return acc + value },100);

console.log(newArray); // 250

이를 응용하여 초기값을 설정하면 다양한 결과를 만들 수 있습니다.

먼저 배열을 다른 배열로 복사하는 기능을 구현해 보겠습니다.

const myArray = [10, 20, 30, 40, 50];
const newArray = myArray.reduce((acc, value) => { 
     acc.push(value);
     return acc; 
 },[]);

console.log(newArray); // [10, 20, 30, 40, 50]

이것으로 map과 똑같은 로직의 구현이 가능합니다.

그렇다면 filter를 구현해 보겠습니다.

const myArray = [10, 20, 30, 40, 50];
const newArray = myArray.reduce((acc, value, index) => { 
     if(index%2){
       acc.push(value);
     }
     return acc; 
 },[]);

console.log(newArray); // [20, 40]

코드를 통해 확인해보니 reduce는 초기값 설정을 통해 map이나 filter와 같은 로직의 구현이 가능합니다.


3. filter

filter는 말 그대로 배열의 요소를 필터링하여 조건이 참이 되는 요소만 반환합니다.

const myArray = [10, 20, 30, 40, 50];

const newArray = myArray.filter((value) => { 
    return value%20 === 0
 });

console.log(newArray); // [20, 40]

filter는 필터링 기능만 갖고 있어 map이나 reduce의 로직을 만들긴 어려울 것 같습니다.



map과 filter는 용도가 조금 더 특정되어 있다보니 다른 메서드의 로직을 그대로 구현하기는 어렵지만 reduce는 초기값을 통해 다양한 로직의 구현이 가능해 다재다능한 메서드인 것 같습니다.

맥 OS(M1)에서 mongoDB 설치(zsh: command not found: mongo 해결)

zsh에 환경변수 설정하기

brew가 설치되어 있는 상황에서 진행합니다.

혹시 설치가 되어 있지 않은 경우에는 ‘M1 homebrew’를 검색하면 많은 안내 자료가 나오므로 참고하시면 됩니다.

먼저 설치 가능한 리스트를 확인합니다.

$ brew search mongodb

위 명령어를 실행하면 다음과 같은 리스트를 확인할 수 있습니다.

tap 명령어를 사용하면 더 많은 리스트를 확인할 수 있습니다.

$ brew tap mongodb/brew

그리고 다시 brew search mongodb를 실행하면 더 확장된 것을 볼 수 있습니다.

여기서 원하는 버전을 선택해서 설치를 진행하면 됩니다.

4.2 버전을 설치하도록 해보겠습니다.

$ brew install mongodb-community@4.2

설치가 완료되었으니 먼저 start로 몽고DB를 시작해 보겠습니다.

$ brew services start mongodb/brew/mongodb-community@4.2

Successfully started 메시지가 나오면 성공입니다.

이제 DB에 접속하기 위해 mongo를 입력해 보겠습니다.

커맨드를 찾을 수 없다는 에러가 나오는데요.

위를 살펴보면 If you need to have~~~~~ 부분에서 처음 시작하는 자는 다음 명령어를 실행하라고 나옵니다.

$ echo 'export PATH="/opt/homebrew/opt/mongodb-community@4.2/bin:$PATH"' >> ~/.zshrc

실행하고 다시 mongo를 실행해도 같은 에러가 발생하는 경우가 있습니다.

이 때는 환경변수 설정만 하고 적용이 안된 상태라서 발생하는 문제인데요.

환경변수를 적용하기 위해 다음 명령어를 실행하면 해결됩니다.

$ source ~/.zshrc

그리고 실행하면 다음과 같이 접속되는 것을 확인할 수 있습니다.

만약 이 환경변수를 직접 수정하고 싶은 경우에는 에디터를 사용해 ~/.zshrc를 열면 됩니다.

vi 에디터를 사용해 zsh(Z shell)의 환경변수 파일을 열어보겠습니다.

$ vi ~/.zshrc

앞에서 설정한 path가 들어있는 것을 볼 수 있습니다.

여기에 원하는 환경변수를 입력하거나 수정하고 저장(ESC + :wq + ENTER)해주시면 됩니다.

그리고 꼭! source ~/.zshrc를 사용해 변경을 적용을 해줘야 환경변수가 작동하므로 참고해주세요!

자바스크립트 배열+반복문의 속도 테스트(map, for, while, for…in, for…of)

반복문의 배열 작업 속도를 확인하기

배열 관련하여 각 요소별로 순회하는 반복문 작업을 할 때 가장 익숙한 방법을 사용하거나 때로는 가장 먼저 떠오르는 방법을 사용하기도 합니다.

배열의 크기가 작으면 상관이 없지만 배열의 크기가 커지면 작은 차이가 큰 차이를 만들어낼 수 있습니다.

문득 연산 속도가 가장 빠른 반복문이 무엇일까라는 궁금증이 생겨 간단하게 하나의 배열 데이터를 다른 배열로 옮기는 연산을 통해 속도를 테스트해보고자 합니다.

크기에 상관없이 반복문 별로 같은 효율을 보여주리라 생각했지만 배열의 크기에 따라 각각 다른 속도를 보여주는 점이 흥미로웠습니다.


1. 테스트 방법

먼저 일정 크기를 갖는 배열 array을 생성합니다.

console.time 메서드를 사용해 이 배열의 데이터를 다른 배열로 복사하는 작업을 진행하고 이 작업 시간을 측정합니다.

테스트는 map, for, while, for…in, for…of의 다섯 가지 반복문을 사용합니다.

// 배열 데이터 생성
const array = [];
for(let i=0;i<10000;i++){
  array.push(i);
}

// 데이터가 복사될 배열
let arraymap = [];

console.time('map');  //타이머 시작
arraymap = array.map(list=>list);
console.timeEnd('map'); //타이머 종료

위와 같은 방식으로 3회씩 테스트를 진행하며 배열의 크기를 조절하여 반복문 별로 상대적인 작업 속도의 변화를 확인합니다.


2. 테스트

먼저 배열의 크기가 아주 작을 때는 모두 큰 차이를 보이지 않습니다.

다음은 배열의 크기가 100일 때 결과입니다.

작업을 세 번 진행한 결과 map과 for…of가 엎치락뒤치락하지만 항상 가장 빠른 속도를 보여줍니다.

for, while, for…in도 세번 모두 결과가 엎치락뒤치락하지만 위의 두 반복문보단 느린 속도를 보여줍니다.

그럼 배열의 크기를 1000으로 올려보겠습니다.

크기가 1000일 때는 대체적으로 비슷한 속도를 보여줍니다.

for…in만 다른 반복문보다 느린 것이 확인됩니다.

이제 확연하게 유의미한 차이를 느낄 수 있는 크기인 10000으로 올려보겠습니다.

10000에서는 map이 가장 우수한 속도를 보여줍니다.

눈에 띄는 부분은 작은 사이즈에서는 map과 대등한 속도를 보이던 for…of가 여기서는 가장 느린 속도를 기록합니다.

또한 for…in은 오히려 for…of보다 빠른 속도를 보여줍니다.

그럼 1000000으로 확인해 보겠습니다.

배열이 매우 커지니 작업 속도는 for문이 가장 준수합니다.

map, while도 큰 차이를 보이지 않고 준수한 성능을 보여줍니다.

for…of는 확연하게 조금 느린 속도를 보여주고 for…in은 이제 인사를 해야할 것 같습니다.

그럼 마지막으로 10000000(천만)으로 테스트를 해보겠습니다.

세 번 테스트한 결과 모두 for 문이 가장 빠른 속도를 보여주었으며, map, while도 의미있는 속도를 보여줍니다.

확실히 for…of는 위의 세 반복문보다 느린 속도를 보여주었으며 for…in은 10배가 넘는 느린 속도의 차이를 보여줍니다.


3. 결과 정리

배열 테이터의 크기가 작을 때는 의미있는 차이가 없지만 배열이 커질수록 map, for, while의 속도가 우수하고 for…in, for…of는 속도 저하를 보이는 결과를 나타냅니다.

어디까지나 단순한 작업인 배열 데이터 복사 기능만을 사용한 테스트이므로 작업 내용에 따라 다른 결과를 보일 가능성이 있어 단순 참고용으로 삼으면 좋을 것 같습니다.

다음은 테스트에 사용한 코드 전체입니다.

console.log('-------------start----------------')

const array = [];

for(let i=0;i<10000;i++){  //크기 변경
	array.push(i);
}

console.log("arraysize:"+array.length);
console.log('--------------------------------');

let arraymap = [];

console.log('arraymap start:'+arraymap.length);
console.time('map');

arraymap = array.map(list=>list);

console.log('arraymap end:'+arraymap.length);
console.timeEnd('map');
console.log('--------------------------------');


let arrayfor = [];
console.log('arrayfor start:'+arrayfor.length);
console.time('for');

for(let i=0; i<array.length;i++){
	arrayfor.push(array[i]);
};

console.log('arrayfor end:'+arrayfor.length);
console.timeEnd('for');
console.log('--------------------------------');

let arraywhile = [];
let whilenum = 0;
console.log('arraywhile start:'+arraywhile.length);
console.time('while');

while(whilenum<array.length){
	arraywhile.push(array[whilenum]);
	whilenum++;
};

console.log('arraywhile end:'+arraywhile.length);
console.timeEnd('while');
console.log('--------------------------------');

const arrayforof = [];
console.log('arrayforof start:'+arrayforof.length);
console.time('forof');

for(const num of array){
	arrayforof.push(num)
};

console.log('arrayfor end:'+arrayforof.length);
console.timeEnd('forof');
console.log('--------------------------------');

let arrayforin = [];
console.log('arrayforin start:'+arrayforin.length);
console.time('forin');

for(const num in array){
	arrayforin.push(array[num]);
};

console.log('arrayforin end:'+arrayforin.length);
console.timeEnd('forin');


console.log('---------------end-----------------');