본문 바로가기
Web/Javascript

Data structures & Algorithms with JavaScripts 스터디 5~7일차

by sjwdev 2021. 5. 5.

210502 일요일 5일차

# 3~4일차 복습

- 배열의 처음에 요소를 추가할 때 변형자 함수가 없다면 배열에 있던 기존 모든 요소를 한 칸씩 뒤로 이동시킨 다음 새 데이터를 추가해야 한다. unshift()는 한 번에 여러 요소를 배열 앞으로 추가할 수도 있다.

 

배열의 앞 요소를 제거할 때 변형자 함수가 없다면 요소를 추가할 때와 마찬가지로 비효율적이다. 비효율적인 동작 후에 맨 끝에 불필요한 값이 저장된다. 마지막 콤마가 출력된 것으로 알 수 있다.

 

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

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

 

 

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

 

- 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개를 삭제한다.

 

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

// 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

 

- 배열을 만들지 않는 반복자 함수로 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() : 문자열도 연결할 수 있다.
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

 

- 문자열에 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()는 불린 함수를 만족하는 요소를 포함하는 새로운 배열을 반환한다.

// 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);

 

- 이차원 베열

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);

 

 

CH2 배열

2.8 객체에 포함된 배열

한 주 동안 관찰된 가장 높은 온도를 저장하는 객체를 만든다. 새 온도를 추가하는 기능, 저장된 온도의 평균을 계산하는 기능을 포함한다.

 

function weekTemps() {
    this.dataStore = [];
    this.add = add;
    this.average = average;
}

function add(temp) {
    this.dataStore.push(temp);
}

function average() {
    let total = 0;
    for (let i = 0; i < this.dataStore.length; ++i) {
        total += this.dataStore[i];
    }
    return total / this.dataStore.length;
}

let thisWeek = new weekTemps();
thisWeek.add(52);
thisWeek.add(55);
thisWeek.add(61);
thisWeek.add(65);
thisWeek.add(55);
thisWeek.add(50);
thisWeek.add(52);
thisWeek.add(49);
console.log(thisWeek.average());

 

 

2.9 연습문제

#1. 객체에 학생들의 점수 집합을 저장하는 grades 객체를 만드시오. 점수를 추가하는 함수, 학생의 평균 점수를 출력하는 기능을 객체에 추가하시오.

 

function grades() {
    this.dataStore = [];
    this.add = add;
    this.average = average;
}

function add(grade) {
    this.dataStore.push(grade);
}

function average() {
    let total = 0;
    for (let i = 0; i < this.dataStore.length; ++i) {
        total += this.dataStore[i];
    }
    return total / this.dataStore.length;
}

let grade = new grades();
grade.add(800);
grade.add(1000);
grade.add(1200);
grade.add(1400);
console.log(grade.average()); // 1100

점수를 추가하기 위해 dataStore라는 배열에 push를 이용하였다. 평균 점수는 배열의 모든 요소를 더한다음 배열의 요소 개수로 나눈 값을 반환하여 그 값을 console.log로 출력했다.

 

#2. 배열의 단어 집합을 저장한 다음 배열의 내용을 정방향 또는 역방향으로 출력하는 기능을 구현하시오.

 

let arr = ["D", "C", "R", "L", "J"];
console.log(arr);
console.log(arr.reverse());

 

#3. 이차원  배열을 이용해 월간 온도 자료를 저장하도록 weeklyTemps 객체를 고치시오. 월간 평균, 지정한 주의 평균, 모든 주의 평균을 출력하는 함수를 만드시오.

 

function weekTemps() {
    this.dataStore = Array.matrix(4, 7, 0); // 이차원 배열 4주, 7일
    this.add = add;
    this.wAverage = wAverage;
    this.wAverageAll = wAverageAll;
    this.mAverage = mAverage;
}

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;
};

function add(temp) {
    this.dataStore.push(temp);
}

function wAverage(i) {
    let total = 0;

    if (0 < i && i < this.dataStore.length) {
        // 자연수만 받기 > 정규표현식 사용
        for (let j = 0; j < this.dataStore[i - 1].length; ++j) {
            total += this.dataStore[i - 1][j];
        }
        return total / this.dataStore[i - 1].length;
    } else {
        return "1 ~ " + this.dataStore.length + " 주로 입력하세요";
    }
}

function wAverageAll() {
    let total = 0;
    let arr = [];

    for (let i = 0; i < this.dataStore.length; ++i) {
        for (let j = 0; j < this.dataStore[i].length; ++j) {
            total += this.dataStore[i][j];
        }
        arr.push(i + 1 + "주의 평균 : " + total);
        total = 0; // 주간 총합 초기화
    }

    return arr;
}

function mAverage() {
    let total = 0;
    let mTotal = 0;

    for (let i = 0; i < this.dataStore.length; ++i) {
        for (let j = 0; j < this.dataStore[i].length; ++j) {
            total += this.dataStore[i][j];
        }
        mTotal += total;
        total = 0; // 주간 총합 초기화
    }
    return mTotal / (this.dataStore.length * this.dataStore[0].length);
}

let thisWeek = new weekTemps();
console.log(thisWeek.wAverage(1)); // 첫째 주 평균
console.log(thisWeek.wAverageAll()); // 모든 주 평균
console.log(thisWeek.mAverage()); // 월간 평균

 

 

CH3 리스트

3.1 리스트 ADT

리스트는 위치를 가리키는 프로퍼티를 포함한다. 리스트에는 front, end 프로퍼티가 있다. next() 함수로 리스트의 현재 요소에서 다음 요소로 이동할 수 있으며 prev() 함수로 현재 요소에서 이전 요소로 이동할 수 있다. moveTo(n) 함수를 이용하면 n 번째 위치로 한 번에 이동할 수 있다. currPos() 함수는 리스트의 현재 위치를 가리킨다.

 

 

 

3.1 List 클래스 구현

 

생성자 함수 정의

function List() {
    this.listSize = 0;
    this.pos = 0;
    this.dataStore = [];
    this.clear = clear;
    this.find = find;
    this.toString = toString;
    this.insert = insert;
    this.append = append;
    this.remove = remove;
    this.front = front;
    this.end = end;
    this.prev = prev;
    this.next = next;
    this.length = length;
    this.currPos = currPos;
    this.moveTo = moveTo;
    this.getElement = getElement;
    this.contains = contains;
}

 

 

3.2.1 Append: 리스트에 요소 추가

리스트의 다음 가용 위치(listSize)에 새 요소 추가하는 함수이다.

function append(element) {
    this.dataStore[this.listSize++] = element;
}

 

 

3.2.2 Find: 리스트의 요소 검색

remove()는 가장 구현이 어렵다. 삭제하려는 요소를 찾은 다음 그 요소를 삭제하고 나머지 요소를 왼쪽으로 이동시켜 자리를 메워야 한다. 우선 요소를 찾는 헬퍼 함수 find()를 구현한다.

// find() : remove는 삭제할 요소를 찾고, 요소를 삭제하고, 나머지 배열 요소를 왼쪽으로 이동시켜 자리를 메운다. 삭제할 요소를 찾을 헬퍼 함수
function find(element) {
    for (let i = 0; i < this.dataStore.length; ++i) {
        if (this.dataStore[i] == element) {
            return i;
        }
    }
    return -1;
}

 

 

3.2.3 Remove: 리스트의 요소 삭제

find()는 루프를 돌면서 요소를 검색하고 발견하면 요소의 위치를 반환한다. 찾지 못하면 -1 을 반환한다. remove()는 find()의 반환값으로 에러를 검사한다. 이 반환값을 splice()에 넘겨주어 원하는 요소를 삭제한 다음 배열을 연결한다. 요소를 삭제했다면 true, 못했다면 false를 반환한다.

// remove() : find()의 반환값을 splice 함수에 넘겨주어 원하는 요소를 삭제한 다음 dataStore 배열을 연결한다. 요소를 삭제하면 true, 못하면 false 반환
function remove(element) {
    let foundAt = this.find(element);
    if (foundAt > -1) {
        this.dataStore.splice(foundAt, 1);
        --this.listSize;
        return true;
    }
    return false;
}

 

 

3.2.4 Length: 리스트의 요소 개수

function length() {
    return this.listSize;
}

 

 

3.2.5 toString: 리스트의 요소 확인

function toString(){
	return this.dataStore;
}

 

# 중간 테스트 코드

// 리스트 중간 테스트
function List() {
    this.listSize = 0;
    // this.pos = 0;
    this.dataStore = [];
    // this.clear = clear;
    this.find = find;
    this.toString = toString;
    // this.insert = insert;
    this.append = append;
    this.remove = remove;
    // this.front = front;
    // this.end = end;
    // this.prev = prev;
    // this.next = next;
    this.length = length;
    // this.currPos = currPos;
    // this.moveTo = moveTo;
    // this.getElement = getElement;
    // this.contains = contains;
}

function append(element) {
    this.dataStore[this.listSize++] = element;
}

// find() : remove는 삭제할 요소를 찾고, 요소를 삭제하고, 나머지 배열 요소를 왼쪽으로 이동시켜 자리를 메운다. 삭제할 요소를 찾을 헬퍼 함수
function find(element) {
    for (let i = 0; i < this.dataStore.length; ++i) {
        if (this.dataStore[i] == element) {
            return i;
        }
    }
    return -1;
}

// remove() : find()의 반환값을 splice 함수에 넘겨주어 원하는 요소를 삭제한 다음 dataStore 배열을 연결한다. 요소를 삭제하면 true, 못하면 false 반환
function remove(element) {
    let foundAt = this.find(element);
    if (foundAt > -1) {
        this.dataStore.splice(foundAt, 1);
        --this.listSize;
        return true;
    }
    return false;
}

function length() {
    return this.listSize;
}

function toString() {
    return this.dataStore;
}

let names = new List();
names.append("C");
names.append("R");
names.append("B");
console.log(names.toString());
names.remove("R");
console.log(names.toString());

 

 

3.2.6 Insert: 리스트에 요소 삽입

insert() 는 리스트의 기존 요소 뒤에 새로운 요소를 삽입하게 된다. find로 기존 요소의 위치를 찾아 해당 위치 +1 에 요소를 추가한 다음 listSize를 1만큼 증가시킨다. 

function insert(element, after) {
    let insertPos = this.find(after);
    if (insertPos > -1) {
        this.dataStore.splice(insertPos + 1, 0, element);
        ++this.listSize;
        return true;
    }
    return false;
}

 

 

3.2.7 리스트의 모든 요소 삭제

clear()는 delete 명령어로 배열을 삭제한다음 빈 배열을 다시 만든다. listSize와 pos도 0(시작 위치)으로 초기화한다.

function clear() {
    delete this.dataStore;
    this.dataStore.length = 0;
    this.listSize = this.pos = 0;
}

 

 

3.2.8 Contains: 리스트에 특정 값이 있는지 판단

function contains(element) {
    for (let i = 0; i < this.dataStore.length; ++i) {
        if (this.dataStore[i] == element) {
            return true;
        }
    }
    return false;
}

 

 

 

3.2.9 리스트 탐색

function List() {
    this.listSize = 0;
    this.pos = 0;
    this.dataStore = [];
    // this.clear = clear;
    // this.find = find;
    // this.toString = toString;
    // this.insert = insert;
    this.append = append;
    // this.remove = remove;
    this.front = front;
    this.end = end;
    this.prev = prev;
    this.next = next;
    // this.length = length;
    this.currPos = currPos;
    this.moveTo = moveTo;
    this.getElement = getElement;
    // this.contains = contains;
}

// @ : 리스트 추가용
function append(element) {
    this.dataStore[this.listSize++] = element;
}
// 3.2.9
function front() {
    this.pos = 0;
}

function end() {
    this.pos = this.listSize - 1;
}

function prev() {
    if (this.pos > 0) {
        --this.pos;
    }
}

function next() {
    if (this.pos < this.listSize - 1) {
        ++this.pos;
    }
}

function currPos() {
    return this.pos;
}

function moveTo(position) {
    this.pos = position;
}

function getElement() {
    return this.dataStore[this.pos];
}

let names = new List();
names.append("C");
names.append("R");
names.append("Cy");
names.append("J");
names.append("B");
names.append("D");

// 첫번째 요소로 이동
names.front();
console.log(names.getElement());
// 한 요소 뒤로 이동
names.next();
console.log(names.getElement());
// 두 요소 뒤로 이동했다가 한 요소 앞으로 이동
names.next();
names.next();
names.prev();
console.log(names.getElement()); // C

 

 

3.3 리스트와 반복

반복자를 이용하면 List 클래스 내부 저장소를 직접 참조하지 않고 리스트를 탐색할 수 있다. 리스트의 front, end, prev, next, currPos를 이용해 반복자를 구현할 수 있다. 

반복자는 배열의 인덱스에 비해 아래의 장점이 있다.

  • 리스트 요소에 접근할 때 내부 데이터 저장소가 무엇인지 걱정할 필요가 없다.
  • 리스트에 새 요소를 추가했을 때 현재 인덱스가 쓸모 없는 값이 되는 반면 반복자를 이용하면 리스트가 바뀌어도 반복자를 갱신할 필요가 없다.
  • List 클래스에 사용하는 데이터 저장소의 종류가 달라져도 이전과 같은 방식으로 요소에 접근할 수 있다.

 

반복자 예제

for (names.front(); names.currPos() < names.length(); names.next()) {
    console.log(names.getElement);
}

현재 위치를 리스트 앞으로 설정(front())한다. 그리고 currPos() 가 리스트 길이보다 작을 때 루프를 반복한다. 루프를 돌 때마다 next()로 한 요소씩 전진한다.

 

반대 방향 탐색 예제

for (names.end(); names.currPos() >= 0; names.prev()) {
    console.log(names.getElement);
}

현재 위치를 마지막 요소로 한다.(end()) prev()를 이용하여 현재 위치가 0보다 크거나 같을 때 현재 위치를 뒤로 이동시킨다. 

리스트를 탐색할 때만 반복자를 사용해야 하며 요소 추가/삭제에는 사용하면 안된다.

 

 

3.4 리스트 기반 애플리케이션

비디오 대여 상점을 운영할 수 있는 시스템에서 리스트를 어떻게 활용할 수 있는지 알아본다.

 

 

3.4.1 텍스트 파일 읽기

  1. The Godfather
  2. The Godfather: Part II
  3. The Dark Knight
  4. 12 Angry Men
  5. Schindler’s List
  6. The Lord of the Rings: The Return of the King
  7. Pulp Fiction
  8. The Good, the Bad and the Ugly
  9. The Lord of the Rings: The Fellowship of the Ring
  10. Fight Club
  11. Forrest Gump
  12. Inception
  13. Star Wars: Episode V - The Empire Strikes Back
  14. The Lord of the Rings: The Two Towers
  15. The Matrix
  16. GoodFellas
  17. One Flew Over the Cuckoo’s Nest
  18. Hamilton
  19. Seven Samurai

위 내용으로 텍스트 파일을 만든다.

 

let fs = require("fs");

fs.readFile("films.txt", "utf8", function (err, data) {
    let movies = data.split("\n");
    console.log(movies);
});

File System 모듈 - 한 눈에 끝내는 Node.js. 파일 처리와 관련된 작업을 하는 모듈로, 보통 FileSystem을 줄여서 fs 모듈이라고 줄여 부릅니다.(책에 있는 코드와 다릅니다.)

 

텍스트 파일의 내용이 배열로 저장될 때 개행 문자가 공백으로 치환되므로 문자열 배열에서 문제가 될 수 있다. 따라서 trim()을 이용해 각 요소의 공백을 제거하는 루프를 추가한다. 

function createArr(file) {
    let fs = require("fs");

    let arr = fs.readFile(file, "utf8", function (err, data) {
        let arr = data.split("\n");
        // console.log(movies);
        for (let i = 0; i < arr.length; ++i) {
            arr[i] = arr[i].trim();
        }
        return arr;
    });

    return arr;
}

 

 

3.4.2 리스트로 상점을 관리하기

movies 배열의 내용을 리스트로 저장한다. 

let movieList = new List();
for (let i = 0; i < movies.length; ++i) {
    movieList.append(movies[i]);
}

function displayList(list) {
    for (list.front(); list.currPos() < list.length(); list.next()) {
        console.log(list.getElement());
    }
}

displayList()가 문자열 등 네이티브 형식을 포함하는 리스트와는 잘 동작하지만 Customer 객체에서 잘 작동하지 않아 개선이 필요하다.

 

function displayList(list) {
    for (list.front(); list.currPos() < list.length(); list.next()) {
        if (list.getElement() instanceof Customer) {
            console.log(
                list.getElement()["name"] + ", " + list.getElement()["movie"]
            );
        } else {
            console.log(list.getElement());
        }
    }
}

instanceof 연산자를 이용해 Customer 객체인지 하나씩 확인하고 Customer 객체면 두 프로퍼티를 인덱스로 활용해 고객이 빌린 영화 이름을 가져온다. 해당 객체가 아니면 요소를 그대로 반환한다.

 

let customers = new list();

function Customer(name, movie) {
    // 고객명과 고객이 빌린 영화 이름을 포함하는 객체 생성자 함수
    this.name = name;
    this.movie = movie;
}

function checkOut(name, movie, filmList, customerList) { // 고객이 영화를 대여하는 기능의 함수
    if (movieList.contains(movie)) { // 영화를 이용할 수 있으면 상점 영화 목록에서 지우고 고객의 영화 목록에 추가한다. 
        let c = new Customer(name, movie);
        customerList.append(c);
        filmList.remove(movie);
    } else {
        console.log(movie + " is not available.");
    }
}

checkOut() 은 영화를 대여할 수 있는지 확인한다. 대여할 수 있는 상태면 영화 제목, 고객명을 이용해 Customer 객체를 만든다. 만든 객체를 고객 리스트로 추가하고 영화 리스트에서 대여할 영화를 삭제한다. 대여할 수 없는 상태면 간단한 메시지를 출력한다.

 

checkOut()을 테스트하는 코드

let movies = createArr("films.txt");
let movieList = new List();
let customers = new List();
for (let i = 0; i < movies.length; ++i) {
    movieList.append(movies[i]);
}
console.log("Available movies: \n");
displayList(movieList);
checkOut("Jane Doe", "The Godfather", movieList, customers);
console.log("\nCustomer Rentals: \n");
displayList(customers);

실행하면 "The Godfather"가 삭제된 영화 목록이 출력되고 고객이 대여한 영화 리스트가 출력된다.

 

 

댓글