[C++] binary_search 정리

메모리 기반의 방대한 데이타가 있다고 가정을 할 때.. 이 데이터 목록 중 어떤 데이타가 존재하는지의 여부를 가장 빠르게 검색해주는 방식이 binary_search인데요. 이 STL 함수를 정리해 봅니다.

먼저 메모리 기반의 방대한 데이터를 준비해 봅니다.

#include <iostream>
#include <list>
#include <algorithm>
#include <iterator>

using namespace std;

int main()
{
    list<int> values{ 1, 2, 8, 7 ,6, 5, 4, 1, 9, 3, 5, 6, 7 };

}

데이타가 몇개 되지 않지만 방대하다고 칩시다. binary_search를 사용하기 위해서는 먼저 데이터가 정렬되어 있어야 합니다. 내림차순으로 정렬하는 코드를 추가하면.. 다음과 같습니다.

#include <iostream>
#include <list>
#include <algorithm>
#include <iterator>

using namespace std;

int main()
{
    list<int> values{ 1, 2, 8, 7 ,6, 5, 4, 1, 9, 3, 5, 6, 7 };
    auto predicate = [](int a, int b) { return a > b; };
    values.sort(predicate);

    copy(values.begin(), values.end(), ostream_iterator<double>{ std::cout, " "});
    cout << endl;
}

람다 함수로 내림차순 정렬을 위한 기준으로 삼았습니다. 그리고 잘 정렬되었는지 콘솔에 표시해 보았구요. 이제 binary_search 함수를 사용하기 위한 데이터 덩어리가 준비 되었고, 다음처럼 데이터 덩어리중 값 5가 존재하는지의 여부를 검색하는 코드는 다음과 같습니다.

#include <iostream>
#include <list>
#include <algorithm>
#include <iterator>

using namespace std;

int main()
{
    list<int> values{ 1, 2, 8, 7 ,6, 5, 4, 1, 9, 3, 5, 6, 7 };
    auto predicate = [](int a, int b) { return a > b; };
    values.sort(predicate);

    copy(values.begin(), values.end(), ostream_iterator<double>{ std::cout, " "});
    cout << endl;

    int wanted{ 5 };
    bool bSearched = binary_search(begin(values), end(values), wanted, predicate);
    if (bSearched) {
        cout << "Found !" << endl;
    } else {
        cout << "Not found." << endl;
    }
}

[C++] Stream을 이용한 Typed Binary 데이터 읽고 쓰기

C++은 stream을 통해 반복자(iterator)를 이용해 데이터를  매우 유연하게 처리 할 수 있습니다. 이 스트림을 통해 데이터의 입력을 키보드나 파일 등을 통해 매우 유연하고 효과적으로 읽고 쓸 수 있는데요. 문제는 일반화된 문자열에 대한 읽고 쓰기에 중심을 두고 있다는 점입니다.  즉,  C++의 스트림을 이용해 실수 타입의 값으로 3.14159265라는 값을 파일에 저장하고자 한다면, 이 값을 메모리에 저장된 그대로의 실수형 바이너리 형식으로의 저장이 아닌 “3.14159265”라는 문자열로 저장되는 것이 C++ 개발자에게 흔히 노출된 방식이라는 것입니다.

다 이유가 있겠으나, 필자는 이 실수형 값을 메모리에 저장된 그대로의 실수형 바이너리 형식으로 파일에 저장하고, 이렇게 저장된 값을 다시 파일을 열어 꺼내와 화면에 표시하고자 합니다.

먼저 3.14159265 값을 d 드라이브의 data.bin에 저장하는 코드입니다.

#include <iostream>
#include <fstream>

int main()
{
	std::ofstream os("d:\\data.bin", std::ios::binary);

	double v = 3.141592625;
	os.write(reinterpret_cast<const char*>(&v), sizeof(double));

	os.close();
}

실행해 보면 data.bin 파일이 생성되어졌고, 파일 크기는 double의 바이트 크기와 동일한 8바이트인 것을 확인할 수 있습니다.

이제 이렇게 생성된 파일에서 다시 실수형 값을 읽어 보는 코드는 다음과 같습니다.

#include <iostream>
#include <fstream>

int main()
{
	std::ifstream is("d:\\data.bin", std::ios::binary);

	double v2;
	is.read(reinterpret_cast<char*>(&v2), sizeof(double));
	
	is.close();

	std::cout << v2;
}

여기서 C++의 template을 이용해 개선을 해보도록 하겠습니다. 즉, 다음과 같은 2개의 템플릿 함수를 추가합니다.

template<typename T>
std::ostream& write_typed_data(std::ostream& stream, const T& value) {
	return stream.write(reinterpret_cast<const char*>(&value), sizeof(T));
}

template<typename T>
std::istream & read_typed_data(std::istream& stream, T& value) {
	return stream.read(reinterpret_cast<char*>(&value), sizeof(T));
}

네, write_typed_data와 read_typed_data는 각각 임이의 데이터 타입에 대해서 지정된 스트림에 메모리에 저장된 그대로의 구조인 바이너리 형식으로 쓰고 읽는 범용 함수입니다.

이 두 함수를 활용해 3.141592625 값을 쓰고 읽는 전체 코드를 다시 작성해 보면 다음과 같습니다.

#include <iostream>
#include <fstream>

template<typename T>
std::ostream& write_typed_data(std::ostream& stream, const T& value) {
	return stream.write(reinterpret_cast<const char*>(&value), sizeof(T));
}

template<typename T>
std::istream & read_typed_data(std::istream& stream, T& value) {
	return stream.read(reinterpret_cast<char*>(&value), sizeof(T));
}

int main()
{
	// Write 
	std::ofstream os("d:\\data.bin", std::ios::binary);

	double v = 3.141592625;
	//os.write(reinterpret_cast<const char*>(&v), sizeof(double));
	write_typed_data(os, v);

	os.close();
	// <- test unit end

	// Read
	std::ifstream is("d:\\data.bin", std::ios::binary);

	double v2;
	read_typed_data(is, v2);
	//is.read(reinterpret_cast<char*>(&v2), sizeof(double));
	
	is.close();

	std::cout << v2;
	// <- test unit end
}

C++에서 스트림을 활용해 타입을 가지는 변수를 바이너리 형식으로 저장하는 위의 방식이 정석인지는 모르겠습니다. (이제와서 이게 무슨 소리? -_-;) reinterpret_cast를 사용했다는 점에서 상당히 의구심이 들기 때문인데요. 더욱 나은 방식이 있다면 제안해 주시기 바랍니다. reinterpret_cast는 포인터가 다른 포인터 형식으로 변환될 수 있도록 하거나 정수 계열 형식이 포인터 형식으로 변환될 수 있도록 하고 그 반대로도 변환될 수 있도록 하는 cast 연산자입니다.