데이터베이스 구현
Project/Win32API로 던그리드 모작

데이터베이스 구현

Woon2World :: Programming & Art Life

 

아이디어

  • csv 문서를 파싱해

https://jartlife.tistory.com/53

 

csv 문서 파싱

csv 파일 csv(comma-separated values) 파일은 쉼표로 구분되는 문자열을 나열한 파일 형식이다. 엑셀과 메모장에서 csv파일을 쉽게 보고 편집할 수 있다. 데이터베이스를 csv 파일로 구축하면 각 필드를

jartlife.tistory.com

 

  • stringstream 으로부터

https://jartlife.tistory.com/55

 

std::stringstream 을 통해 문자열을 값으로

문자열을 값으로 int, double, long 같은 primitive 자료형은 헤더만 추가하면 얼마든지 std::stoi, std::stod, std::stol 과 같은 함수를 통해 문자열을 실제 값으로 변환할 수 있다. 그러나 각각의 함수 이름을.

jartlife.tistory.com

 

  • tuple 을 입력받는다.

https://jartlife.tistory.com/56

 

std::iostream 으로 std::tuple 입출력하기

istream& operator>> 의 오버로딩 std::istream 을 통해 다양한 primitive 타입을 입력받을 수 있다. 하지만 std::tuple 은 primitive 가 아니고, c++ 언어측에서 따로 std::istream 과 std::tuple 을 받는 opera..

jartlife.tistory.com

 

 


database 클래스 인터페이스

  • template < typename ... TupleElems >
    class database< TupleElems... >
    설명 : TupleElems... 를 템플릿 인자로 취하는 std::tuple 들의 나열이다.

    • template < typename Str_t >
      database( const Str_t file_path )
      설명 : 주어진 경로의 파일(.csv)을 열어 데이터베이스를 입력받는다.
      예시
      • 튜플의 형식이 int, int 인 "mycsvfile.csv" 입력받기
        database< int, int >{ "mycsvfile.csv" };

      • 튜플의 형식이 A, B, C 인 "ABCcsv.csv" 입력받기
        database< A, B, C >{ "ABCcsv.csv" };

    • decltype( auto ) get( const size_t row )
      설명 : row 번째 행의 std::tuple 을 반환한다.
      호출한 database 객체의 const 여부에 따라 반환되는 std::tuple 의 const 여부가 결정된다.
      예시
      • 데이터베이스의 0번째 행( 시작 행 ) 가져오기
        auto tup{ db.get( 0 ) };

      • 데이터베이스의 15번째 행 가져오기
        auto row{ db.get( 15 ) };

      • 데이터베이스의 마지막 행 가져오기
        auto tuple{ db.get( db.size() - 1 ) };

    • const size_t size() const
      설명 : 데이터베이스의 행의 갯수를 반환한다.
      예시
      • 데이터베이스의 행의 갯수 얻어오기
        const auto num_rows = db.size();

      • 데이터베이스의 마지막 행 가져오기
        auto row{ db.get( db.size() - 1 ) };

 

 


database 클래스 구현

// database.h

#ifndef
 _database
#define _database
 
// ...
 
#include <vector>
#include <sstream>
#include <tuple>
#include "TMP.h"
#include "keyword.h"
 
// ...
 
template < typename... TupleElems >
class database NONCOPYABLE
{
public:
    template < typename Str_t >
    database( const Str_t file_path )
    {
        auto parsed_csv{ _csv::csv_read( file_path ) };
 
        impl.reserve( 0x20 );
        
        for ( const auto& row : parsed_csv )
        {
            std::tuple< TupleElems... > tp;
            std::stringstream ss;
            for ( const auto& field : row )
            {
                ss << field << ' ';
            }
            ss >> tp;
            impl.push_backstd::move( tp ) );
        }
 
        impl.shrink_to_fit();
    }
 
    // row begins with 0
    decltype( auto ) get( const size_t row ) const NOASSIGNMENT
    {
        return impl.at( row );
    }
 
    // row begins with 0
    decltype( auto ) get( const size_t row ) NOASSIGNMENT
    {
        return impl.at( row );
    }
 
    // num of rows
    const size_t size() const { return impl.size(); }
 
    database() = delete;
    database( const database& other ) = delete;
    database& operator=const database& other ) = delete;
 
private:
    std::vector< std::tuple< TupleElems... > > impl;
};
 
#endif
cs

 

 

get() 은 database 객체가 const 일 경우와 const 가 아닐 경우에 대해 오버로드되어있다.

std::vector 의 at() 도 const 와 const 가 아닐 경우에 대해서 오버로드되어 있으므로, 별다른 구분 없이 std::vector 의 at() 을 부르면 호출 객체의 const 여부에 맞게 요소가 const 화 되어 반환된다.

 

 

더보기

 

// database.h + TMP.h
 
#ifndef _database
#define _database
 
#include <fstream>
#include <string>
#include <vector>
#include <sstream>
#include <tuple>
#include "TMP.h"
#include "keyword.h"
 
namespace _csv
{
    HELPER auto csv_read_row( std::istream& in, char delimiter )
    {
        std::stringstream ss;
        bool inquotes = false;
        bool packs = false;
        std::vector< std::string > row;
 
        while (in.good())
        {
            char c = in.get();
 
            switch (c)
            {
            case L'"':
                if ( !inquotes )      // begin quote char
                {
                    inquotes = true;
                }
                else
                {
                    if ( in.peek() == '"' )   // 2 consecutive quotes resolve to 1
                        ss << static_cast<char>( in.get() );
                    else                    // end quote char
                        inquotes = false;
                }
                break;
 
            case L'{':
                if ( !packs )
                {
                    packs = true;
                }
                else
                {
                    throw std::ios_base::failure{ "csv_read_row : '{' duplicated" };
                }
                break;
 
            case L'}':
                if ( !packs )
                {
                    throw std::ios_base::failure{ "csv_read_row : '{' must be followed by '}'" };
                }
                else
                {
                    packs = false;
                }
                break;
 
            case L'\r'case L'\n':
                if ( !inquotes && !packs )
                {
                    if ( in.peek() == '\n' )
                    {
                        in.get();
                    }
 
                    row.push_back( ss.str() );
                    row.shrink_to_fit();
                    return row;
                }
                break;
 
            default:
                if ( c == delimiter && !inquotes )
                {
                    row.push_back( ss.str() );
                    ss.str( "" );
                }
                else
                {
                    ss << c;
                }
                break;
 
            }
        }
 
        throw std::ios_base::failure{ "csv_read_row : '\\n', '\\r' not found" };
    }
 
    template < typename Str_t >
    auto csv_read( const Str_t file_path )
    {
        std::ifstream in{ file_path };
        std::vector< std::vector< std::string > > parsed;
        parsed.reserve( 0x20 );
 
        if ( in.fail() )
        {
            throw std::ios_base::failure{ "csv_read : cannot open file" };
        }
 
        while ( in.good() )
        {
            parsed.push_back( _csv::csv_read_row( in, ',' ) );
        }
 
        parsed.shrink_to_fit();
        return parsed;
    }
 
}
 
 
 
template < typename ... Elems, size_t ... Idx >
HELPER std::istream& istreamget_impl( std::istream& is, std::tuple< Elems... >& tup, const std::index_sequence< Idx... > )
{
    int dummy[ sizeof...( Elems ) ] = { ( is >> std::get< Idx >( tup ), 0 )... };
    return is;
}
 
template < typename ... Elems >
std::istream& operator>>std::istream& is, std::tuple< Elems... >& tup )
{
    istreamget_impl( is, tup, std::make_index_sequence< sizeof...( Elems ) >() );
    return is;
}
 
 
 
template < typename... TupleElems >
class database NONCOPYABLE
{
public:
    template < typename Str_t >
    database( const Str_t file_path )
    {
        auto parsed_csv{ _csv::csv_read( file_path ) };
 
        impl.reserve( 0x20 );
        
        for ( const auto& row : parsed_csv )
        {
            std::tuple< TupleElems... > tp;
            std::stringstream ss;
            for ( const auto& field : row )
            {
                ss << field << ' ';
            }
            ss >> tp;
            impl.push_backstd::move( tp ) );
        }
 
        impl.shrink_to_fit();
    }
 
    // row begins with 0
    decltype( auto ) get( const size_t row ) const NOASSIGNMENT
    {
        return impl.at( row );
    }
 
    // row begins with 0
    decltype( auto ) get( const size_t row ) NOASSIGNMENT
    {
        return impl.at( row );
    }
 
    // num of rows
    const size_t size() const { return impl.size(); }
 
    database() = delete;
    database( const database& other ) = delete;
    database& operator=const database& other ) = delete;
 
private:
    std::vector< std::tuple< TupleElems... > > impl;
};
 
#endif
cs

 

 

 


테스트

더보기

 

// database.h
 
// ...
 
#include <iostream>
void test_database()
{
    database<intintintintstd::string> db{ "testcsv.csv" };
 
    for ( int i = 0; i < db.size(); ++i )
    {
        std::cout << db.get( i ) << std::endl;
    }
}
cs

 

// database.h


// ...


#include
 <iostream>
void test_database()
{
    database<intintintintstd::string> db{ "testcsv.csv" };
 
    for ( int i = 1; i <= db.size(); ++i )
    {
        auto tuple = db.get( i - 1 );
        std::cout << i << " 행 1번째 열 : " << std::get<0>( tuple ) << std::endl;
        std::cout << i << " 행 2번째 열 : " << std::get<1>( tuple ) << std::endl;
        std::cout << i << " 행 3번째 열 : " << std::get<2>( tuple ) << std::endl;
        std::cout << i << " 행 4번째 열 : " << std::get<3>( tuple ) << std::endl;
        std::cout << i << " 행 5번째 열 : " << std::get<4>( tuple ) << std::endl;
    }
}
cs