리스트, 딕셔너리
페이지 제목을 리스트, 딕셔너리 라고 지었지만, 이는 파이썬 용어고 실제로는 여러 값들을 담아놓을 수 있는 자료구조에 대해 다룰 것이다. 자료구조 안에 들어있는 값들은 순서(인덱스) 혹은 이름(키)을 통해 접근 가능하고, 자료구조에 새로운 값을 추가하거나 안에 들어있는 값을 제거하는 것이 가능하다.
이 페이지에서는 되도록이면 간단한 개념 및 사용법만 설명하고 넘어갈 것이다. 즉, 구체적인 내부 구현이나 성능에 관한 이야기는 하지 않는다.
순서를 통해 접근할 수 있는 자료구조
순서를 통해 접근할 수 있는 자료구조는 일반적으로 다음의 특징을 가지고 있다.
- [a, b, c, d] 꼴로 표기한다.
- a, b, c, d는 각각 아이템이라고 한다.
- 아이템들은 타입이 같을 수도, 아닐 수도 있다. 이는 언어마다 조금씩 다르므로 이후에 더 설명한다.
- 안에 있는 총 아이템의 개수를 확인할 수 있다.
- 위의 예시에서는 아이템이 총 4개 들어있다.
- 아이템들에는 순서가 있다.
- 제일 왼쪽에 있는 아이템이 가장 앞에 있는 아이템이다.
- 순서(혹은, 인덱스)가 0부터 시작한다.
- 즉, 첫 번째 아이템이 0번, 두 번째 아이템이 1번, ... 순서로 번호가 매겨져있다.
- 특정한 인덱스에 있는 아이템에 접근할 수 있다.
- 예를 들어, 리스트 안에 5개의 아이템이 있다면 인덱스 0번에 있는 아이템에 접근할 경우 맨 앞에 있는 아이템의 값을 받아올 수 있다.
- 특정 인덱스에 있는 아이템을 수정하는 것도 가능하다.
- 특정한 인덱스에 새로운 아이템을 더할 수 있다.
- 이 경우 기존에 해당 인덱스에 있는 아이템들부터 뒤로 밀린다.
- [0, 1, 2, 3, 4]에서 2번 인덱스에 새로운 아이템 9를 넣으면 [0, 1, 9, 2, 3, 4]가 된다.
- 특정한 인덱스에 있는 아이템을 제거할 수 있다.
- 이 경우 기존에 해당 인덱스 뒤에 있는 아이템들부터 앞으로 당겨진다.
- [0, 1, 2, 3, 4]에서 2번 인덱스에 있는 아이템을 제거하면 [0, 1, 3, 4]가 된다.
# 파이썬에는 list(리스트)를 통해 위의 자료구조가 구현되어 있다.
# 지금까지는 변수 이름을 알파벳 하나로 썼는데, 실제 개발할 때는 변수가 어떤 값을
# 담고있는지 더 쉽게 파악하기 위해서 변수명을 더 길고 자세하게 쓴다.
# 파이썬에서는 변수명을 지을때 언더바(_)로 구분된 단어들을 사용하는데, 이를
# snake case라고 한다. camel case, kebab case, pascal case등 다른 종류의 case도
# 있는데, 자세한 내용은 검색해보길 바란다.
# 아래와 같이 리스트를 생성한다.
number_list = [1, 2, 3, 4, 5]
# 리스트의 길이가 곧 리스트에 들어있는 아이템 개수다.
list_length = len(number_list) # 5
# 아래와 같이 리스트의 특정 인덱스에 접근한다.
n = number_list[0] # 0번째 인덱스에는 1이 들어있다.
n = number_list[3] # 3번째 인덱스에는 4가 들어있다.
n = number_list[-1] # 파이썬에서는 뒤에서부터 인덱스를 접근할 수도 있다. n은 5다.
n = number_list[-2] # 뒤에서 두 번째 아이템은 4다.
# 마지막 인덱스는 4다. 즉, 인덱스 5에 해당하는 아이템은 존재하지 않는다.
#n = number_list[5] # list index out of range 에러가 발생한다.
# 특정 인덱스에 있는 값을 바꿀 수도 있다.
number_list[0] = 8 # number_list가 [8, 2, 3, 4, 5]가 된다.
# 리스트의 끝에 새로운 아이템을 더할 수 있다.
number_list.append(123) # number_list가 [8, 2, 3, 4, 5, 123]이 된다.
# 리스트의 끝에 있는 아이템을 제거할 수 있다.
val = number_list.pop() # val에는 제거한 값이 들어간다.
# 여기서 val은 123, number_list는 [8, 2, 3, 4, 5]다.
# 리스트의 특정 인덱스 새로운 아이템을 더할 수 있다.
number_list.insert(2, 123) # 인덱스 2에 해당하는 위치에 123을 추가한다.
# 여기서 number_list는 [8, 2, 123, 3, 4, 5]다.
number_list.insert(999, 123) # 인덱스 999에 해당하는 위치에 123을 추가한다.
# 인덱스 999에 해당하는 자리가 없지만, 조용히 끝에 더해준다.
# 여기서 number_list는 [8, 2, 123, 3, 4, 5, 123]이다.
# 리스트의 특정 인덱스의 아이템을 제거할 수 있다.
del number_list[3] # 인덱스 3에 해당하는 위치에 있는 아이템을 제거한다.
# 여기서 number_list는 [8, 2, 123, 4, 5, 123]이다.
del number_list[-2] # 인덱스 -2, 즉, 뒤에서 두 번재 있는 아이템을 제거한다.
# 여기서 number_list는 [8, 2, 123, 4, 123]이다.
# 리스트에 있는 특정 아이템을 찾아서 제거할 수도 있다.
number_list.remove(2) # 리스트에서 2를 찾아서 제거한다.
# 여기서 number_list는 [8, 123, 4, 123]이다.
number_list.remove(123) # 리스트에서 123을 찾아서 제거한다.
# 이때, 앞에 있는 아이템을 먼저 찾아서 제거한다.
# 여기서 number_list는 [8, 4, 123]이다.
number_list.remove(123)
# 여기서 number_list는 [8, 4]다.
number_list.remove(123) # 없는 값을 제거하려고 시도하면 에러가 발생한다.
// js에는 Array(배열)를 통해 위의 자료구조가 구현되어 있다.
// 아래와 같이 배열을 생성한다.
let numberList = [1, 2, 3, 4, 5];
// 배열의 길이가 곧 배열에 들어있는 아이템 개수다.
let listLength = numberList.length; // 5
// 아래와 같이 배열의 특정 인덱스에 접근한다.
let n = numberList[0]; // 0번째 인덱스에는 1이 들어있다.
n = numberList[3]; // 3번째 인덱스에는 4가 들어있다.
n = numberList[numberList.length - 1]; // 마지막 아이템에 접근. n은 5다.
// 마지막 인덱스는 4다. 인덱스 5에 해당하는 아이템은 존재하지 않는다.
n = numberList[5]; // 에러는 나지 않지만, undefined가 된다.
// 특정 인덱스에 있는 값을 바꿀 수도 있다.
numberList[0] = 8; // numberList가 [8, 2, 3, 4, 5]가 된다.
// 배열의 끝에 새로운 아이템을 더할 수 있다.
numberList.push(123); // numberList가 [8, 2, 3, 4, 5, 123]이 된다.
// 배열의 끝에 있는 아이템을 제거할 수 있다.
let val = numberList.pop(); // val에는 제거한 값이 들어간다.
// 여기서 val은 123, numberList는 [8, 2, 3, 4, 5]다.
// 배열의 특정 인덱스에 새로운 아이템을 더할 수 있다.
numberList.splice(2, 0, 123); // 인덱스 2에 123을 추가한다.
// 여기서 numberList는 [8, 2, 123, 3, 4, 5]다.
// 배열의 특정 인덱스의 아이템을 제거할 수 있다.
numberList.splice(3, 1); // 인덱스 3에서 1개의 아이템을 제거한다.
// 여기서 numberList는 [8, 2, 123, 4, 5]다.
// 배열에 있는 특정 아이템을 찾아서 제거할 수도 있다.
let idx = numberList.indexOf(2); // 2가 있는 인덱스를 찾는다.
if (idx !== -1) numberList.splice(idx, 1); // 찾았으면 제거한다.
// 여기서 numberList는 [8, 123, 4, 5]다.
using System.Collections.Generic;
// C#에는 List<T>를 통해 위의 자료구조가 구현되어 있다.
// 아래와 같이 리스트를 생성한다.
List<int> numberList = new List<int> {1, 2, 3, 4, 5};
// 리스트의 길이가 곧 리스트에 들어있는 아이템 개수다.
int listLength = numberList.Count; // 5
// 아래와 같이 리스트의 특정 인덱스에 접근한다.
int n = numberList[0]; // 0번째 인덱스에는 1이 들어있다.
n = numberList[3]; // 3번째 인덱스에는 4가 들어있다.
n = numberList[numberList.Count - 1]; // 마지막 아이템에 접근. n은 5다.
// 특정 인덱스에 있는 값을 바꿀 수도 있다.
numberList[0] = 8; // numberList가 [8, 2, 3, 4, 5]가 된다.
// 리스트의 끝에 새로운 아이템을 더할 수 있다.
numberList.Add(123); // numberList가 [8, 2, 3, 4, 5, 123]이 된다.
// 리스트의 끝에 있는 아이템을 제거할 수 있다.
numberList.RemoveAt(numberList.Count - 1);
// 여기서 numberList는 [8, 2, 3, 4, 5]다.
// 리스트의 특정 인덱스에 새로운 아이템을 더할 수 있다.
numberList.Insert(2, 123); // 인덱스 2에 123을 추가한다.
// 여기서 numberList는 [8, 2, 123, 3, 4, 5]다.
// 리스트의 특정 인덱스의 아이템을 제거할 수 있다.
numberList.RemoveAt(3); // 인덱스 3에 있는 아이템을 제거한다.
// 여기서 numberList는 [8, 2, 123, 4, 5]다.
// 리스트에 있는 특정 아이템을 찾아서 제거할 수도 있다.
numberList.Remove(2); // 리스트에서 2를 찾아서 제거한다.
// 여기서 numberList는 [8, 123, 4, 5]다.
#include <vector>
#include <algorithm>
using namespace std;
// C++에는 vector<T>를 통해 위의 자료구조가 구현되어 있다.
// 아래와 같이 벡터를 생성한다.
vector<int> numberList = {1, 2, 3, 4, 5};
// 벡터의 길이가 곧 벡터에 들어있는 아이템 개수다.
int listLength = numberList.size(); // 5
// 아래와 같이 벡터의 특정 인덱스에 접근한다.
int n = numberList[0]; // 0번째 인덱스에는 1이 들어있다.
n = numberList[3]; // 3번째 인덱스에는 4가 들어있다.
n = numberList.back(); // 마지막 아이템에 접근. n은 5다.
// 특정 인덱스에 있는 값을 바꿀 수도 있다.
numberList[0] = 8; // numberList가 [8, 2, 3, 4, 5]가 된다.
// 벡터의 끝에 새로운 아이템을 더할 수 있다.
numberList.push_back(123); // numberList가 [8, 2, 3, 4, 5, 123]이 된다.
// 벡터의 끝에 있는 아이템을 제거할 수 있다.
numberList.pop_back();
// 여기서 numberList는 [8, 2, 3, 4, 5]다.
// 벡터의 특정 인덱스에 새로운 아이템을 더할 수 있다.
numberList.insert(numberList.begin() + 2, 123); // 인덱스 2에 123을 추가한다.
// 여기서 numberList는 [8, 2, 123, 3, 4, 5]다.
// 벡터의 특정 인덱스의 아이템을 제거할 수 있다.
numberList.erase(numberList.begin() + 3); // 인덱스 3에 있는 아이템을 제거한다.
// 여기서 numberList는 [8, 2, 123, 4, 5]다.
// 벡터에 있는 특정 아이템을 찾아서 제거할 수도 있다.
auto it = find(numberList.begin(), numberList.end(), 2);
if (it != numberList.end()) numberList.erase(it);
// 여기서 numberList는 [8, 123, 4, 5]다.
이름을 통해 접근할 수 있는 자료구조
이름을 통해 접근할 수 있는 자료구조는 일반적으로 다음의 특징을 가지고 있다.
- {x: a, y: b, z: c} 꼴로 표기한다.
- x, y, z를 키(key), a, b, c를 값(value)이라고 하고, (x, a)를 키-값 쌍(key-value pair)이라고 한다.
- 키는 보통 string, 혹은 int타입의 값을 사용한다.
- 꼭 그래야 하는 것은 아니지만, 많은 경우 이렇게 사용할 것이다.
- 자세한 설명은 이후 관련 내용을 다뤄야 할때 설명하도록 하겠다.
- 특정한 키를 통해 값에 접근할 수 있다.
- 위의 예시에서는 x를 통해 a에 접근할 수 있다.
- 그렇기 때문에 키값은 중복될 수 없다.
- 즉, {x: a, x: b} 같이 x라는 키가 두 번 사용될 수 없다.
- 키를 통해 접근한 값을 수정할 수 있다.
- 새로운 키와 값을 할당할 수 있다.
- 키값을 통해 키-값 쌍을 제거할 수 있다.
# 파이썬에는 dictionary(딕셔너리, 줄여서 dict)를 통해 위의 자료구조가 구현되어 있다.
# 아래와 같이 딕셔너리를 생성한다.
sample_dict = {'a': 123, 10: [1, 2, 3]}
# 키를 통해 값에 접근이 가능하다.
x = sample_dict['a'] # x에 123이 들어간다.
x = sample_dict[10] # x에 [1, 2, 3]이 들어간다.
# 키를 통해 접근한 값을 변경할 수 있다.
sample_dict['a'] += 10 # 값에 접근해서 값을 수정했다.
# 여기서 sample_dict는 {'a': 133, 10: [1, 2, 3]}이 된다.
sample_dict[10] = 42 # 특정 키에 들어있는 값을 덮어썼다.
# 여기서 sample_dict는 {'a': 133, 10: 42}가 된다.
# 새로운 키-값 쌍을 추가할 수 있다.
sample_dict['x'] = 123 # 'x'라는 키에 123이라는 값을 할당했다.
# 여기서 sample_dict는 {'a': 133, 10: 42, 'x': 123}이 된다.
# 123이라는 값이 두 번 들어갔다. 값은 중복이 되어도 상관 없다.
# 키를 통해 키-값 쌍을 제거할 수 있다.
del sample_dict['a'] # 'a'라는 키와 이에 할당된 값을 제거했다.
# 여기서 sample_dict는 {'a': 133, 10: 42, 'x': 123}이 된다.
// 자바스크립트에는 Object(객체)와 Map을 통해 위의 자료구조가 구현되어 있다.
// 여기에서는 Object를 사용하는 방법을 설명하겠다.
// 아래와 같이 객체를 생성한다.
let sampleDict = {'a': 123, 10: [1, 2, 3]};
// 키를 통해 값에 접근이 가능하다.
let x = sampleDict['a']; // x에 123이 들어간다.
x = sampleDict[10]; // x에 [1, 2, 3]이 들어간다.
// 키를 통해 접근한 값을 변경할 수 있다.
sampleDict['a'] += 10; // 값에 접근해서 값을 수정했다.
// 여기서 sampleDict는 {'a': 133, 10: [1, 2, 3]}이 된다.
sampleDict[10] = 42; // 특정 키에 들어있는 값을 덮어썼다.
// 여기서 sampleDict는 {'a': 133, 10: 42}가 된다.
// 새로운 키-값 쌍을 추가할 수 있다.
sampleDict['x'] = 123; // 'x'라는 키에 123이라는 값을 할당했다.
// 여기서 sampleDict는 {'a': 133, 10: 42, 'x': 123}이 된다.
// 키를 통해 키-값 쌍을 제거할 수 있다.
delete sampleDict['a']; // 'a'라는 키와 이에 할당된 값을 제거했다.
// 여기서 sampleDict는 {10: 42, 'x': 123}이 된다.
using System.Collections.Generic;
// C#에는 Dictionary<TKey, TValue>를 통해 위의 자료구조가 구현되어 있다.
// 아래와 같이 딕셔너리를 생성한다.
// C#의 Dictionary는 키 타입이 통일되어야 하므로, 여기서는 string 키를 사용한다.
Dictionary<string, int> sampleDict = new Dictionary<string, int> {
{"a", 123},
{"b", 456}
};
// 키를 통해 값에 접근이 가능하다.
int x = sampleDict["a"]; // x에 123이 들어간다.
x = sampleDict["b"]; // x에 456이 들어간다.
// 키를 통해 접근한 값을 변경할 수 있다.
sampleDict["a"] += 10; // 값에 접근해서 값을 수정했다.
// 여기서 sampleDict["a"]는 133이 된다.
sampleDict["b"] = 42; // 특정 키에 들어있는 값을 덮어썼다.
// 여기서 sampleDict["b"]는 42가 된다.
// 새로운 키-값 쌍을 추가할 수 있다.
sampleDict["x"] = 123; // "x"라는 키에 123이라는 값을 할당했다.
// 키를 통해 키-값 쌍을 제거할 수 있다.
sampleDict.Remove("a"); // "a"라는 키와 이에 할당된 값을 제거했다.
#include <unordered_map>
#include <string>
#include <vector>
using namespace std;
// C++에는 unordered_map<Key, Value>를 통해 위의 자료구조가 구현되어 있다.
// 아래와 같이 맵을 생성한다.
// C++의 unordered_map은 키 타입이 통일되어야 하므로, 여기서는 string 키를 사용한다.
unordered_map<string, int> sampleDict = {
{"a", 123},
{"b", 456}
};
// 키를 통해 값에 접근이 가능하다.
int x = sampleDict["a"]; // x에 123이 들어간다.
x = sampleDict["b"]; // x에 456이 들어간다.
// 키를 통해 접근한 값을 변경할 수 있다.
sampleDict["a"] += 10; // 값에 접근해서 값을 수정했다.
// 여기서 sampleDict["a"]는 133이 된다.
sampleDict["b"] = 42; // 특정 키에 들어있는 값을 덮어썼다.
// 여기서 sampleDict["b"]는 42가 된다.
// 새로운 키-값 쌍을 추가할 수 있다.
sampleDict["x"] = 123; // "x"라는 키에 123이라는 값을 할당했다.
// 키를 통해 키-값 쌍을 제거할 수 있다.
sampleDict.erase("a"); // "a"라는 키와 이에 할당된 값을 제거했다.