icon안동민 개발노트

STL을 활용한 데이터 처리 프로그램


실습 개요

 이 실습에서는 STL(Standard Template Library)의 다양한 컨테이너, 알고리즘, 그리고 함수 객체를 활용하여 실제 데이터 처리 프로그램을 구현해볼 것입니다. 이를 통해 STL의 실제 응용 방법과 효율적인 데이터 처리 기법을 학습할 수 있습니다.

학습 목표

  1. STL 컨테이너를 적절히 선택하여 데이터 저장 및 관리하는 방법 학습
  2. STL 알고리즘을 활용한 데이터 처리 및 분석 기법 이해
  3. 함수 객체와 람다 표현식을 이용한 커스텀 연산 구현 능력 향상
  4. 실제 문제 해결을 위한 STL의 종합적 활용 능력 개발

프로그램 요구사항

 도서관 관리 시스템을 구현하되, 다음 기능을 포함해야 합니다.

  1. 도서 정보 (제목, ISBN, 저자, 출판년도) 입력 및 저장
  2. 전체 도서 목록 출력
  3. 다양한 기준(제목, 저자, 출판년도)으로 도서 정렬
  4. 특정 조건(저자, 출판년도 범위)에 맞는 도서 검색
  5. 가장 최근에 출판된 N권의 도서 목록 출력
  6. 도서 정보 수정 및 삭제
  7. 도서관 통계 정보 계산 (총 도서 수, 특정 연도 이후 출판된 도서 비율 등)

프로그램 구현

 필요한 헤더 파일과 네임스페이스 선언

#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <functional>
#include <iomanip>
#include <numeric>
#include <map>
 
using namespace std;

 도서 클래스 정의

class Book {
public:
    string title;
    string isbn;
    string author;
    int publicationYear;
 
    Book(string t, string i, string a, int y) 
        : title(t), isbn(i), author(a), publicationYear(y) {}
 
    // 출력을 위한 연산자 오버로딩
    friend ostream& operator<<(ostream& os, const Book& b) {
        os << setw(30) << b.title << setw(15) << b.isbn 
           << setw(20) << b.author << setw(6) << b.publicationYear;
        return os;
    }
};

 도서관 관리 클래스 정의

class LibraryManagementSystem {
private:
    vector<Book> books;
 
public:
    void addBook(const Book& book) {
        books.push_back(book);
    }
 
    void printAllBooks() const {
        for (const auto& book : books) {
            cout << book << endl;
        }
    }
 
    void sortBooks(function<bool(const Book&, const Book&)> comp) {
        sort(books.begin(), books.end(), comp);
    }
 
    vector<Book> findBooks(function<bool(const Book&)> predicate) const {
        vector<Book> result;
        copy_if(books.begin(), books.end(), back_inserter(result), predicate);
        return result;
    }
 
    vector<Book> getRecentBooks(int n) const {
        vector<Book> recentBooks = books;
        partial_sort(recentBooks.begin(), recentBooks.begin() + min(n, (int)recentBooks.size()),
                     recentBooks.end(),
                     [](const Book& a, const Book& b) {
                         return a.publicationYear > b.publicationYear;
                     });
        recentBooks.resize(min(n, (int)recentBooks.size()));
        return recentBooks;
    }
 
    void updateBook(const string& isbn, function<void(Book&)> updateFunc) {
        auto it = find_if(books.begin(), books.end(),
                          [&isbn](const Book& b) { return b.isbn == isbn; });
        if (it != books.end()) {
            updateFunc(*it);
        }
    }
 
    void removeBook(const string& isbn) {
        books.erase(
            remove_if(books.begin(), books.end(),
                      [&isbn](const Book& b) { return b.isbn == isbn; }),
            books.end()
        );
    }
 
    map<string, int> getAuthorBookCount() const {
        map<string, int> authorCount;
        for (const auto& book : books) {
            authorCount[book.author]++;
        }
        return authorCount;
    }
 
    double getRecentBookRatio(int year) const {
        int recentBooks = count_if(books.begin(), books.end(),
                                   [year](const Book& b) { return b.publicationYear >= year; });
        return static_cast<double>(recentBooks) / books.size();
    }
};

 메인 함수 및 사용자 인터페이스

int main() {
    LibraryManagementSystem lms;
 
    // 샘플 데이터 추가
    lms.addBook(Book("The Great Gatsby", "9780743273565", "F. Scott Fitzgerald", 1925));
    lms.addBook(Book("To Kill a Mockingbird", "9780446310789", "Harper Lee", 1960));
    lms.addBook(Book("1984", "9780451524935", "George Orwell", 1949));
    lms.addBook(Book("Pride and Prejudice", "9780141439518", "Jane Austen", 1813));
    lms.addBook(Book("The Catcher in the Rye", "9780316769174", "J.D. Salinger", 1951));
 
    int choice;
    do {
        cout << "\n1. 도서 추가" << endl;
        cout << "2. 전체 도서 목록 출력" << endl;
        cout << "3. 도서 정렬" << endl;
        cout << "4. 도서 검색" << endl;
        cout << "5. 최근 출판된 도서 목록" << endl;
        cout << "6. 도서 정보 수정" << endl;
        cout << "7. 도서 삭제" << endl;
        cout << "8. 저자별 도서 수 통계" << endl;
        cout << "9. 최근 출판 도서 비율 계산" << endl;
        cout << "0. 종료" << endl;
        cout << "선택: ";
        cin >> choice;
 
        switch(choice) {
            case 1: {
                string title, isbn, author;
                int year;
                cout << "제목, ISBN, 저자, 출판년도 입력: ";
                cin >> title >> isbn >> author >> year;
                lms.addBook(Book(title, isbn, author, year));
                break;
            }
            case 2:
                lms.printAllBooks();
                break;
            case 3: {
                cout << "정렬 기준 (1: 제목, 2: 저자, 3: 출판년도): ";
                int sortChoice;
                cin >> sortChoice;
                switch(sortChoice) {
                    case 1:
                        lms.sortBooks([](const Book& a, const Book& b) { return a.title < b.title; });
                        break;
                    case 2:
                        lms.sortBooks([](const Book& a, const Book& b) { return a.author < b.author; });
                        break;
                    case 3:
                        lms.sortBooks([](const Book& a, const Book& b) { return a.publicationYear < b.publicationYear; });
                        break;
                }
                lms.printAllBooks();
                break;
            }
            case 4: {
                cout << "검색 기준 (1: 저자, 2: 출판년도 범위): ";
                int searchChoice;
                cin >> searchChoice;
                vector<Book> result;
                if (searchChoice == 1) {
                    string author;
                    cout << "저자 이름: ";
                    cin >> author;
                    result = lms.findBooks([&author](const Book& b) { return b.author == author; });
                } else if (searchChoice == 2) {
                    int startYear, endYear;
                    cout << "시작 연도와 끝 연도: ";
                    cin >> startYear >> endYear;
                    result = lms.findBooks([startYear, endYear](const Book& b) {
                        return b.publicationYear >= startYear && b.publicationYear <= endYear;
                    });
                }
                for (const auto& book : result) {
                    cout << book << endl;
                }
                break;
            }
            case 5: {
                int n;
                cout << "출력할 도서 수: ";
                cin >> n;
                auto recentBooks = lms.getRecentBooks(n);
                for (const auto& book : recentBooks) {
                    cout << book << endl;
                }
                break;
            }
            case 6: {
                string isbn;
                cout << "수정할 도서의 ISBN: ";
                cin >> isbn;
                string newTitle;
                cout << "새 제목: ";
                cin >> newTitle;
                lms.updateBook(isbn, [&newTitle](Book& b) { b.title = newTitle; });
                break;
            }
            case 7: {
                string isbn;
                cout << "삭제할 도서의 ISBN: ";
                cin >> isbn;
                lms.removeBook(isbn);
                break;
            }
            case 8: {
                auto authorStats = lms.getAuthorBookCount();
                for (const auto& [author, count] : authorStats) {
                    cout << author << ": " << count << " books" << endl;
                }
                break;
            }
            case 9: {
                int year;
                cout << "기준 연도: ";
                cin >> year;
                double ratio = lms.getRecentBookRatio(year);
                cout << year << "년 이후 출판된 도서 비율: " << fixed << setprecision(2) << ratio * 100 << "%" << endl;
                break;
            }
            case 0:
                cout << "프로그램을 종료합니다." << endl;
                break;
            default:
                cout << "잘못된 선택입니다." << endl;
        }
    } while (choice != 0);
 
    return 0;
}

코드 설명

  1. vector<Book>을 사용하여 도서 정보를 저장합니다.
  2. sort, copy_if, partial_sort 등의 STL 알고리즘을 활용하여 다양한 데이터 처리 작업을 수행합니다.
  3. 람다 표현식을 사용하여 커스텀 비교 함수와 조건을 구현합니다.
  4. find_if, remove_if 등을 사용하여 특정 조건을 만족하는 요소를 찾거나 제거합니다.
  5. function 객체를 활용하여 유연한 함수 인자 전달을 구현합니다.

주요 STL 사용 예시 설명

  1. sort 알고리즘 사용
    sort(books.begin(), books.end(), comp);
    이 코드는 comp 함수 객체를 사용하여 books 벡터를 정렬합니다.
  2. copy_if 알고리즘 사용
    copy_if(books.begin(), books.end(), back_inserter(result), predicate);
    이 코드는 predicate 조건을 만족하는 요소만 result 벡터에 복사합니다.
  3. partial_sort 알고리즘 사용:
    partial_sort(recentBooks.begin(), recentBooks.begin() + n, recentBooks.end(),
                 [](const Book& a, const Book& b) {
                     return a.publicationYear > b.publicationYear;
                 });
    이 코드는 출판년도를 기준으로 상위 n개의 도서만 정렬합니다.
  4. find_if 알고리즘 사용
    auto it = find_if(books.begin(), books.end(),
                      [&isbn](const Book& b) { return b.isbn == isbn; });
    이 코드는 특정 ISBN을 가진 도서를 찾습니다.
  5. count_if 알고리즘 사용
    int recentBooks = count_if(books.begin(), books.end(),
                               [year](const Book& b) { return b.publicationYear >= year; });
    이 코드는 특정 연도 이후에 출판된 도서의 수를 계산합니다.

추가 기능 구현 연습

  1. 도서 대출 및 반납 기능 구현
    • Book 클래스에 bool isAvailable 멤버 변수 추가
    • 대출 및 반납 함수 구현
    • 현재 대출 중인 도서 목록 출력 기능 추가
  2. 도서 평점 시스템 구현
    • Book 클래스에 vector<double> ratings 멤버 변수 추가
    • 평점 추가 및 평균 평점 계산 함수 구현
    • 평점 기준 도서 정렬 기능 추가
  3. 도서 카테고리 시스템 구현
    • Book 클래스에 string category 멤버 변수 추가
    • 카테고리별 도서 검색 및 통계 기능 구현

성능 최적화 고려사항

  1. 대용량 데이터 처리
    • 도서 수가 매우 많을 경우, 인덱싱이나 해시 테이블 사용 고려
    • 정렬된 상태를 유지하며 삽입하는 방법 고려 (std::set 또는 std::map 사용)
  2. 검색 성능 향상
    • 자주 사용되는 검색 키에 대해 인덱스 구현 고려
    • 이진 검색 활용 (정렬된 데이터의 경우)
  3. 메모리 사용 최적화
    • 필요한 경우 std::vectorreserve 함수를 사용하여 불필요한 재할당 방지
    • 큰 객체의 경우 복사 대신 참조 사용 고려

예외 처리

 프로그램의 안정성을 높이기 위해 다음과 같은 예외 처리를 추가할 수 있습니다.

try {
    // 사용자 입력 또는 파일 입출력 등의 작업
} catch (const std::invalid_argument& e) {
    std::cerr << "Invalid input: " << e.what() << std::endl;
} catch (const std::runtime_error& e) {
    std::cerr << "Runtime error: " << e.what() << std::endl;
} catch (...) {
    std::cerr << "Unknown error occurred" << std::endl;
}

파일 입출력 기능 추가

 도서 정보를 파일로 저장하고 불러오는 기능을 구현해 봅시다.

#include <fstream>
 
class LibraryManagementSystem {
    // ... 기존 코드 ...
 
public:
    void saveToFile(const string& filename) const {
        ofstream file(filename);
        if (file.is_open()) {
            for (const auto& book : books) {
                file << book.title << "," << book.isbn << "," 
                     << book.author << "," << book.publicationYear << "\n";
            }
        }
    }
 
    void loadFromFile(const string& filename) {
        ifstream file(filename);
        string line;
        while (getline(file, line)) {
            stringstream ss(line);
            string title, isbn, author;
            int year;
            getline(ss, title, ',');
            getline(ss, isbn, ',');
            getline(ss, author, ',');
            ss >> year;
            addBook(Book(title, isbn, author, year));
        }
    }
};

연습 문제

  1. 도서 제목의 부분 문자열로 검색하는 기능을 구현하세요. (힌트 : std::string::find 사용)
  2. 특정 연도 범위 내에서 가장 많은 책을 출판한 저자를 찾는 기능을 구현하세요.
  3. 도서 대여 시스템을 구현하세요. 대여 가능한 도서 목록, 대여 중인 도서 목록, 반납 예정일 등의 정보를 관리해야 합니다.

결론

 이 실습을 통해 STL의 다양한 컨테이너와 알고리즘을 실제 문제 해결에 적용해보았습니다. 도서관 관리 시스템이라는 실제적인 예제를 통해 데이터 저장, 검색, 정렬, 필터링 등의 작업을 STL을 활용하여 효율적으로 구현할 수 있음을 확인했습니다. STL을 효과적으로 사용하면 코드의 가독성, 유지보수성, 효율성을 크게 향상시킬 수 있습니다. 앞으로 더 복잡한 프로그램을 개발할 때도 이러한 STL의 기능을 적극 활용하시기 바랍니다. 실제 프로젝트에서는 이 예제를 바탕으로 더 복잡한 기능(예 : 사용자 인증, 데이터베이스 연동, 네트워크 기능 등)을 추가하여 완전한 도서관 관리 시스템을 구축할 수 있을 것입니다.