JavaScript

자료구조, 배열 Arrays, 내장 메서드로 재배열 하기

남희정 2023. 8. 19. 01:19

Computer Science의 필수, 자료구조. 듣기야 많이 들었는데 대체 뭘까?

 왜 자료구조와 알고리즘 공부의 필요성을 강조하는 걸까? 이런 공부를 통해 얻을 수 있는 것은 무엇일까?

직접 알아보고 생각해보고 느껴보자!

강의로 배열을 공부하면서 배열이 자료구조의 제일 쉽고 기본적인 형태라고 들어서 제대로 짚고 넘어가자고 생각했다.

테스트 코드를 만들며 겪은 문제들을 해결하는 과정도 포스팅 해보겠다.


자료구조 Data Structure

효율적인 접근 및 수정을 가능케 하는 자료의 조직, 관리, 저장을 의미한다.
더욱 정확히 말하면, 자료구조는 데이터 값의 모임, 데이터 간의 관계, 데이터에 적용할 수 있는 함수나 명령을 의미한다.

 

컴퓨터가 데이터를 효율적으로 다룰 수 있게 도와주는 데이터 보관 방법과 데이터에 관한 연산의 총체.

자료구조는 다음 그림과 같이 단순 자료구조(Primitive Data Structure)복합 자료구조(Non-Primitive Data Structure)로 나뉜다. 단순 자료구조는 integer(정수)를 포함해 프로그래밍 언어에서 통상적으로 제공하는 기본 데이터 형식을 말한다.

 

자료구조 분류

 

복합 자료구조는 또 다시 선형 자료구조(Linear Data Structure)비선형 자료구조(Non-Linear Data Structure)로 나뉜다.

선형 자료구조는 말그대로 데이터 요소를 순차적으로 연결하는 자료구조로, 구현하기 쉽고 사용하기도 쉽다. 비선형 자료구조는 한 데이터 요소에서 여러 데이터 요소로 연결되기도 하고, 여러 데이터 요소가 하나의 데이터 요소로 연결되기도 한다.

선형 자료구조와 비선형 자료구조의 예시

 

추상 데이터 형식 Abstract Data Types, ADT 

ADT는 프로그래밍에서 데이터와 그 데이터에 대한 연산을 추상화하여 정의하는 방법론이나 개념이다. 데이터의 내부 구현 방식을 숨기고 해당 데이터와 관련된 연산만을 노출하여 사용자가 더 추상화된 레벨에서 프로그래밍할 수 있도록 한다.

데이터의 논리적인 특징을 설명하고, 그에 따라 수행할 수 있는 연산을 정의한다. 

 

ex: Stack 스택의 경우 데이터를 쌓아 올리는 방식의 자료구조로, 일반적으로 push와 pop이라는 두 가지 연산을 지원한다. 스택 ADT는 이런 연산들을 정의하고 사용자는 이 연상들을 통해 스택을 조작한다. 내부 구현 방식(배열, 연결리스트 등)은 노출되지 않는다.

 

ADT

 

알고리즘 Algorithm

9세기 페르시아 수학자 알 콰리즈미(Al-Khwarizmi)의 이름에서 유래된 말로 주어진 문제를 해결하기 위해 정해진 명확하고 단계적인 절차나 방법. 컴퓨터 과학 및 프로그래밍에서 알고리즘은 입력 데이터를 받아들여 원하는 출력을 생성하는 과정을 설명하는 것을 말한다. 자료 구조마다 다르게 그에 맞는 효율적인 알고리즘을 선택하여 문제를 해결한다.

 

알고리즘

 

왜 알아야할까?

사용할 수 있는 컴퓨터의 메모리 자원은 한정적인데 반해 처리해야할 데이터는 무수히 많을 수 있다. 이 메모리 공간을 효율적으로 사용해야 하는데 필요한 것이 자료구조이다. 다양한 자료구조의 장점과 한계를 아는 것이 중요하다. 메모리 공간의 효율 뿐만이 아니라 실행 시간의 효율성도 따질 수 밖에 없는데, 요구사항에 맞는 해결책을 제시하되, 최대한 효율적인 방법으로 소모되는 비용을 절약하기 위함이다. 자료구조와 알고리즘을 제대로 이해하게 된다면 메모리(공간)와 프로세싱 파워(시간)을 아끼고 자원을 효율적으로 활용하면서도 고성능의 코드를 작성하는 더 나은 개발자로 성장할 수 있는 것 ✅ 이다.

 

여러 자료구조 중에서 오늘 정리해볼 것은 배열이다.

 

배열 Arrays

프로그래밍에서 여러 개의 값을 하나의 변수에 저장하고 관리하는 데이터 구조. 순서가 있는 요소element들의 모음으로 각 요소는 Index를 가지고 있어 해당 위치([0], [1]...)를 통해 접근할 수 있다. JavaScript에서 배열은 크기 조정이 가능하고 데이터 유형을 혼합하여 포함할 수 있다. (타입을 지정하여 배열을 설정할 수도 있음.) 그리고 참조형 데이터 타입이다! 🌟 이것과 같이 얕은 복사shallow copy와 관련한 포스팅도 해보겠다.

 

실습 예제 + 테스트 코드

오늘 풀어본 퀴즈 3가지모던하게 바꿀 수 있는 방법까지 알아보겠다. (테스트 코드도 같이..!)

정말 하수같은 시도를 했던 나를 기억하기 위해 전부 기록해보겠다 모두에게 처음은 있는 거니까..🫠

 

Quiz 1.

주어진 배열 안의 딸기 아이템을 키위로 교체하는 함수를 만들기 (단, 주어진 배열을 수정하지 않도록!)

// input: ['🍌', '🍓', '🍇', '🍓']
// output: [ '🍌', '🥝', '🍇', '🥝' ]

1차 시도

// replaceArray.js

const fruits = ["🍌", "🍓", "🍇", "🍓"];
const newFruits = [];

for (let i = 0; i < fruits.length; i++) {
  if (fruits[i] !== "🍓") {
    newFruits.push(fruits[i]);
  } else {
    newFruits.push("🥝");
  }
}

export { fruits, newFruits };
// replaceArray.test.js

import { test, it, expect } from "vitest";
import { fruits, newFruits } from "../../array/replaceArray";

test("주어진 배열 수정 없이 내부 아이템 교체해서 새 배열 만들기", () => {
  it("주어진 배열 확인", () => {
    expect(fruits).toStrictEqual(["🍌", "🍓", "🍇", "🍓"]);
  });
  it("새 배열 확인", () => {
    expect(newFruits).toStrictEqual(["🍌", "🥝", "🍇", "🥝"]);
  });
});

물론 테스트는 통과했지만 항상 질문해야한다. 최선인가?🤔

우선 제일 중요한 것으로, 함수를 안만듦...(왜)

재사용성이 떨어진다는 점! 그리고 for문을 대체할 수 있는 것이 분명 있을 거라 생각했다.

+ 도중에 test가 제대로 작동하지 않음을 깨달음. Vitest에서 it을 써도 소용이 없다는 것..◕‿◕ ..

3단으로 사용시 describe와 test, expect를 써야함. it은 먹히지 않음. 스스로 바보 같아서 잠시 영혼이 나갔다왔다. 

 

2차 시도 : 재사용할 수 있게 함수로 만들기

// replaceArray.js

function replace(array, from, to) {
  const replaced = Array.from(array);
  for (let i = 0; i < replaced.length; i++) {
    if (replaced[i] === from) {
      replaced[i] = to;
    }
  }
  return replaced;
}

export { replace };
// replaceArray.test.js

import { describe, test, expect } from "vitest";
import { replace } from "../../array/replaceArray";

const array = ["🍌", "🍓", "🍇", "🍓"];
const replaced = replace(array, "🍓", "🥝");

describe("주어진 배열 수정 없이 내부 아이템 교체해서 새 배열 만들기", () => {
  test("주어진 배열 확인", () => {
    expect(array).toStrictEqual(["🍌", "🍓", "🍇", "🍓"]);
  });
  test("새 배열 확인", () => {
    expect(replaced).toStrictEqual(["🍌", "🥝", "🍇", "🥝"]);
  });
});

꼭 딸기와 키위가 아니더라도, 추후에 교체하고 싶은 배열 요소를 찾아낼 수 있는 함수를 만들어냈다.

또 우리는 질문 해보아야한다. 최선일까? 🤔

 

3차 시도(최종) : map() 함수로 간결하게 만들기

Array.prototype.map()

map() 메서드는 배열 내의 모든 요소 각각에 대하여 주어진 함수를 호출한 결과를 모아 새로운 배열을 반환한다.

// replaceArray.js

function replace(array, from, to) {
  return array.map(item => (item === from ? to : item));
}

export { replace };

Testcode는 2차시도 그대로 진행했다.

 

Quiz 2.

배열과 특정한 요소를 전달받아, 배열 안에 그 요소가 몇 개나 있는지 카운트 하는 함수 만들기

// input: [ '🍌', '🥝', '🍇', '🥝' ], '🥝'
// output: 2

1차 시도

// countArray.js

const howMany = ["🍌", "🥝", "🍇", "🥝"];
let total = 0;
for (let i = 0; i < howMany.length; i++) {
  if (howMany[i] === "🥝") {
    total++;
  } else {
    continue;
  }
}

export { total };
// countArray.test.js

import { test, expect } from "vitest";
import { total } from "../../array/countArray";

test("배열안에 그 요소가 몇개나 있는지 카운트 하기", () => {
  expect(total).toBe(2);
});

이것 또한 재사용성이 떨어진다. 함수 안만드니..? 🥹 정신 차려야겠다.

또한 변수 이름도 짓기 어려워서 헤매는 것 같다. 많이 참고 하는 중.. 

 

2차 시도 : 재사용성을 위해 함수로 만들기

// countArray.js

function count(array, item) {
  let total = 0;
  for (let i = 0; i < array.length; i++) {
    if (array[i] === item) {
      total++;
    }
  }
  return total;
}

export { count };
// countArray.test.js

import { test, expect } from "vitest";
import { count } from "../../array/countArray";

test("배열안에 그 요소가 몇개나 있는지 카운트 하기", () => {
  expect(count(["🍌", "🥝", "🍇", "🥝"], "🥝")).toBe(2);
});

3차 시도(최종)

이전에 포스팅 한 적 있는 filter() 메서드를 사용했다. 역시 계속 써봐야 아는 것 같다. 🥹

2023.08.15 - [JavaScript] - 반복문 없이 Array.prototype.filter()로 쉽게 재배열 하기

// countArray.js

function count(array, item) {
  const filteredArray = array.filter(element => element === item);
  return filteredArray.length;
}

export { count };

Quiz 3.

배열 1, 배열 2 두개의 배열을 전달 받아, 배열 1 아이템 중 배열 2에 존재하는 아이템만 담고 있는 배열을 반환한다.

// input: ['🍌', '🥝', '🍇'],  ['🍌', '🍓', '🍇', '🍓']
// output: [ '🍌', '🍇' ]

1차 시도

// returnArray.js

const array1 = ["🍌", "🥝", "🍇"];
const array2 = ["🍌", "🍓", "🍇", "🍓"];

for (let i = 0; i < array1.length; i++) {
  if (array2.includes(array1[i])) {
    continue;
  } else {
    array1.splice(i, 1);
  }
}

export { array1 };
// returnArray.test.js

import { test, expect } from "vitest";
import { array1 } from "../../array/returnArray";

test("두개의 배열을 전달받아, 배열2에 존재하는 아이템만 담고 있는 배열 반환", () => {
  expect(array1).toStrictEqual(["🍌", "🍇"]);
});

2차 시도 

어게인 함수 적용.. 재사용성.. 각인시키기

// returnArray.js

function match(input, search) {
  const result = [];
  for (let i = 0; i < input.length; i++) {
    if (search.includes(input[i])) {
      result.push(input[i]);
    }
  }
  return result;
}

export { match };
// returnArray.test.js

import { test, expect } from "vitest";
import { match } from "../../array/returnArray";

test("두개의 배열을 전달받아, 배열2에 존재하는 아이템만 담고 있는 배열 반환", () => {
  expect(match(["🍌", "🥝", "🍇"], ["🍌", "🍓", "🍇", "🍓"])).toStrictEqual([
    "🍌",
    "🍇",
  ]);
});

3차 시도

이번에도 filter() 메서드를 활용해본다

// returnArray.js

function match(input, search) {
	return input.filter(item => serch.includes(item));
}

export { match };

 

내장 배열 메서드를 좀 더 익힌다면 반복문을 직접 작성하지 않고 모던하게 만들 수 있다! 열심히 적용해보겠다..!


 

[개발자는 반드시 자료구조와 알고리즘을 배워야 할까?]

[자료구조(Data Structure) 개념 및 종류 정리]

[What is Algorithm]

[배열 Arrays]

ChatGPT 🤖

드림코딩아카데미 | 자바스크립트 마스터리 (ES6+)