본문 바로가기
Web/Javascript

Data structures & Algorithms with JavaScripts 스터디 3~4일차

by sjwdev 2021. 5. 2.

210430 금요일 3일차

# 1~2일차 복습

- 자바스크립트에서 모든 함수의 파라미터는 값으로 전달되며(call by value) 레퍼런스 전용 파라미터는 없다. 하지만 배열처럼 객체 레퍼런스를 함수 파라미터로 사용할 때는 레퍼런스로 전달된다.

 

- JS는 기본 타입(number, string, boolean, null, undefined)을 제외한 모든 값이 '객체'이다. 따라서 배열, 함수, 정규표현식 등 모두 객체로 표현된다. 이것은 객체의 모든 연산이 실제 값이 아닌 참조값으로 처리되기 때문이다.

 

- 기본 타입과 참조 타입의 경우는 함수 호출 방식도 다르다. 기본 타입은 call by value로 동작한다. 함수의 인자로 기본 타입이 넘겨질 경우, 함수의 매개변수로 복사된 값이 전달된다. 따라서 함수 내부에서 매개변수를 이용해 값을 변경해도, 실제로 호출된 변수의 값이 변경되지는 않는다.

반면, 객체와 같은 참조 타입의 경우, 함수를 호출할 때 call by reference(틀림!)로 동작한다. 즉, 함수를 호출할 때, 객체의 참조값이 전달되고 함수 내부에서 참조값을 이용해 인자로 넘긴 실제 객체의 값을 변경할 수 있다.

-> JS에서 call by reference가 틀린 이유?

JS는 항상 call by value다. 왜냐하면 cbr이 불완전하기 때문이다. 

function foo(obj2) {
  obj2= 10;
  console.log(obj2);	// 10
}
let obj1 = { a: 5, b: 8 };
foo(obj1);
console.log(obj1);	// C, C++에서는 10 / JS는 { a: 5, b: 8 } 출력!!

 

c나 c++ 같은 언어에서 포인터를 예로 들면, 함수 내에서 obj2 = 10;을 하는 경우 obj1의 값이 10이 된다. 완전한 주소값 참조로 되어 있기 때문이다. 

하지만 JS의 경우에는 객체의 프로퍼티 값이 아닌 객체 자체의 변경을 허용하지 않는다. 왜냐하면 겉보기에는 주소값을 참조하는 것 같지만 실제로는 주소값의 복사본을 만들어서 넘기기 때문이다.

 

https://velog.io/@leo-xee/JS-Call-by-value-reference

 

이처럼 함수 내부의 obj2 = 10;에서 obj2의 주소값이 10이 저장된 위치의 주소값으로 변경된다. 결론적으로 두 객체는 다른 객체이다. 때문에 JS는 call by sharing이라는 용어를 사용하기도 한다.

 

 

- var 키워드를 사용하지 않고 변수를 선언하면 함수 내에 선언하더라도 그 변수는 자동으로 전역 범위를 갖는다. 함수에서 선언한 변수가 메인 프로그램에 있는 전역 변수의 값을 변경하게 된다.(var : function scope / const, let : block scope)

 

- JS에서는 변수 범위가 함수 범위고 블록 범위가 지원되지 않는다고 했으나 ES6(ES2015)에서 let, const가 지원되면서 가능해졌다. 

"let은 변수가 선언된 블록, 구문 또는 표현식 내에서만 유효한 변수를 선언한다. 이는 var 키워드가 블록 범위를 무시하고 전역 변수나 함수 지역변수로 선언되는 것과 다르다."

 

- let은 var과 달리 변수의 재할당을 허용하지 않아 재할당 문제를 해결해준다.

 

- 그 밖에 var은 호이스팅이 지원되는데 변수가 함수 내에서 정의되면 그 함수의 최상위로, 함수 바깥에 정의된 경우 전역 컨텍스트의 최상위로 변경되는 것이다.

코드의 순서와 상관 없이 변수와 함수 선언문에 대한 메모리를 먼저 확보하는 것이다. 하지만 값이 할당되지는 않으므로 undefined를 출력한다. 

 

- 객체는 생성자 함수를 이용해 정의한다. this 키워드는 각 함수와 프로퍼티를 객체 인스턴스로 연결하는 역할이다. 어떤 객체의 balance 프로퍼티를 참조하는 것인지 알 수 있도록 this 키워드를 사용했다. 

function Checking(amount){
	this.balance = amount; // 프로퍼티
    this.deposit = deposit; // 함수
	// ...
}

function deposit(amount){
	this.balance += amount;
}

// ... 

let account = new Checking(500);
account.deposit(1000);

// ...

 

- 배열은 정수 인덱스(오프셋)을 이용해 각 요소에 접근할 수 있게 순차적으로 요소를 저장한 집합체다. JS의 배열은 특화된 JS 객체이며 정수 인덱스(객체의 프로퍼티명 역할)로 객체 내 데이터 오프셋을 표현한다. 정수를 인덱스로 사용하면 내부적으로 정수를 문자열로 변환한다. 내부적으로는 특화된 객체이므로 배열로 취급하는 것이다.

 

- 한 배열에 다양한 종류의 요소를 포함할 수 있다.(대부분 스크립트 언어가 그렇다.)

 

- Array.isArray()로 특정 객체가 배열인지 여부를 확인할 수 있다.

 

- JS 배열은 객체이므로 언제든지 크기가 늘어날 수 있다. length 프로퍼티를 사용하면 현재 배열의 실제 요소 수가 정확히 반환되므로 언제나 배열의 모든 요소에 접근할 수 있다.

 

- split() 함수는 문자열을 특정 구분자(공백 등)로 분리한다음 분리된 문자열을 포함하는 배열을 반환한다.

 

- 배열을 다른 배열에 할당할 수 있다. 이는 할당된 배열의 레퍼런스를 할당하는 것이다. 따라서 원래 배열을 바꾸게 되면 할당된 배열도 바뀐다. 이를 얕은 복사(shallow copy)라고 한다.

 

- 깊은 복사(deep copy)원래 배열 요소를 새로운 배열 요소로 복사하는 기능이 필요할 때 쓴다.

 

- 접근자 함수(accessor function)은 특정값을 포함하는 결과 배열을 반환한다.

 

- 그 중 자주 쓰이는 것이 indexOf()로 인자로 제공된 값이 배열에 있으면 인덱스 위치를 반환하고 없으면 -1을 반환한다. 여러 개면 첫 번째로 발견한 인자의 인덱스를 반환한다. 

 

- join(), toString() 함수는 배열을 문자열 형식으로 바꾼다. 두 함수 모두 배열의 요소를 콤마(,)로 구분하는 문자열을 반환한다.

 

- print()나 console.log()의 인자로 배열의 이름을 제공하면 자동으로 배열의 toString()이 호출된다.

 

- concat(), splice()는 기존 배열을 이용해 새 배열을 만든다. concat()은 두 개 이상의 배열을 합쳐서 새 배열을 만들고, splice()는 기존 배열의 서브셋으로 새 배열을 만든다.

// concat()
let itDiv = cisDept.concat(dmpDept);
console.log(itDiv);

인자로 제공된 배열이 원래 배열의 뒤로 추가된다.

 

splice()는 사용할 첫 요소의 위치, 기존 배열에서 사용할 요소의 수를 인자로 받는다.

// splice()
let itDiv = ["D", "C", "R", "L", "J", "R", "C", "B"];
let dmpDept = itDiv.splice(3,3);
let cisDept = itDiv;

console.log(dmpDept); // ["L", "J", "R"]
console.log(cisDept); // ["D", "C", "R", "C", "B"]

사용할 첫 요소의 위치는 3이므로 itDiv[3] = L 부터 사용할 요소의 수 3만큼 잘라서 3개의 요소를 가진 배열이 만들어졌다. 해당 요소들을 제외한 배열은 itDiv에 남아있음을 볼 수 있다.

따라서 splice는 원하는 요소를 제거하는 용도로 사용할 수 있다.

 

 

CH2 배열

2.4 변형자 함수

개별적으로 요소를 건드리지 않고 배열 전체 내용을 고치는 여러 변형자 함수(mutator function)

 

2.4.1 배열에 요소 추가하기

push(), unshift() 함수는 각각 배열에 요소를 추가하고, 배열의 요소를 제거하는 함수다. push()는 배열의 끝에 요소를 추가한다. length를 이용해 배열을 확장하는 것보다 직관적이다.

 

배열의 처음에 요소를 추가할 때 변형자 함수가 없다면 배열에 있던 기존 모든 요소를 한 칸씩 뒤로 이동시킨 다음 새 데이터를 추가해야 한다. 

// 배열의 처음에 요소를 추가하기
let nums = [2, 3, 4, 5];
let newnum = 1;
let N = nums.length;
for (let i = N; i >= 0; --i) {
    nums[i] = nums[i - 1]; // length로 인덱스를 지정하면 한 칸씩 뒤로
}
nums[0] = newnum;
console.log(nums); // 1,2,3,4,5

배열의 요소가 많을수록 코드의 효율성이 떨어진다.

 

unshift()는 이 고민을 해결해준다.

// unshift()
let nums = [2, 3, 4, 5];
console.log(nums); // 2,3,4,5

let newnum = 1;
nums.unshift(newnum);
console.log(nums); // 1,2,3,4,5

nums = [3, 4, 5];
nums.unshift(newnum, 2);
console.log(nums); // 1,2,3,4,5

한 번에 여러 요소를 배열 앞으로 추가할 수도 있다.

 

 

210501 토요일 4일차

CH2 배열

2.4 변형자 함수

2.4.2 배열의 요소 삭제하기

pop()  변형자 함수를 이용하면 간단하게 배열의 마지막 요소를 제거할 수 있다.

 

let nums = [1,2,3,4,5,9];
nums.pop();
console.log(nums); // 1,2,3,4,5

 

배열의 앞 요소를 제거할 때 변형자 함수가 없다면 요소를 추가할 때와 마찬가지로 비효율적이다. 

let nums = [9,1,2,3,4,5];
console.log(nums);
for ( let i = 0; i < nums.length; ++i){
	nums[i] = nums[i+1];
}
console.log(nums); // 1,2,3,4,5,

비효율적인 동작 후에 맨 끝에 불필요한 값이 저장된다. 마지막 콤마가 출력된 것으로 알 수 있다.

 

 

shift() 함수를 이용하면 간단히 배열의 맨 처음 요소를 제거할 수 있다. 

let nums = [9,1,2,3,4,5];
nums.shift();
console.log(nums); // 1,2,3,4,5

이번에는 배열 끝에 불필요한 요소가 없어졌다. 

pop()과 shift()는 제거된 요소를 반환하므로 필요하면 이 요소를 변수에 저장할 수 있다.

 

// pop,shift는 제거된 요소를 반환하여 요소를 변수에 저장할 수 있다.
let nums = [6, 1, 2, 3, 4, 5];
let first = nums.shift(); // 6 저장
nums.push(first);
console.log(nums); // 1,2,3,4,5,6

 

 

2.4.3 배열 중간에 요소를 추가하거나 배열의 중간에 있는 요소 삭제하기

중간에 추가하거나 삭제할 때도 다른 요소를 이동시켜야 하는 문제가 생긴다. splice()를 이용하면 한번에 두 가지 동작을 수행할 수 있다.

배열에 요소를 추가하려면 세 가지 인자가 필요하다.

  • 시작 인덱스(어느 지점부터 요소를 추가할 것인지)
  • 삭제할 요소의 개수(요소를 추가할 때는 O)
  • 배열에 추가할 요소들
let nums = [1, 2, 3, 7, 8, 9];
let newElements = [4, 5, 6];
nums.splice(3, 0, ...newElements);
console.log(nums); // 1,2,3,4,5,6,7,8,9

시작 인덱스 3(nums[3]), 0이므로 요소 추가, 그 다음부터는 추가하려는 요소 목록을 자유롭게 제공하면 된다.

 

 

// splice() : 중간에 삭제
let nums = [1, 2, 3, 100, 200, 300, 400, 4, 5];
nums.splice(3, 4);
console.log(nums); // 1,2,3,4,5

시작 인덱스 3(nums[3])부터 요소 4개를 삭제한다.

 

 

2.4.4 배열 요소 정렬하기

reverse()는 배열의 요소를 역순으로 바꾼다. 

let nums = [1, 2, 3, 4, 5];
nums.reverse();
console.log(nums); // 5,4,3,2,1

 

 

sort() 함수가 배열 요소를 순서대로 정렬하며 특히 문자열을 정렬할 때 유용하다.

// sort() : 순서대로 정렬
let names = ["D", "M", "C", "Cl", "B", "R"];
nums.sort();
console.log(names); // B, Cl, C, D, M, R

숫자는 생각대로 되지 않는다.

 

sort()는 배열 요소를 문자열로 간주하고 알파벳 순으로 요소를 정렬한다. sort()에 순서를 결정해주는 함수를 인자로 전달하면 이를 통해 숫자를 올바르게 정렬할 수 있다. 

 

단순하게 한 숫자에서 다른 숫자를 빼는 방식으로 숫자의 순서를 결정하는 함수를 구현할 수 있다. 뺄셈 결과가 음수면 왼쪽이 작다는 것이고 0이면 같다는 것이며, 결과가 양수면 왼쪽이 오른쪽보다 크다는 것이다. 

// sort() 숫자 정렬 함수
function compare(num1, num2) {
    return num1 - num2;
}
let nums = [3, 1, 2, 100, 4, 200];
nums.sort(compare);
console.log(nums); // 1,2,3,4,100,200

 

 

2.5 반복자 함수

반복자 함수는 배열의 각 요소에 함수를 적용한 다음 그 결과 값 또는 값의 집합 또는 새로운 배열을 반환한다.

 

 

2.5.1 배열을 만들지 않는 반복자 함수

배열을 만들지 않는 반복자 함수로 forEach() 함수가 있다. 배열의 모든 요소에 인자로 받은 함수를 호출한다.

function square(num) {
    console.log(num, num * num);
}

let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
nums.forEach(square);

 

 

every()불린 함수를 배열에 적용해 배열의 모든 요소가 참이면 true를 반환한다.

// every() : 불린 함수를 배열에 적용, 모든 요소가 참이면 true 반환.
function isEven(num) {
    return num % 2 == 0;
}

let nums = [2, 4, 6, 8, 10];
let even = nums.every(isEven);
if (even) {
    console.log("all numbers are even");
} else {
    console.log("not all numbers are even");
}

 

 

some() 함수는 배열 요소 중에 한 요소라도 인자로 받은 불린 요소의 기준을 만족하면 true를 반환한다. 

// some() : 불린 함수를 배열에 적용, 하나라도 요소가 참이면 true 반환.
function isEven(num) {
    return num % 2 == 0;
}

let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let someEven = nums.some(isEven);
if (someEven) {
    console.log("some numbers are even");
} else {
    console.log("not all numbers are even");
}
nums = [1, 3, 5, 7, 9];
someEven = nums.some(isEven);
if (someEven) {
    console.log("some numbers are even");
} else {
    console.log("no numbers are even");
}

 

 

reduce() 함수는 누적자 함수를 인자로 받은 다음 배열의 모든 요소를 누적자 함수에 적용한다. reduce()는 add()를 이용해 왼쪽에서 오른쪽으로 배열의 합을 계산한다.

 

add(1,2) => 3

add(3,3) => 6

add(6,4) => 10

,,,

 

 

reduce() 함수를 이용해 문자열을 연결할 수도 있다.

// reduce() : 문자열도 연결할 수 있다.
function concat(accumulatedString, item) {
    return accumulatedString + item;
}

let words = ["the ", "quick ", "brown ", "fox "];
let sentence = words.reduce(concat);
console.log(sentence); // the quick brown fox

 

reduceRight()는 배열의 오른쪽 요소에서 왼쪽 요소로 처리하는 방향만 다르다. 

// reduceRight() : 오른쪽에서 왼쪽으로 처리. 순서 뒤집기
function concat(accumulatedString, item){
  return accumulatedString + item
}

let words = ["the ", "quick ", "brown ", "fox "]
let sentence = words.reduceRight(concat);
console.log(sentence); // "fox brown quick the"

 

 

2.5.2 새 배열을 반환하는 반복자 함수

map(), filter() 함수는 모두 새 배열을 반환하는 반복자 함수다. map()은 forEach() 처럼 배열의 각 요소에 함수를 적용하는 함수다. 다만 map()은 배열 요소에 함수를 적용한 결과를 포함하는 새 배열을 반환한다는 점이 다르다.

// map() : 배열의 각 요소에 함수를 적용하고 그 결과를 포함하는 새 배열을 반환한다.
function curve(grade) {
    return (grade += 5);
}

let grades = [77, 65, 81, 92, 83];
let newgrades = grades.map(curve);
console.log(newgrades); // 82, 70, 86, 97, 88

 

 

문자열에 map() 사용

function first(word){
	return word[0];
}

let words = ["for", "your", "information"]
let acronym = words.map(first)
console.log(acronym.join("")); // "fyi"

toString() 을 이용해 요소를 출력하면 콤마가 표시되므로 공백문자열을 분리자로 사용하도록 join()을 호출한다.

 

 

filter()는 every()와 비슷하다. filter()는 불린 함수를 만족하는 요소를 포함하는 새로운 배열을 반환한다.

// filter() : 불린 함수를 만족하는 요소를 포함하는 새로운 배열을 반환
function isEven(num) {
    return num % 2 == 0;
}

function isOdd(num) {
    return num % 2 != 0;
}

let nums = [];
for (let i = 0; i < 20; ++i) {
    nums[i] = i + 1;
}
let evens = nums.filter(isEven);
console.log("Even numbers: ");
console.log(evens);
let odds = nums.filter(isOdd);
console.log("Odd numbers: ");
console.log(odds);

 

 

// filter() : 성적 처리에 이용
function passing(num) {
    return num >= 60;
}
let grades = [];
for (let i = 0; i < 20; ++i) {
    grades[i] = Math.floor(Math.random() * 101);
}
let passGrades = grades.filter(passing);
console.log("All grades: ");
console.log(grades);
console.log("Passing grades: ");
console.log(passGrades);

 

 

문자열에 filter() 적용. e앞에 i, c 다음에 나온 e 앞에는 i가 나오지 않아도 된다를 구현한 예제

// fliter() : 문자열에 적용
function afterc(str) {
    if (str.indexOf("cie") > -1) {
        return true;
    }
    return false;
}

let words = ["recie", "dec", "percie", "dec", "concie"];
let misspelled = words.filter(afterc);
console.log(misspelled); // rec, per, con

 

 

2.6 이차원 배열과 다차원 배열

자바스크립트는 기본적으로 일차원만 지원하지만 다차원 배열을 만들 수 있다.

 

 

2.6.1 이차원 배열 만들기

배열을 만든 다음 각 요소를 배열로 만들어야 한다. 최소한 배열의 행 개수를 알아야 이차원 배열을 만들 수 있다.

 

let twod = [];
let rows = 5;
for ( let i = 0; i < rows; ++i ) {
	twod[i] = [];
}

배열의 모든 요소가 undefined가 된다는 단점이 있다.

 

 

이 함수는 배열의 행과 열의 개수를 설정하며 함수에 제공된 값으로 모든 요소를 초기화한다.

Array.matrix = function (numrows, numcols, initial) {
    let arr = [];
    for (let i = 0; i < numrows; ++i) {
        let columns = [];
        for (let j = 0; j < numcols; ++j) {
            columns[j] = initial;
        }
        arr[i] = columns;
    }

    return arr;
};

let arr = Array.matrix(3, 2, 3);
console.log(arr);

 

간단한 데이터 집합은 아래 코드로 가장 쉽게 만들 수 있다.

let grade = [[89,77], [75,82]];

 

 

2.6.2 이차원 배열 요소 처리하기

두 가지 주요 패턴이 있다. 한 가지는 배열의 열을 기준으로 하는 것이고, 다른 것은 행을 기준으로 요소를 처리하는 것이다. 

 

두 패턴 모두 중첩 for문을 사용한다. 열 기준 처리에서는 외부 루프가 행처리, 안쪽 루프가 열 처리. grades 에서 각 행이 한 학생의 점수 집합을 포함한다고 간주한다. 따라서 행의 모든 점수를 더해 total 변수에 저장한 다음 total 변수를 행에 있는 점수의 개수로 나누면 평균을 구할 수 있다.

 

let grades = [
    [89, 77, 78],
    [76, 82, 81],
    [91, 94, 89],
];
let total = 0;
let average = 0.0;
for (let row = 0; row < grades.length; ++row) {
    for (let col = 0; col < grades[row].length; ++col) {
        total += grades[row][col];
    }
    average = total / grades[row].length;
    console.log(
        "Student " + parseInt(row + 1) + " average: " + average.toFixed(2)
    );
    total = 0;
    average = 0.0;
}

각 행은 배열을 포함하므로 각 행에는 length 프로퍼티가 있다.

 

각 학생의 점수 평균은 toFixed(n) 에 의해 소수점 두 자릿수로 반올림된다. 

 

행 중심으로 처리하려면 외부 루프가 열을 처리하고 내부 루프가 행을 처리하도록 바꾸면 된다.

 

 

2.6.3 들쭉날쭉한 배열

배열의 행이 포함하는 요소의 개수가 서로 다른 배열이다. 다른 언어에 비해 JS는 각 행의 길이를 정확히 알 수 있으므로 들쭉날쭉한 배열도 잘 처리할 수 있다.

 

 

2.7 객체를 요소로 포함하는 배열

배열은 객체도 요소로 포함할 수 있으며 지금까지 살펴본 함수와 프로퍼티도 잘 동작한다.

function Point(x, y) {
    this.x = x;
    this.y = y;
}

function displayPts(arr) {
    for (let i = 0; i < arr.length; ++i) {
        console.log(arr[i].x + ", " + arr[i].y);
    }
}

let p1 = new Point(1, 2);
let p2 = new Point(3, 5);
let p3 = new Point(2, 8);
let p4 = new Point(4, 4);
let points = [p1, p2, p3, p4];
for (let i = 0; i < points.length; ++i) {
    console.log(
        "Point " + parseInt(i + 1) + ": " + points[i].x + ", " + points[i].y
    );
}
let p5 = new Point(12, -3);
points.push(p5);
console.log("After push : ");
displayPts(points);
points.shift();
console.log("After shift : ");
displayPts(points);

push()를 이용해 12, -3을 추가했으며 shift()를 이용해 1,2 를 제거했다.

댓글