언어 자료구조 알고리즘/[C++]디딤돌 자료구조와 알고리즘

3.1.5 vector를 인덱스 연산으로 사용 [디딤돌 자료구조와 알고리즘 with C++]

언제나휴일 2016. 4. 4. 10:28
반응형

3.1.5 vector를 인덱스 연산으로 사용

vector는 확장 가능한 배열로 연속적인 메모리에 자료를 보관하는 자료구조입니다. 연속적인 메모리에 자료를 보관하기 때문에 메모리의 시작 위치에서 상대적 거리를 통해 원하는 요소를 접근하게 프로그래밍하면 접근 비용이 거의 들지 않습니다.

 

프로그래밍 언어에서 제공하는 배열은 거의 모두 인덱스 연산을 사용하여 배열의 원소에 접근하는 것을 제공하고 있습니다.

 

STLvector에서도 인덱스 연산을 제공합니다. 주의할 점은 vector에는 저장소의 크기와 보관한 자료 개수를 기억하는 멤버가 있는데 인덱스 연산으로 사용할 때는 0에서 보관한 자료 개수 -1 까지 사용할 수 있어요. 따라서 보관한 자료에 접근하거나 변경할 때만 사용할 수 있다는 것입니다.

 

따라서 STLvector를 인덱스 연산으로 사용할 때는 미리 보관할 최대 개수만큼 디폴트 값(대부분 0이 적합하겠죠.)을 보관해 두었다가 원하는 자료로 변경하는 형태로 사용하면 좋습니다.

 

앞에서 작성했던 프로그램을 다시 한 번 인덱스 연산으로 사용하는 프로그램을 만들어 보기로 해요.

 

작성할 프로그램은 장르 관리 프로그램입니다. 장르에는 장르 번호와 장르명이 있습니다. 최대 장르 번호는 프로그램 시작하면서 입력받습니다. 장르 번호와 장르명은 사용자에게 입력받습니다. 장르 번호와 장르명으로 삭제할 수 있고 검색할 수 있습니다. 그리고 모든 장르 목록을 확인할 수 있습니다.

 

제공할 기능을 살펴보면 장르 추가, 장르 번호로 장르 삭제, 장르명으로 장르 삭제, 장르 번호로 검색, 장르명으로 검색, 모든 장르 목록 확인이 있어요.

 

먼저 App 클래스에 최대 장르 번호를 기억할 멤버 필드를 선언하세요.

class App

{

    Genres genres;

    int max_genres;//최대 장르 번호

    ...중략...

};

 

App::App(void)

{

최대 장르 번호를 입력받아야겠죠.

    cout<<"최대 장르 번호:";

    max_genres = ehglobal::getnum();

vector를 사용하기 전에 먼저 보관할 수 있는 최대 개수만큼 디폴트 값으로 보관(설정)해야겠죠. 이 때 사용할 멤버는 resize 메서드입니다.

    genres.resize(max_genres,0);//max_genres 만큼 0으로 보관(설정)

}

 

 

이제 추가 기능을 구현합시다.

void App::AddGenre() //장르 추가

{

먼저 추가할 장르 번호를 입력받아야겠죠.

    int num=0;

    cout<<"추가할 장르 번호(1~"<<max_genres<<"):";

    num = ehglobal::getnum();

만약 입력한 번호가 0보다 작거나 같거나 max_genres보다 크면 유효한 장르 번호가 아닙니다.

    if((num<=0)||(num>max_genres))

    {

        cout<<"유효한 장르 번호가 아닙니다."<<endl;

        return;

    }

초기에 모든 요소의 값을 0으로 보관(설정)해 둔 상태입니다. 만약 인덱스 연산으로 보관한 값이 0이 아니면 유효한 자료를 보관한 상태입니다. 여기에서 주의할 점은 번호는 1부터 시작하고 인덱스는 0부터 시작한다는 것이예요.

    if(genres[num-1])//보관한 값이 유효하면(0이 아니면)

    {

        cout<<"이미 있습니다."<<endl;

        return;

    }

장르명을 입력받고 장르를 생서하세요.

    cout<<"추가할 장르명:";

    string name = ehglobal::getstr();//장르명 입력

    Genre *genre = new Genre(num,name); //장르 생성

생성한 장르(유효한 자료)를 보관하는 것은 인덱스 연산을 통해 vector의 원소의 값을 변경하는 것입니다.

    genres[num-1] = genre;//변경

}

 

이제 번호로 장르 삭제 기능을 구현합시다.

void App::RemoveGenreByNum()//번호로 장르 삭제

{

    int num=0;

삭제할 장르 번호를 입력받아야겠죠.

    cout<<"삭제할 장르 번호(1~"<<max_genres<<"):";

    num = ehglobal::getnum();

유효한 번호가 아닌지 확인하는 과정이 필요합니다.

    if((num<=0)||(num>max_genres))

    {

        cout<<"유효한 장르 번호가 아닙니다."<<endl;

        return;

    }

 

인덱스 연산을 통해 vector에 보관한 값을 확인합니다. 만약 보관한 값이 0이면 유효한 장르를 보관한 것이 아닙니다.

    if(genres[num-1]==0)//보관한 값이 0이면

    {

        cout<<"없습니다."<<endl;

        return;

    }

이 곳에 도달하였다면 return문을 만나지 않은 것이므로 인덱스 연산으로 확인한 값이 0이 아닐 때입니다.

 

보관한 장르르 소멸하세요.

    delete genres[num-1];

그리고 인덱스 연산으로 초기값으로 변경하세요.

    genres[num-1]=0; //초기값으로 설정   

}

 

이름으로 장르 삭제 기능을 구현합시다. 앞에서 검색에 사용할 EqualerByName 클래스를 수정해야 합니다.

class EqualerByName

{

    string name;

public:

    EqualerByName(string name)

    {

        this->name = name;

    }

    bool operator()(const Genre *genre)const

    {

vector를 인덱스 연산으로 사용하기 위해 모든 요소를 0으로 초기화한 상태로 출발하였습니다. 따라서 vector에 보관한 값이 0일 때는 개체의 메서드를 호출하면 오류가 발생합니다. 이에 genre0일 때는 원하는 장르가 아니므로 거짓을 반환하게 수정하세요.

        if(genre==0)

        {

            return false;

        }

        return name.compare(genre->GetName())==0;

    }

};

 

void App::RemoveGenreByName()//이름으로 장르 삭제

{

먼저 삭제할 장르명을 입력받아야겠죠.

    string name;

    cout<<"삭제할 장르명:";

    name = ehglobal::getstr();

이름으로 검색할 함수 개체를 생성하세요.

    EqualerByName ebname(name);

find_if 알고리즘으로 보관한 위치를 탐색합니다.

    GIter seek = find_if(genres.begin(), genres.end(), ebname);

만약 반환한 값이 end 메서드 호출 결과와 같으면 보관한 장르가 없는 것입니다.

    if(seek != genres.end())

    {

탐색한 반복자의 간접 연산으로 보관한 장르를 구합니다.

        Genre *genre = (*seek);

그리고 인덱스 연산에 사용한 장르 번호를 구합니다.

        int num = genre->GetNum();       

이제 장르 개체를 소멸하세요.

        delete genre;

인덱스 연산으로 디폴트 값으로 다시 설정하세요.

        genres[num-1] = 0;

        cout<<"삭제하였습니다."<<endl;

    }

    else

    {

        cout<<"존재하지 않는 장르명입니다."<<endl;

    }

}

 

번호로 장르 검색 기능을 구현합시다. 번호로 장르 삭제 기능과 찾는 부분까지는 똑같습니다.

void App::FindGenreByNum()const //번호로 장르 검색

{

    int num=0;

    cout<<"검색할 장르 번호(1~"<<max_genres<<"):";

    num = ehglobal::getnum();

    if((num<=0)||(num>max_genres))

    {

        cout<<"유효한 장르 번호가 아닙니다."<<endl;

        return;

    }

    if(genres[num-1]==0)//보관한 값이 0이면

    {

        cout<<"없습니다."<<endl;

        return;

    }

만약 유효한 장르를 보관했다면 장르의 정보를 출력하세요.

    genres[num-1]->View();

}

 

이름으로 장르 검색 기능은 순차적으로 보관이나 특정 키 순으로 보관할 때와 같습니다. 물론 find_if 알고리즘에 전달할 EqualerByName 형식을 변경했기 때문에 같은 것일 뿐입니다.

void App::FindGenreByName()const //이름으로 장르 검색

{

    string name;

    cout<<"검색할 장르명:";

    name = ehglobal::getstr();

    EqualerByName ebname(name);

    GCIter seek = find_if(genres.begin(), genres.end(), ebname);

    if(seek != genres.end())

    {

        Genre *genre = (*seek);

        genre->View();

    }

    else

    {

        cout<<"존재하지 않는 장르명입니다."<<endl;

    }

}

 

장르 목록 보기를 구현합시다. 인덱스 연산을 통해 유효한 값이 있는 요소를 출력하세요.

void App::ListGenre()const //장르 목록 보기

{

    for(int i = 0; i<max_genres; i++)

    {

        if(genres[i])

        {

            genres[i]->View();

        }

    }

}

 

소멸자도 같은 방법으로 유효한 값이 있는 요소만 소멸하세요.

App::~App(void)

{

    for(int i = 0; i<max_genres; i++)

    {

        if(genres[i])

        {

            delete genres[i];

        }

    }

}

3.1.6 vector를 인덱스 연산으로 사용 예제

ehglobal 클래스는 3.1.1 이 책에서 공통으로 사용할 것들 항목을 참고하세요. 그리고 Genre 클래스와 진입점 main 함수는 3.1.3 vector에 순차적으로 보관하는 예제를 참고하세요.

 

여기에서는 App 클래스만 보여드릴게요.

//App.h

#pragma once

#include "Genre.h"

#include "ehglobal.h"

#include <vector>

#include <algorithm>

using std::vector;

using std::find;

using std::find_if;

 

typedef vector<Genre *> Genres;

typedef Genres::iterator GIter;

typedef Genres::const_iterator GCIter;

class App

{

    Genres genres;

    int max_genres;//최대 장르 번호

public:

    App(void);

    ~App(void);

    void Run();

private:

    int SelectMenu();

    void AddGenre(); //장르 추가

    void RemoveGenreByNum();//번호로 장르 삭제

    void RemoveGenreByName();//이름으로 장르 삭제

    void FindGenreByNum()const; //번호로 장르 검색

    void FindGenreByName()const; //이름으로 장르 검색

    void ListGenre()const; //장르 목록 보기

};

 

//App.cpp

#include "App.h"

class EqualerByName

{

    string name;

public:

    EqualerByName(string name)

    {

        this->name = name;

    }

    bool operator()(const Genre *genre)const

    {

        if(genre==0)

        {

            return false;

        }

        return name.compare(genre->GetName())==0;

    }

};

App::App(void)

{

    cout<<"최대 장르 번호:";

    max_genres = ehglobal::getnum();

    genres.resize(max_genres,0);//max_genres 만큼 0으로 보관(설정)

}

App::~App(void)

{

    for(int i = 0; i<max_genres; i++)

    {

        if(genres[i])

        {

            delete genres[i];

        }

    }

}

void App::Run()

{

    int key=NO_DEFINED;

    while((key = SelectMenu())!=ESC)

    {

        switch(key)

        {

        case F1: AddGenre(); break;

        case F2: RemoveGenreByNum(); break;

        case F3: RemoveGenreByName(); break;

        case F4: FindGenreByNum(); break;

        case F5: FindGenreByName(); break;

        case F6: ListGenre(); break;

        default: cout<<"잘못 선택하셨습니다."<<endl; break;

        }

        cout<<"아무 키나 누르세요."<<endl;

        ehglobal::getkey();

    }

}

int App::SelectMenu()

{

    ehglobal::clrscr();//콘솔 화면을 지우기

    cout<<"장르 관리 프로그램 [ESC]: 종료"<<endl;

    cout<<"F1: 장르 추가 F2: 번호로 장르 삭제 F3: 이름으로 장르 삭제"<<endl;

    cout<<"F4: 번호로 장르 검색 F5: 이름으로 장르 검색 F6: 장르 목록 보기"<<endl;

    return ehglobal::getkey();//사용자가 입력한 기능 키 반환

}

 

void App::AddGenre() //장르 추가

{

    int num=0;

    cout<<"추가할 장르 번호(1~"<<max_genres<<"):";

    num = ehglobal::getnum();

    if((num<=0)||(num>max_genres))

    {

        cout<<"유효한 장르 번호가 아닙니다."<<endl;

        return;

    }

    if(genres[num-1])//보관한 값이 유효하면(0이 아니면)

    {

        cout<<"이미 있습니다."<<endl;

        return;

    }

    cout<<"추가할 장르명:";

    string name = ehglobal::getstr();//장르명 입력

    Genre *genre = new Genre(num,name); //장르 생성

    genres[num-1] = genre;//변경

}

void App::RemoveGenreByNum()//번호로 장르 삭제

{

    int num=0;

    cout<<"삭제할 장르 번호(1~"<<max_genres<<"):";

    num = ehglobal::getnum();

    if((num<=0)||(num>max_genres))

    {

        cout<<"유효한 장르 번호가 아닙니다."<<endl;

        return;

    }

    if(genres[num-1]==0)//보관한 값이 0이면

    {

        cout<<"없습니다."<<endl;

        return;

    }

   

    delete genres[num-1];

    genres[num-1]=0; //초기값으로 설정   

}

 

void App::RemoveGenreByName()//이름으로 장르 삭제

{

    string name;

    cout<<"삭제할 장르명:";

    name = ehglobal::getstr();

    EqualerByName ebname(name);

 

    GIter seek = find_if(genres.begin(), genres.end(), ebname);

    if(seek != genres.end())

    {

        Genre *genre = (*seek);

        int num = genre->GetNum();       

        delete genre;

        genres[num-1] = 0;

        cout<<"삭제하였습니다."<<endl;

    }

    else

    {

        cout<<"존재하지 않는 장르명입니다."<<endl;

    }

}

 

void App::FindGenreByNum()const //번호로 장르 검색

{

    int num=0;

    cout<<"검색할 장르 번호(1~"<<max_genres<<"):";

    num = ehglobal::getnum();

    if((num<=0)||(num>max_genres))

    {

        cout<<"유효한 장르 번호가 아닙니다."<<endl;

        return;

    }

 

    if(genres[num-1]==0)//보관한 값이 0이면

    {

        cout<<"없습니다."<<endl;

        return;

    }

    genres[num-1]->View();

}

 

void App::FindGenreByName()const //이름으로 장르 검색

{

    string name;

    cout<<"검색할 장르명:";

    name = ehglobal::getstr();

    EqualerByName ebname(name);

 

    GCIter seek = find_if(genres.begin(), genres.end(), ebname);

    if(seek != genres.end())

    {

        Genre *genre = (*seek);

        genre->View();

    }

    else

    {

        cout<<"존재하지 않는 장르명입니다."<<endl;

    }

}

 

void App::ListGenre()const //장르 목록 보기

{   

    for(int i = 0; i<max_genres; i++)

    {

        if(genres[i])

        {

            genres[i]->View();

        }

    }

}

반응형