알고리즘 사용, 프로그램 계획
STL은 이터레이터를 통해 컨테이너의 요소를 조작할 수 있는 일련의 알고리즘을 정의한다.
이 알고리즘들은 검색, 랜점, 정렬가 같은 일반적인 작업을 위해 존재한다.
제너릭한 방법으로 컨테이너 요소를 조작하는 단순한 작업을 STL에 맡길 수 있다.
이 알고리즘들의 강력한 점을 그들이 일반적, 제너릭하다는 점이다.
제너릭 프로그래밍은 다양한 유형의 데이터나 자료구조에 대해 일반적인 알고리즘을 작성하는 방법론이다.
데이터나 자료구조의 특정 유형에 의존하지 않고, 일반적으로 작동하는 코드를 작성할 수 있도록 해준다.
제네릭을 사용하면 코드의 재사용성이 향상되고, 유지보수가 쉬워지며, 더 깔끔하고 효율적인 코드를 작성할 수 있다.
// High Scores
// Demonsrates algorithms
#include <iostream>
#include <vector>
#include <algorithm>
#include <ctime>
#include <cstdlib>
using namespace std;
int main() {
vector<int>::const_iterator iter;
cout << "Creating a list of scores.";
vector<int> scores;
scores.push_back(1500);
scores.push_back(3500);
scores.push_back(7500);
cout << "\nHigh scores:\n";
for (iter = scores.begin(); iter != scores.end(); ++iter) {
cout << *iter << endl;
}
cout << "\nFinding a score.";
int score;
cout << "\nEnter a score to find: ";
cin >> score;
iter = find(scores.begin(), scores.end(), score);
if (iter != scores.end()) {
cout << "Score found.\n";
}
else {
cout << "Socre not found.\n";
}
cout << "\nRandomizing scores.";
srand(static_cast<unsigned int>(time(0)));
random_shuffle(scores.begin(), scores.end());
cout << "\nHigh Scores:\n";
for (iter = scores.begin(); iter != scores.end(); ++iter) {
cout << *iter << endl;
}
cout << "\nSorting scores.";
sort(scores.begin(), scores.end());
cout << "\nHigh Socres:\n";
for (iter = scores.begin(); iter != scores.end(); ++iter) {
cout << *iter << endl;
}
return 0;
}
STL 알고리즘을 사용하기 위해 알고리즘 정의가 들어 있는 파일을 포함시킨다. -> #include <algorithm>
모든 STL 구성 요소는 std 네임스페이스에 속해있다.
find() STL 알고리즘
컨테이너 요소의 지정된 범위에서 값을 검색한다.
일치하는 첫 번째 요소를 참조하는 이터레이터를 반환한다.
일치하는 값이 없으면 범위의 끝을 참조하는 이터레이터를 반환한다.
위의 코드에서는 시작 이터레이터, 끝 이터레이터, 찾을 값 3개의 인수를 전달했다.
전체 값을 검색하여 score 값이 발견되지 않은 경우, iter와 scores.end()가 동일해진다.
random_shuffle 알고리즘
점수를 무작위로 재배열 한다.
단일 난수를 생성할 때와 마찬가지로 난수 생성기에 시드를 제공하여 프로그램을 실행할 때마다 점수의 순서가 다르도록 한다.
이 알고리즘은 시퀀스의 요소를 무작위로 섞는다.
시퀀스의 시작점과 끝점에 대한 이터레이터를 인수로 제공해야 한다.
sort() 알고리즘
시퀀스의 요소를 오름차순으로 정렬한다.
시퀀스의 시작점과 끝점에 대한 이터레이터를 인수로 제공해야 한다.
+ STL 알고리즘의 매우 멋진 특성 중 하나는 STL 외부에 정의된 컨테이너와 함께 작동할 수 있다는 점이다.
이러한 컨테이너는 특정 요구 사항을 충족하기만 하면 된다.
예를 들어, string 객체는 STL의 일부가 아니다. 그렇지만 적절한 STL 알고리즘을 사용할 수 있다.
string word = "High Scores";
random_shuffle(word.begin(), word.end());
컨테이너에는 각각의 장단점이 있으며, 프로그래머는 작업에 적합한 올바른 컨테이너 유형을 선택할 수 있도록 각 컨테이너 유형의 성능 특성을 이해해야 한다.
벡터는 필요에 따라 동적으로 확장하지만, 모든 벡터는 특정 크기를 가지고 있다.
새로운 요소를 벡터에 추가하여 현재 크기를 넘어서면, 컴퓨터는 메모리를 재할당하고 심지어 모든 벡터 요소를 이 새롭게 할당한 메모리 공간으로 복사한다. 이는 성능에 영향을 줄 수 있다.
벡터 메모리 재할당이 프로그램의 성능에 중요하지 않는 경우 재할당 비용을 안전하게 무시할 수 있다.
작은 벡터의 경우 재할당 비용이 무시할 만큼 미미할 수 있다.
capacity() 벡터 멤버 함수
벡터의 용량을 반환한다.
즉, 프로그램이 더 많은 메모리를 할당하기 전에 벡터가 보유할 수 있는 요소의 수이다.
벡터의 용량은 현재 크기와 같지 않다.
cout << "10개의 요소를 보유할 벡터를 생성합니다.\n";
vector<int> scores(10, 0); // 모든 10개 요소를 0으로 초기화
cout << "벡터 크기는 :" << scores.size() << endl; //10
cout << "벡터 용량은:" << scores.capacity() << endl; //10
cout << "점수를 추가합니다.\n";
scores.push_back(0); // 성장을 수용하기 위해 메모리가 재할당됨
cout << "벡터 크기는 :" << scores.size() << endl; //11
cout << "벡터 용량은:" << scores.capacity() << endl; //20
요소가 추가된 후에 벡터의 크기는 11이고 용량은 20이다.
프로그램이 추가 메모리를 위해 메모리를 재할당할 때마다 벡터의 용량이 두 배로 증가하기 때문이다.
reserve() 멤버 함수
인수로 제공된 수의 요소를 수용할 수 있도록 벡터의 용량을 증가시킨다.
이 함수를 사용하면 추가 메모리 재할당이 말생하는 시점을 제어할 수 있다.
cout << "점수 목록을 생성합니다.\n";
vector<int> scores(10, 0); // 모든 10개 요소를 0으로 초기화
cout << "벡터 크기는 :" << scores.size() << endl;
cout << "벡터 용량은:" << scores.capacity() << endl;
cout << "더 많은 메모리를 예약합니다.\n";
scores.reserve(20); // 10개의 추가 요소에 대한 메모리 예약
cout << "벡터 크기는 :" << scores.size() << endl;
cout << "벡터 용량은:" << scores.capacity() << endl;
10개의 추가 요소에 대한 메모리를 예약한 후에도,
벡터의 크기는 여전히 10이고 용량은 20이 된다.
reserve() 함수를 사용하여 벡터의 용량을 충분히 크게 유지함으로써, 메모리 재할당을 원하는 시점으로 지연시킬 수 있다.
벡터에 대해 추가 또는 삭제를 수행할 때, push_back() 또는 pop_back() 함수를 사용하여 벡터 끝에서 요소를 추가하거나 제거하는 것이 극도록 효율적이다.
그러나 벡터의 다른 위치에 요소를 추가하거나 제거해야 하는 경우(insert() 또는 erase()), 여러 요소를 이동해야 할 수 있기 때문에 더 많은 작업이 필요할 수 있다.
작은 벡터의 경우에는 이러한 오버헤드가 무시할 수 있지만, 큰 벡터의 경우 중간에 요소를 삽입하거나 제거하는 것이 성능에 영향을 줄 수 있다.
다행히도, STL은 sequence container 유형 중 하나인 list를 제공하여 시퀀스 크기에 관계없이 효율적인 삽입 및 삭제가 가능하다.
STL 컨테이너
두 가지 기본 카테고리로 나뉜다.
1. 시퀀셜 컨테이너 : 시퀀스에서 값을 검색
2. 연관 컨테이너 : 키를 기반으로 값을 검색
게임을 예로 들면,
시퀀스를 순환하고 싶은 플레이어 그룹을 저장하는 데에는 벡터와 같은 시퀀셜 컨테이너를 사용한다.
고유한 식별자인 플레이어의 IP 주소를 찾아 랜덤한 방식으로 플레이어 정보를 검색하고 싶은 경우, 연관 컨테이너를 사용한다.
STL은 하나의 시퀀스 컨테이너를 적응시키는 컨테이너 어댑터를 정의한다.
컨테이너 어댑터는 STl에서 제공하는 컨테이너 유형 중 하나이다.
다른 컨테이너를 적응시켜서 새로운 인터페이스를 제공하는 방법으로 구현된다.
기존의 컨테이너를 다른 형태로 사용하거나 확장할 수 있도록 한다.
스택, 큐, 우선순위 큐 등이 해당된다.
프로그램 계획
프로그램을 계획하는 것은 프로그램이 효율적으로 잘 구조화되고 목표를 달성하게 되는 데에 중요하다.
계획하는 일반적인 방법 중 하나는 의사 코드(pseudocode)를 사용하는 것이다.
의사 코드는 프로그래밍 언어와 비슷한 일반 언어를 사용하여 프로그램 로직을 높은 수준에서 설명하는 것이다.
의사 코드는 프로그래밍 언어에 익숙하지 않은 사람도 이해할 수 있어야 한다.
새로운 유용한 제품을 생각할 수 있다면
그것이 당신의 제품입니다
그렇지 않으면
기존 제품을 재패키징하여 당신의 제품으로 만듭니다
당신의 제품에 대한 인포머셜을 만듭니다
TV에서 인포머셜을 방영합니다
제품 당 $100을 청구합니다
당신의 제품을 10,000개 판매합니다
의사 코드가 프로그램 코드와 유사하게 느껴져야 한다.
단계별 세분화를 사용하면 의사 코드를 프로그래밍 코드로 재작성할 수 있다.
단계별 세분화는 매우 간단한 과정이다.
기본적으로 "세부 사항을 더 구체화하라"는 의미이다.
의사 코드에서 설명된 각 단계를 가져와서 간단한 단계의 시리즈로 나누면 계획이 프로그래밍 코드에 더 가까워진다.
각 단계를 계속해서 세분화하다가 전체 계획이 프로그램으로 비교적 쉽게 번역될 수 있을 때까지 진행한다.
위 코드에서 '당신의 제품에 대한 인포머셜을 만듭니다'를 세분화하면
당신의 제품에 대한 인포머셜에 대한 대본을 작성합니다
하루동안 TV 스튜디오를 빌립니다
제작 크루를 고용합니다
열정적인 관객을 고용합니다
인포머셜을 촬영합니다
이 다섯단계가 명확하고 달성 가능하다고 생각되면 해당 부분의 의사 코드가 충분히 세밀하게 세분화된 것이다.
행맨 프로그램 계획
행맨 프로그램에서 컴퓨터는 비밀 단어를 선택하고 플레이어는 한 번에 한 글자씩 추측한다.
플레이어는 8번의 잘못된 추측이 허용된다.
만약 플레이어가 제한 시간 내에 단어를 추측하지 못하면, 플레이어는 처형되고 게임이 종료된다.
1. 의사코드 작성
단어 그룹을 만든다.
그룹에서 무작위로 단어를 선택하여 비밀 단어로 정한다.
플레이어가 너무 많은 잘못된 추측을 하지 않았고 비밀 단어를 맞히지 않은 동안
플레이어게 남은 잘못된 추측 횟수를 알려준다.
플레이어가 이미 추측한 글자를 보여준다.
플레이어가 지금까지 추측한 비밀 단어의 일부를 보여준다.
플레이어의 다음 추측을 받는다.
플레이어가 이미 추측한 글자를 입력하는 동안
플레이어의 추측을 받는다.
새로운 추측을 이미 사용한 글자 그룹에 추가한다.
만약 추측이 비밀 단어에 있다면
플레이어에게 추측이 맞다고 알려준다.
지금까지 추측한 단어를 새로운 글자로 업데이트한다.
그렇지 않다면
플레이어에게 추측이 잘못되었다고 알려준다.
잘못된 추측 횟수를 증가시킨다.
플레이어가 너무 많은 잘못된 추측을 했다면
플레이어에게 처형되었다고 알려준다.
그렇지 않다면
플레이어가 비밀 단어를 맞힌 것을 축하해준다.
2. 프로그램 설정
일반적으로, 몇 가지 주석으로 시작하고 필요한 파일을 포함시킨다.
// Hangman
// The classic game of hangman
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <ctime>
#include <cctype>
using namespace std;
cctype : 표준 라이브러리 중 하나이며, 문자를 대문자로 변환하는 함수가 포함되어 있다. 개별 문자를 비교할 때 대문자와 대문자를 비교하려고 하기 때문에 사용한다.
3. 변수 및 상수 초기화
main() 함수 작성
int main() {
//setup
const int MAX_WRONG = 8;
vector<string> words; // collection of possible words to guess
words.push_back("GUESS");
words.push_back("HANGMAN");
words.push_back("DIFFICULT");
srand(static_cast<unsigned int>(time(0)));
random_shuffle(words.begin(), words.end());
const string THE_WORD = words[0];
int wrong = 0;
string soFar(THE_WORD.size(), '-');
string used = "";
cout << "Welcome to Hangman. Good Luck!\n";
}
words는 문제로 낼 단어들의 벡터이다.
random_shuffle() 알고리즘을 사용하여 words를 무작위로 섞은 다음, THE_WORD에 벡터의 첫 번째 단어를 할당한다.
wrong은 플레이어가 틀린 횟수이다. soFar는 플레이어가 지금까지 추측한 단어이다.
soFar는 비밀 단어의 각 문자에 해당하는 대시로 싲갛나다.
플레이어가 비밀 단어에 있는 문자를 추측하여 해당 위치의 대시를 해당 문자로 바꾼다.
4. 메인 루프 진입
//main loop
while ((wrong < MAX_WRONG) && (soFar != THE_WORD)) {
cout << "\n\nYou have " << (MAX_WRONG - wrong);
cout << " incorrect guesses left.\n";
cout << "\nYou've used the following letters:\n" << used << endl;
cout << "\nSo far, the word is :\n" << soFar << endl;
}
5. 플레이어에게 추측 입력 받기
char guess;
cout << "\n\nEnter your guess: ";
cin >> guess;
guess = toupper(guess);
while (used.find(guess) != string::npos) {
cout << "\nYou've already guessed "<< guess << endl;
cout << "Enter your guess: ";
cin >> guess;
guess = toupper(guess);
}
used += guess;
if (THE_WORD.find(guess) != string::npos) {
cout << "That's right! " << guess << " is in the word.\n";
//update soFar to include newly guessed letter
for (int i=0; i < THE_WORD.length(); ++i) {
if (THE_WORD[i] == guess) {
soFar[i] = guess;
}
}
}
else {
cout << "Sorry, " << guess << " isn't in the word.\n";
++wrong;
}
만약 플레이어가 이미 추측한 문자를 다시 추측하면, 플레이어에게 다시 추측하라고 한다.
플레이어가 문자를 올바르게 추측하면, 지금까지 추측한 단어를 업데이트한다.
그렇지 않으면, 비밀 단어에 추측이 없다고 알리고, 틀린 횟수를 증가시킨다.
6. 게임 종료
//shut down
if (wrong == MAX_WRONG) {
cout << "\nYou've been hanged!";
}
else {
cout << "\nYou guessed it!";
}
cout << "\nThe word was " << THE_WORD << endl;
return 0;
Q & A
Q : STL이 중요한 이유는 ?
A : 프로그래머에게 시간과 노력을 절약해준다. 일반적으로 사용되는 컨테이너 유형과 알고리즘을 제공한다.
Q : STL은 빠른가 ?
A : 그렇다. 여러 프로그래머에 의해 다양한 플랫폼에서 최대의 성능을 끌어낼 수 있도록 미세 조정되었다.
Q : 언제 배열 대신 벡터를 사용하는가 ?
A : 거의 항상. 벡터는 효율적이고 유연하다. 배열보다 조금 더 많은 메모리가 필요하긴 하지만 거의 항상 이득이다.
Q : 벡터에 대괄호 연산자를 사용할 수 있다면 반복자가 필요한 이유
A : 많은 벡터 함수가 반복자를 필요로 한다(insert(), erase()). 대부분의 STL 컨테이너에서 대괄호 연산자를 사용할 수 없으므로, 언젠가는 사용해야 함.
Q : STL은 왜 여러 시퀀셜 컨테이너 유형을 정의하는가?
A : 서로 다른 시퀀셜 컨테이너는 서로 다른 성능 특성을 가지고 있다.
Q : 컨테이너 어댑터란 ?
A : STL 시퀀스 컨테이너 중 하나를 기반으로 하며 표준 컴퓨터 데이터 구조를 나타낸다.
Q : 점진적 세부화는 언제 ?
A : 의사 코드를 더 자세히 작성하려는 경우
Discussion Questions
- Why should a game programmer use the STL?
- Standard Template Library는 게임 프로그래머가 노력과 시간을 절약할 수 있는 이유이다. 널리 사용되는 컨테이너 유형과 알고리즘을 제공하여 코드 작성과 유지 관리를 단순화한다. 이러한 컨테이너 및 알고리즘은 이미 검증되고 최적화되어 있으며, 다양한 프로그래밍 작업에 사용할 수 있다. 따라서 STL을 사용하면 개발자가 직접 구현할 필요 없이 표준화된 구성요소를 활용하여 빠르고 안정적인 코드를 작성할 수 있다.
- What are the advantages of a vector over an array?
- 크기의 동적 조절 : 배열과 달리 실행 중에 요소를 추가하거나 제거할 수 있다.
- 메모리 관리 : 벡터는 요소가 추가되거나 제거될 때 자동으로 메모리를 재할당하고 관리한다.
- 반복자 지원 : 이터레이터를 통해 요소에 접근할 수 있다. 범위 기반 루프와 같은 반복 작업을 훨씬 간편하게 만든다.
- 알고리즘과의 통합 : STL 알고리즘과의 통합이 용이하다. 벡터는 STL 컨테이너이므로 STL 알고리즘과 함께 사용할 수 있다.
- What types of game objects might you store with a vector?
- 플레이어 캐릭터나, 적 캐릭터
- 아이템이나 강화템
- 장애물 및 지형
- How do performance characteristics of a container type affect the decision to use it?
- 속도 : 컨테이너의 삽입, 삭제, 검색 및 반복 속도는 알고리즘과 자료 구조에 따라 다르다. 특정 작업에 빠르고 효율적이지만 다른 작업에는 느릴 수 있다. 따라서 프로그램의 성능 요구 사항에 맞는 가장 효율적인 컨테이너를 선택해야 한다.
- 메모리 사용량 : 컨테이너는 메모리를 다르게 사용한다.
- 데이터 구조 : 컨테이너의 내부 데이터 구조는 작업을 수행하는 데 중요하다. 작업의 종류와 크기에 따라 가장 적합한 데이터 구조를 선택해야 한다.
- 알고리즘 호환성 : 특정 알고리즘이나 작업에는 특정 컨테이너 타입이 필요할 수 있다.
- Why is program planning important?
- 구조화된 개발 : 전체적인 구조를 파악하고 개발을 구조화할 수 있다. 프로그램의 각 부분이 어떻게 상호작용하고 연결되는 지를 이해하는 데 도움이 된다.
- 문제 해결 : 계획을 세우면 문제를 분석하고 해결할 수 있는 체계적인 방법을 개발할 수 있다.
- 시간과 비용 절감 : 개발 과정에서 발생할 수 있는 문제를 미리 파악하고 예방할 수 있다. 프로젝트의 시간과 비용을 절약하는 데 도움이 된다.
- 의사 소통 : 계획은 팀 간의 의사소통을 촉진한다. 각 구성원이 프로그램의 목표와 요구사항을 이해하고 역할 및 책임을 명확하게 알 수 있다.
Exercises
1. Write a program using vectors and iterators that allows a user to main- tain a list of his or her favorite games. The program should allow the user to list all game titles, add a game title, and remove a game title.
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;
int main() {
vector<string> favoriteGames;
while (true) {
cout << "\nChoose an option:\n";
cout << "1. List all game titles\n";
cout << "2. Add a game title\n";
cout << "3. Remove a game title\n";
cout << "4. Quit\n";
int choice;
cin >> choice;
switch (choice) {
case 1:
if (favoriteGames.empty()) {
cout << "No game titles available.\n";
}
else {
cout << "Your favorite game titles:\n";
for (const string& game : favoriteGames) {
cout << game << endl;
}
}
break;
case 2: {
cout << "Enter the name of the game to add: ";
string newGame;
cin >> newGame;
favoriteGames.push_back(newGame);
break;
}
case 3: {
if (favoriteGames.empty()) {
cout << "No game titles to remove.\n";
}
else {
cout << "Enter the name of the game to remove: ";
string gameToRemove;
cin >> gameToRemove;
auto it = find(favoriteGames.begin(), favoriteGames.end(), gameToRemove);
if (it != favoriteGames.end()) {
favoriteGames.erase(it);
}
else {
cout << "Game title not found.\n";
}
}
break;
}
case 4:
cout << "Exiting program.\n";
return 0;
default:
cout << "Invalid choice. Please try again.\n";
}
}
}
2. Assuming that scores is a vector that holds elements of type int, what’s wrong with the following code snippet (meant to increment each element)?
vector<int>::iterator iter;
//increment each score
for (iter = scores.begin(); iter != scores.end(); ++iter) {
iter++; }
이 코드는 루프 본문에서 iter를 증가시키고 있다.
이미 for 루프에서 ++iter를 사용하여 증가시키고 있기 때문에 추가적으로 호출할 필요가 없다.
본문에서는 '*iter' 를 사용하여 iter 자체가 아니라 iter가 가리키는 요소를 수정해야 한다.
'C++' 카테고리의 다른 글
beginning C++ through game programming / Chap 4 (0) | 2024.05.14 |
---|---|
beginning C++ through game programming / Chap 3-2 (0) | 2024.04.24 |
beginning C++ through game programming / chapter 3 (0) | 2024.04.10 |
beginning C++ through game programming / chapter 2-2 (0) | 2024.04.10 |
beginning C++ through game programming / chapter 2-1 (0) | 2024.04.03 |