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

7.5 STL의 map 사용 [디딤돌 자료구조와 알고리즘 with C++]

언제나휴일 2016. 4. 4. 11:41
반응형

7.5 STL map 사용

이번에는 STLmap 사용 방법을 알아보기로 합시다.

 

map key value를 쌍으로 구성하는 pair를 보관하는 구조입니다. key를 기준으로 자료를 보관하고 검색하며 실질적으로 보관할 자료를 value입니다. 이처럼 키와 값을 쌍으로 보관하는 자료구조를 사전 컬렉션이라고도 부릅니다. 만약 회원 관리 프로그램에서 회원의 id를 기준으로 map에 보관하면 다음처럼 string을 키로 하고 Member * value로 하는 pair를 보관합니다.

#include <map>

using std::map;

using std::pair;

typedef map<string,Member *> Mdic;

 

여기에서는 map을 사용하는 방법을 두 가지 방법으로 나누어 설명할게요. 첫 번째 방법은 insert, find, erase, iterator를 사용하는 방법이고 두 번째 방법은 인덱스 연산자를 사용하는 방법입니다.

 

map을 사용하는 방법을 설명하기 위한 프로그램은 두 방법 모두 회원 관리 프로그램으로 하겠습니다.

 

먼저 두 방법에 공통으로 사용할 회원과 프로토 타이핑을 합시다. 프로젝트를 생성한 후에 공통으로 사용할 파일(3.1.1 참고)을 추가하세요.

 

회원 클래스는 아이디와 이름을 멤버로 갖게 간단히 구현하기로 해요.

//Member.h

#pragma once

#include "ehglobal.h"

class Member

{

아이디를 주요 키로 할 거예요.

    const string id;

    string name;

public:

생성자에서 회원 개체의 정보를 입력받기로 합시다.

    Member(string id,string name);

멤버 필드에 접근할 수 있는 접근자를 제공하세요.

    string GetID()const;

    string GetName()const;

회원 정보를 출력하는 기능도 제공하세요.

    void View()const;

};

 

 

 

회원 클래스의 구체적 구현은 별다른 설명은 하지 않을게요.

Member::Member(string id,string name):id(id)

{

    this->name = name;

}

string Member::GetID()const

{

    return id;

}

string Member::GetName()const

{

    return name;

}

void Member::View()const

{

    cout<<"아이디:"<<id<<" 이름:"<<name<<endl;

}

 

회원 관리 프로그램의 프로토 타이핑도 같습니다. 여기에서는 map에 회원 보관, 삭제, 검색, 전체 보기를 제공할 거예요. 앞에서 작성했던 도서 관리 프로그램과 매우 유사합니다.

map을 사용하려면 map 파일을 포함하세요.

#include <map>

알고리즘도 필요합니다.

#include <algorithm>

mappair를 사용할 거예요.

using std::map;

using std::pair;

키를 회원 ID로 할 것이므로 ID 형식인 stringkey 형식으로 보관할 Member *를 값으로 타입 지정하세요.

typedef map<string, Member *> MDic;

class App

{

map을 멤버 필드로 추가해야겠죠.

    MDic mdic;

public:   

    void Run();

    ~App();

private:

    int SelectMenu();

    void AddMember(); //회원 추가

    void RemoveMember();//회원 삭제

    void FindMember()const; //회원 검색   

    void ListAll()const; //전체 보기

};

프로토 타이핑을 구현합시다.

void App::Run()

{

메뉴를 출력하고 선택한 기능을 수행하는 반복문을 구현하세요.

    int key=NO_DEFINED;

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

    {

        switch(key)

        {

        case F1: AddMember(); break;

        case F2: RemoveMember(); break;

        case F3: FindMember(); break;

        case F4: ListAll(); break;       

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

        }

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

        ehglobal::getkey();

    }

}

int App::SelectMenu()

{

메뉴 출력하고 선택하는 부분도 구현합시다.

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

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

    cout<<"F1: 회원 추가 F2: 회원 삭제 F3: 회원 검색 F4: 전체 보기"<<endl;

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

}

나머지 기능은 빈 상태로 정의하세요.

void App::AddMember() //회원 추가

{   

}

void App::RemoveMember()//회원 삭제

{

}

void App::FindMember()const //회원 검색   

{

}

void App::ListAll()const //전체 보기

{

}

App::~App()

{

}

//Program.cpp

#include "App.h"

int main()

{

    App *app = new App();

    app->Run();

    delete app;

    return 0;

}

 

현재까지 작성한 코드입니다.

//Member.h

#pragma once

#include "ehglobal.h"

class Member

{

    const string id;

    string name;

public:

    Member(string id,string name);

    string GetID()const;

    string GetName()const;

    void View()const;

};

 

//Member.cpp

#include "Member.h"

Member::Member(string id,string name):id(id)

{

    this->name = name;

}

string Member::GetID()const

{

    return id;

}

string Member::GetName()const

{

    return name;

}

void Member::View()const

{

    cout<<"아이디:"<<id<<" 이름:"<<name<<endl;

}

//App.h

#pragma once

#include "ehglobal.h"

#include "Member.h"

#include <map>

#include <algorithm>

using std::map;

using std::pair;

typedef map<string, Member *> MDic;

class App

{

    MDic mdic;

public:   

    void Run();

    ~App();

private:

    int SelectMenu();

    void AddMember(); //회원 추가

    void RemoveMember();//회원 삭제

    void FindMember()const; //회원 검색   

    void ListAll()const; //전체 보기

};

 

//App.cpp

#include "App.h"

void App::Run()

{

    int key=NO_DEFINED;

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

    {

        switch(key)

        {

        case F1: AddMember(); break;

        case F2: RemoveMember(); break;

        case F3: FindMember(); break;

        case F4: ListAll(); break;       

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

        }

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

        ehglobal::getkey();

    }

}

 

int App::SelectMenu()

{

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

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

    cout<<"F1: 회원 추가 F2: 회원 삭제 F3: 회원 검색 F4: 전체 보기"<<endl;

 

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

}

void App::AddMember() //회원 추가

{   

}

void App::RemoveMember()//회원 삭제

{

}

void App::FindMember()const //회원 검색   

{

}

void App::ListAll()const //전체 보기

{

}

App::~App()

{

}

 

//Program.cpp

#include "App.h"

int main()

{

    App *app = new App();

    app->Run();

    delete app;

    return 0;

}

 

,

 

7.5.1 STL map 사용 방법 1

이제 첫 번째 방법인 insert, find, erase, iterator 사용하는 방법으로 구현해 보기로 해요.

 

먼저 반복자를 타입 재지정하세요.

typedef MDic::iterator MIter;

typedef MDic::const_iterator CMIter;

 

먼저 회원 추가 기능을 구현합시다.

void App::AddMember() //회원 추가

{

먼저 주요 키인 아이디를 입력받으세요.

    cout<<"추가할 회원 아이디를 입력하세요."<<endl;

    string id = ehglobal::getstr();

 

같은 키를 갖는 회원 개체가 있는지 탐색하세요.

 

map이나 list에서는 알고리즘 find 혹은 find_if 메서드를 사용해서 탐색했었죠. map도 알고리즘 findfind_if를 사용할 수 있어요. 하지만 이는 순차 탐색하는 것이며 빠른 탐색을 원하면 map에 제공하는 멤버 find 메서드를 사용하세요.

    MIter seek = mdic.find(id); //map find 메서드에 키(회원 아이디)로 위치 찾기

 

만약 반환 값이 end와 같지 않다면 탐색이 성공한 것입니다.

    if(seek != mdic.end())//seek base.end()와 같지 않으므로 seek위치에 보관되어 있음

    {

탐색이 성공했다는 것은 이미 존재하는 아이디이므로 메시지를 출력하고 기능을 종료하세요.

        cout<<"이미 존재하는 아이디입니다."<<endl;

        return;

    }

 

회원 이름을 입력받습니다.

    cout<<"회원 이름을 입력하세요."<<endl;

    string name = ehglobal::getstr();

 

보관할 데이터는 id를 키로 회원 개체를 값으로 하는 pair 입니다.

    //map은 키와 값으로 구성된 pair를 보관

    pair<string, Member *> pd(id,new Member(id,name));

 

추가할 때 insert 메서드를 사용하세요. vectorlist와 다르게 insert 메서드에 위치 정보를 전달하지 않습니다. 이는 map 내부에서 입력받은 pairkey 값으로 탐색할 위치를 찾아 보관하기 때문이예요.

    mdic.insert(pd);

}

 

 

이제 회원 삭제 기능을 구현합시다.

void App::RemoveMember()//회원 삭제

{

삭제할 아이디를 입력받으세요.

    cout<<"삭제할 회원 아이디를 입력하세요."<<endl;

    string id = ehglobal::getstr();

 

삭제할 데이터를 찾을 때도 find 메서드를 사용하세요.

    MIter seek = mdic.find(id); //map find 메서드에 키로 보관된 요소 찾기

    if(seek == mdic.end())//seek base.end()와 같다면 검색 조건에 맞는 요소가 없음

    {

만약 반환한 위치가 end와 같다면 탐색 실패입니다. 메시지를 출력하고 기능을 종료하세요.

        cout<<"존재하지 않는 아이디입니다."<<endl;

        return;

    }

 

map에서는 키와 쌍을 값으로 하는 pair를 보관했습니다. 따라서 반복자의 간접 연산은 pair 형식의 자료입니다. pair 형식은 first 멤버가 key이고 second 멤버가 second입니다. 반복자의 간접 연산으로 접근한 pairsecond 멤버인 회원 개체를 소멸하세요.

    delete (*seek).second; //pair second는 값인 회원 개체 소멸

 

vectorlist처럼 erase 메서드로 반복자 위치에 보관한 자료를 지우세요.

    mdic.erase(seek);

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

}

검색 기능도 삭제 기능과 비슷합니다.

void App::FindMember()const //회원 검색   

{

검색할 아이드를 입력받아 검색하세요.

    cout<<"검색할 회원 아이디를 입력하세요."<<endl;

    string id = ehglobal::getstr();   

    CMIter seek = mdic.find(id); //map find 메서드에 키로 보관된 요소 찾기   

    if(seek == mdic.end())//seek base.end()와 같다면 검색 조건에 맞는 요소가 없음

    {

검색 실패일 때 메시지를 출력하고 기능을 종료하세요.

        cout<<"존재하지 않는 아이디입니다."<<endl;

        return;

    }

반복자의 간접 연산으로 접근한 pairsecond 멤버인 회원 개체의 정보를 출력하세요.

    Member *member = (*seek).second;

    member->View();   

}

 

전체 보기는 반복자를 이용합니다.

void App::ListAll()const //전체 보기

{

    CMIter seek = mdic.begin();

    CMIter end = mdic.end();

    Member *member = 0;   

    //반복자를 사용하여 차례대로 회원의 정보를 출력

    for(  ; seek != end ; ++seek)

    {

반복자의 간접 연산으로 접근한 pairsecond 멤버인 회원 개체의 정보를 출력하세요.

        member = (*seek).second; //pairsecond는 보관한 회원 개체

        member->View();

    }

}

 

App::~App()

{

소멸자에서는 App 내부에서 생성한 회원 개체를 모두 소멸하세요.

    MIter seek = mdic.begin();

    MIter end = mdic.end();

    Member *member = 0;

 

    for(  ; seek != end ; ++seek)

    {

반복자의 간접 연산으로 접근한 pairsecond 멤버인 회원 개체를 소멸하세요.

        member = (*seek).second; //pair second는 보관한 회원 개체

        delete member;

    }

}

 

 

 

다음은 앞에서 구현한 App.cpp 파일의 내용입니다.

//App.cpp

#include "App.h"

 

typedef MDic::iterator MIter;

typedef MDic::const_iterator CMIter;

 

void App::Run()

{

    int key=NO_DEFINED;

 

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

    {

        switch(key)

        {

        case F1: AddMember(); break;

        case F2: RemoveMember(); break;

        case F3: FindMember(); break;

        case F4: ListAll(); break;       

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

        }

 

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

        ehglobal::getkey();

    }

}

 

int App::SelectMenu()

{

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

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

    cout<<"F1: 회원 추가 F2: 회원 삭제 F3: 회원 검색 F4: 전체 보기"<<endl;

 

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

}

 

void App::AddMember() //회원 추가

{   

    cout<<"추가할 회원 아이디를 입력하세요."<<endl;

    string id = ehglobal::getstr();   

 

 

 

    MIter seek = mdic.find(id); //map find 메서드에 키(회원 아이디)로 위치 찾기

 

    if(seek != mdic.end())//seek base.end()와 같지 않으므로 seek위치에 보관되어 있음

    {

        cout<<"이미 존재하는 아이디입니다."<<endl;   return;

    }

    cout<<"회원 이름을 입력하세요."<<endl;

    string name = ehglobal::getstr();   

    pair<string, Member *> pd(id,new Member(id,name));

    mdic.insert(pd);

}

 

void App::RemoveMember()//회원 삭제

{

    cout<<"삭제할 회원 아이디를 입력하세요."<<endl;

    string id = ehglobal::getstr();   

    MIter seek = mdic.find(id); //map find 메서드에 키로 보관된 요소 찾기   

 

    if(seek == mdic.end())//seek base.end()와 같다면 검색 조건에 맞는 요소가 없음

    {

        cout<<"존재하지 않는 아이디입니다."<<endl;

        return;

    }

 

    //반복자의 간접 연산의 결과는 보관한 요소 형식 pair<string,Member *>

    delete (*seek).second; //pair second는 값인 회원 개체 소멸

    mdic.erase(seek);

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

}

 

void App::FindMember()const //회원 검색   

{

    cout<<"검색할 회원 아이디를 입력하세요."<<endl;

    string id = ehglobal::getstr();   

    CMIter seek = mdic.find(id); //map find 메서드에 키로 보관된 요소 찾기   

    if(seek == mdic.end())//seek base.end()와 같다면 검색 조건에 맞는 요소가 없음

    {

        cout<<"존재하지 않는 아이디입니다."<<endl;

        return;

    }

    Member *member = (*seek).second;

    member->View();   

}

void App::ListAll()const //전체 보기

{

    CMIter seek = mdic.begin();

    CMIter end = mdic.end();

    Member *member = 0;   

 

    //반복자를 사용하여 차례대로 회원의 정보를 출력

    for(  ; seek != end ; ++seek)

    {

        member = (*seek).second; //pair second는 보관한 회원 개체

        member->View();

    }

}

 

App::~App()

{

    MIter seek = mdic.begin();

    MIter end = mdic.end();

    Member *member = 0;

 

    for(  ; seek != end ; ++seek)

    {

        member = (*seek).second; //pair second는 보관한 회원 개체

        delete member;

    }

}

 

 

 

7.5.2 STL map 사용 방법 2

앞에서는 STLmapinsert, erase, find 멤버 메서드와 반복자를 사용하는 방법을 살펴보았습니다. 이번에는 인덱스 연산을 사용하는 방법으로 회원 관리 프로그램을 구현해 보아요.

 

먼저 반복자를 타입 재지정하세요.

typedef MDic::iterator MIter;

typedef MDic::const_iterator CMIter;

 

AddMember 메서드부터 구현합시다.

void App::AddMember() //회원 추가

{

추가할 회원 아이디를 입력받습니다.

    cout<<"추가할 회원 아이디를 입력하세요."<<endl;

    string id = ehglobal::getstr();

 

키를 인자로 인덱스 연산을 사용하면 보관한 값을 구할 수 있습니다. 만약 없다면 인덱스 연산에 사용한 키와 0을 값으로 하는 pair를 보관한 후에 pairsecond를 반환합니다. 즉 결과는 0이예요.

    if(mdic[id])

    {

결과가 0이 아니라면 이미 존재하는 아이디입니다. 메시지를 출력하고 기능을 종료하세요.

        cout<<"이미 존재하는 아이디입니다."<<endl;

        return;

    }

 

회원 이름을 입력받습니다.

    cout<<"회원 이름을 입력하세요."<<endl;

    string name = ehglobal::getstr();

 

키를 인자로 인덱스 연산을 사용한 표현을 대입 연산자 좌항에 두고 대입 연산자 우항에 값을 표현합니다. 따라서 여기에서는 다음처럼 사용할 수 있겠죠.

    mdic[id]=new Member(id,name);   

}

 

회원 삭제 기능을 구현합시다.

void App::RemoveMember()//회원 삭제

{

삭제할 회원 아이디를 입력받습니다.

    cout<<"삭제할 회원 아이디를 입력하세요."<<endl;

    string id = ehglobal::getstr();

 

키를 인자로 인덱스 연산을 사용하여 값을 얻어오세요.

    Member *member = mdic[id];

 

    if(member==0)

    {

만약 값이 0이면 보관한 회원 개체가 없는 것입니다. 메시지 출력 후에 기능을 종료하세요.

        cout<<"존재하지 않는 아이디입니다."<<endl;

        return;

    }   

 

보관한 회원 개체를 소멸하세요.

    delete member;

그리고 키를 인자로 인덱스 연산을 표현한 것을 대입 연산자 좌항에 표현하여 0을 대입하세요.

    mdic[id]=0;

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

}

 

검색 기능에서도 인덱스 연산을 사용할 것입니다. 앞에서 FindMember 메서드는 상수화 멤버 메서드로 정의하였는데 map의 인덱스 연산은 상수화 멤버가 아닙니다. 불가피하게 FindMember 메서드를 상수화가 아닌 멤버 메서드로 수정하세요. 물론 헤더 파일도 수정하셔야겠죠.

class App

{

   ...중략...

    void FindMember(); //회원 검색

    ...중략...

};

 

회원 검색 기능을 구현합시다.

void App::FindMember() //회원 검색   

{

검색할 회원 아이디를 입력받으세요.

    cout<<"검색할 회원 아이디를 입력하세요."<<endl;

    string id = ehglobal::getstr();

   

키를 인자로 인덱스 연산을 사용하여 값을 얻어오세요.

    Member *member = mdic[id];

    if(member==0)

    {

만약 값이 0이면 보관한 회원 개체가 없는 것입니다. 메시지 출력 후에 기능을 종료하세요.

        cout<<"존재하지 않는 아이디입니다."<<endl;

        return;

    }   

회원 정보를 출력하세요.

    member->View();   

}

 

void App::ListAll()const //전체 보기

{

    CMIter seek = mdic.begin();

    CMIter end = mdic.end();

    Member *member = 0;

   

    //반복자를 사용하여 차례대로 회원의 정보를 출력

    for(  ; seek != end ; ++seek)

    {

        member = (*seek).second; //pair second는 보관한 회원 개체

여기에서는 인덱스 연산으로 값이 존재하지 않는 검색을 하였을 때 검색에 사용한 키와 0을 값인 pair를 보관합니다. 따라서 반복자의 간접 연산의 second로 값을 얻어온 후 0인지 판별하세요.

        if(member)

        {

0이 아닐 때만 회원 정보를 출력하세요.

            member->View();

        }

    }

}

 

App::~App()

{

    MIter seek = mdic.begin();

    MIter end = mdic.end();

    Member *member = 0;

       

    for(  ; seek != end ; ++seek)

    {

        member = (*seek).second; //pair second는 보관한 회원 개체

        if(member)

        {

소멸할 때도 0이 아닐 때만 회원 정보를 출력하세요.

            delete member;

        }

    }

}

 

 

 

다음은 앞에서 구현한 App.cpp 파일의 내용입니다.

//App.cpp

#include "App.h"

typedef MDic::iterator MIter;

typedef MDic::const_iterator CMIter;

void App::Run()

{

    int key=NO_DEFINED;

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

    {

        switch(key)

        {

        case F1: AddMember(); break;

        case F2: RemoveMember(); break;

        case F3: FindMember(); break;

        case F4: ListAll(); break;       

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

        }

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

        ehglobal::getkey();

    }

}

int App::SelectMenu()

{

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

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

    cout<<"F1: 회원 추가 F2: 회원 삭제 F3: 회원 검색 F4: 전체 보기"<<endl;

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

}

void App::AddMember() //회원 추가

{   

    cout<<"추가할 회원 아이디를 입력하세요."<<endl;

    string id = ehglobal::getstr();   

    if(mdic[id])

    {

        cout<<"이미 존재하는 아이디입니다."<<endl;

        return;

    }

    cout<<"회원 이름을 입력하세요."<<endl;

    string name = ehglobal::getstr();   

    mdic[id]=new Member(id,name);   

}

 

void App::RemoveMember()//회원 삭제

{

    cout<<"삭제할 회원 아이디를 입력하세요."<<endl;

    string id = ehglobal::getstr();

   

    Member *member = mdic[id];

    if(member==0)   

    {

        cout<<"존재하지 않는 아이디입니다."<<endl;

        return;

    }   

    delete member;

    mdic[id]=0;

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

}

 

void App::FindMember() //회원 검색   

{

    cout<<"검색할 회원 아이디를 입력하세요."<<endl;

    string id = ehglobal::getstr();   

    Member *member = mdic[id];

    if(member==0)

    {

        cout<<"존재하지 않는 아이디입니다."<<endl;

        return;

    }   

    member->View();   

}

void App::ListAll()const //전체 보기

{

    CMIter seek = mdic.begin();

    CMIter end = mdic.end();

    Member *member = 0;   

    //반복자를 사용하여 차례대로 회원의 정보를 출력

    for(  ; seek != end ; ++seek)

    {       

        member = (*seek).second; //pair second는 보관한 회원 개체

        if(member)

        {

            member->View();

        }

    }

}

App::~App()

{

    MIter seek = mdic.begin();

    MIter end = mdic.end();

    Member *member = 0;

    for(  ; seek != end ; ++seek)

    {

        member = (*seek).second; //pair second는 보관한 회원 개체

        if(member)

        {

            delete member;

        }

    }

}

 

다음은 FindMember메서드를 상수화 멤버 메서드를 비상수화 멤버 메서드로 수정한 App.h 파일 내용입니다.

//App.h

#pragma once

#include "ehglobal.h"

#include "Member.h"

#include <map>

#include <algorithm>

using std::map;

using std::pair;

typedef map<string, Member *> MDic;

class App

{

    MDic mdic;

public:   

    void Run();

    ~App();

private:

    int SelectMenu();

    void AddMember(); //회원 추가

    void RemoveMember();//회원 삭제

    void FindMember(); //회원 검색   

    void ListAll()const; //전체 보기

};

반응형