"HAT-trie: 문자열에 대한 캐시 인식 Trie 기반 데이터 구조"를 기반으로 한 Trie 구현입니다. (Askitis Nikolas 및 Sinha Ranjan, 2007) 논문. 현재로서는 순수 HAT-trie만 구현되었으며 하이브리드 버전은 나중에 출시될 수 있습니다. HAT-trie 데이터 구조에 대한 자세한 내용은 여기에서 확인할 수 있습니다.
라이브러리는 공통 접두사를 압축하여 문자열 집합이나 맵을 저장하는 효율적이고 간단한 방법을 제공합니다. 또한 접두사와 일치하는 키를 검색할 수도 있습니다. 구조의 기본 매개변수는 정확한 검색을 최적화하는 데 맞춰져 있지만 접두사 검색을 많이 수행하는 경우 burst_threshold
메서드를 통해 버스트 임계값을 줄이는 것이 좋습니다.
많은 수의 문자열을 저장하기에 적합한 구조입니다.
배열 해시 부분의 경우 array-hash 프로젝트가 사용되며 저장소에 포함됩니다.
라이브러리는 tsl::htrie_map
및 tsl::htrie_set
의 두 가지 클래스를 제공합니다.
tsl::hat_trie
대상을 사용할 수도 있습니다.equal_prefix_range
통한 접두사 검색(예: 자동 완성에 유용함)과 erase_prefix
통한 접두사 삭제를 지원합니다.longest_prefix
통해 가장 긴 일치 접두사 검색을 지원합니다.serialize/deserialize
메서드 참조)max_load_factor
메서드를 통해 수정할 수 있습니다. 최대 로드 비율이 낮을수록 속도가 빨라지고, 높을수록 메모리 사용량이 줄어듭니다. 기본값은 8.0으로 설정되어 있습니다.burst_threshold
메소드를 통해 결과를 더 빠르게 반복하기 위해 1024 이하로 줄이는 것이 좋습니다.KeySizeT
템플릿 매개변수를 통해 늘릴 수 있습니다.스레드 안전성 및 예외 보장은 STL 컨테이너와 유사합니다.
구조에서 사용되는 기본 해시 함수는 std::string_view
존재 여부에 따라 달라집니다. 사용 가능한 경우 std::hash<std::string_view>
사용되고, 그렇지 않으면 종속성을 피하기 위해 간단한 FNV-1a 해시 함수가 사용됩니다.
C++17 이상을 사용할 수 없는 경우 더 나은 성능을 위해 해시 함수를 CityHash, MurmurHash, FarmHash 등과 같은 것으로 바꾸는 것이 좋습니다. 우리가 수행한 테스트에서 CityHash64는 FNV-1a에 비해 읽기 성능이 최대 20% 향상되었습니다.
# include < city.h >
struct str_hash {
std:: size_t operator ()( const char * key, std:: size_t key_size) const {
return CityHash64 (key, key_size);
}
};
tsl::htrie_map< char , int , str_hash> map;
std::hash<std::string>
구조가 std::string
개체를 저장하지 않으므로 효율적으로 사용할 수 없습니다. 해시가 필요할 때마다 임시 std::string
만들어야 합니다.
벤치마크는 Wikipedia 아카이브의 기본 네임스페이스에 있는 모든 제목을 데이터 구조에 삽입하고, 삽입 후 사용된 메모리 공간(잠재적인 메모리 조각화 포함)을 확인하고, 데이터 구조에서 모든 제목을 다시 검색하는 것으로 구성됩니다. 삽입 프로세스 중 최대 메모리 사용량도 시간(1)으로 측정됩니다.
각 제목은 int(32비트)와 연결됩니다. 모든 해시 기반 구조는 CityHash64를 해시 함수로 사용합니다. Reserve 로 표시된 테스트의 경우 재해시를 피하기 위해 reserve
함수가 미리 호출됩니다.
tsl::hopscotch_map
, std::unordered_map
, google::dense_hash_map
및 spp::sparse_hash_map
키 길이가 단 1자인 경우에도 최소 크기 32바이트(x64에서)를 적용하는 키로 std::string
사용합니다. 다른 구조에서는 포인터에 대해 1바이트 + 8바이트(x64)의 1문자 키를 저장할 수 있습니다.
벤치마크는 GCC 6.3으로 컴파일되었으며 Intel i5-5200u 및 8Go RAM을 갖춘 Debian Stretch x64에서 실행되었습니다. 20개의 실행 중 최고가 선택되었습니다.
벤치마크 코드는 Gist에서 확인할 수 있습니다.
enwiki-20170320-all-titles-in-ns0.gz 데이터세트는 알파벳순으로 정렬되어 있습니다. 이 벤치마크에서는 먼저 shuf(1)을 통해 데이터세트를 섞어 편향된 정렬 데이터세트를 방지합니다.
도서관 | 데이터 구조 | 최대 메모리(MiB) | 메모리(MiB) | 삽입(ns/키) | 읽기(ns/키) |
---|---|---|---|---|---|
tsl::htrie_map | HAT 트리 | 405.22 | 402.25 | 643.10 | 250.87 |
tsl::htrie_map max_load_factor=4 | HAT 트리 | 471.85 | 468.50 | 638.66 | 212.90 |
tsl::htrie_map max_load_factor=2 | HAT 트리 | 569.76 | 566.52 | 630.61 | 201.10 |
tsl::htrie_map max_load_factor=1 | HAT 트리 | 713.44 | 709.81 | 645.76 | 190.87 |
삼나무::다 | 이중 어레이 트라이 | 1269.68 | 1254.41 | 1102.93 | 557.20 |
삼나무::다 주문됨=false | 이중 어레이 트라이 | 1269.80 | 1254.41 | 1089.78 | 570.13 |
삼나무::다 | 이중 배열 감소 트라이 | 1183.07 | 1167.79 | 1076.68 | 645.79 |
삼나무::다 주문됨=false | 이중 배열 감소 트라이 | 1183.14 | 1167.85 | 1065.43 | 641.98 |
삼나무::다 | 이중 배열 접두사 트라이 | 498.69 | 496.54 | 1096.90 | 628.01 |
삼나무::다 주문됨=false | 이중 배열 접두사 트라이 | 498.65 | 496.60 | 1048.40 | 628.94 |
모자 트리 1 (C) | HAT 트리 | 504.07 | 501.50 | 917.49 | 261.00 |
qp 트리(C) | QP 트라이 | 941.23 | 938.17 | 1349.25 | 1281.46 |
크리티컬 트라이(C) | 크리티컬 비트 트라이 | 1074.96 | 1071.98 | 2930.42 | 2869.74 |
주디SL(C) | 주디 배열 | 631.09 | 628.37 | 884.29 | 803.58 |
주디HS(C) | 주디 배열 | 723.44 | 719.47 | 476.79 | 417.15 |
tsl::배열_맵 | 배열 해시 테이블 | 823.54 | 678.73 | 603.94 | 138.24 |
tsl::배열_맵 예비로 | 배열 해시 테이블 | 564.26 | 555.91 | 249.52 | 128.28 |
tsl::hopscotch_map | 해시 테이블 | 1325.83 | 1077.99 | 368.26 | 119.49 |
tsl::hopscotch_map 예비로 | 해시 테이블 | 1080.51 | 1077.98 | 240.58 | 119.91 |
google::dense_hash_map | 해시 테이블 | 2319.40 | 1677.11 | 466.60 | 138.87 |
google::dense_hash_map 예비로 | 해시 테이블 | 1592.51 | 1589.99 | 259.56 | 120.40 |
spp::sparse_hash_map | 희소 해시 테이블 | 918.67 | 917.10 | 769.00 | 175.59 |
spp::sparse_hash_map 예비로 | 희소 해시 테이블 | 913.35 | 910.65 | 427.22 | 159.08 |
표준::순서가 없는_맵 | 해시 테이블 | 1249.05 | 1246.60 | 590.88 | 173.58 |
표준::순서가 없는_맵 예비로 | 해시 테이블 | 1212.23 | 1209.71 | 350.33 | 178.70 |
키는 알파벳순으로 삽입되어 읽혀집니다.
도서관 | 데이터 구조 | 최대 메모리(MiB) | 메모리(MiB) | 삽입(ns/키) | 읽기(ns/키) |
---|---|---|---|---|---|
tsl::htrie_map | HAT 트리 | 396.10 | 393.22 | 255.76 | 68.08 |
tsl::htrie_map max_load_factor=4 | HAT 트리 | 465.02 | 461.80 | 248.88 | 59.23 |
tsl::htrie_map max_load_factor=2 | HAT 트리 | 543.99 | 541.21 | 230.13 | 53.50 |
tsl::htrie_map max_load_factor=1 | HAT 트리 | 692.29 | 689.70 | 243.84 | 49.22 |
삼나무::다 | 이중 어레이 트라이 | 1269.58 | 1254.41 | 278.51 | 54.72 |
삼나무::다 주문됨=false | 이중 어레이 트라이 | 1269.66 | 1254.41 | 264.43 | 56.02 |
삼나무::다 | 이중 배열 감소 트라이 | 1183.01 | 1167.78 | 254.60 | 69.18 |
삼나무::다 주문됨=false | 이중 배열 감소 트라이 | 1183.03 | 1167.78 | 241.45 | 69.67 |
삼나무::다 | 이중 배열 접두사 트라이 | 621.59 | 619.38 | 246.88 | 57.83 |
삼나무::다 주문됨=false | 이중 배열 접두사 트라이 | 621.59 | 619.38 | 187.98 | 58.56 |
해트트리 2 (C) | HAT 트리 | 521.25 | 518.52 | 503.01 | 86.40 |
qp 트리(C) | QP 트라이 | 940.65 | 937.66 | 392.86 | 190.19 |
크리티컬 트라이(C) | 크리티컬 비트 트라이 | 1074.87 | 1071.98 | 430.04 | 347.60 |
주디SL(C) | 주디 배열 | 616.95 | 614.27 | 279.07 | 114.47 |
주디HS(C) | 주디 배열 | 722.29 | 719.47 | 439.66 | 372.25 |
tsl::배열_맵 | 배열 해시 테이블 | 826.98 | 682.99 | 612.31 | 139.16 |
tsl::배열_맵 예비로 | 배열 해시 테이블 | 565.37 | 555.35 | 246.55 | 126.32 |
tsl::hopscotch_map | 해시 테이블 | 1331.87 | 1078.02 | 375.19 | 118.08 |
tsl::hopscotch_map 예비로 | 해시 테이블 | 1080.51 | 1077.97 | 238.93 | 117.20 |
google::dense_hash_map | 해시 테이블 | 2325.27 | 1683.07 | 483.95 | 137.09 |
google::dense_hash_map 예비로 | 해시 테이블 | 1592.54 | 1589.99 | 257.22 | 113.71 |
spp::sparse_hash_map | 희소 해시 테이블 | 920.96 | 918.70 | 772.03 | 176.64 |
spp::sparse_hash_map 예비로 | 희소 해시 테이블 | 914.84 | 912.47 | 422.85 | 158.73 |
표준::순서가 없는_맵 | 해시 테이블 | 1249.09 | 1246.65 | 594.85 | 173.54 |
표준::순서가 없는_맵 예비로 | 해시 테이블 | 1212.21 | 1209.71 | 347.40 | 176.49 |
벤치마크는 Dr. Askitis의 "Distinct Strings" 데이터 세트의 모든 단어를 데이터 구조에 삽입하고, 사용된 메모리 공간을 확인하고, "Skew String Set 1" 데이터 세트의 모든 단어를 검색하는 것으로 구성됩니다. 여러 번 존재함)을 데이터 구조에 포함합니다. 이 데이터 세트의 문자열은 평균 및 중간 키 길이가 매우 짧습니다(위에서 사용된 Wikipedia 데이터 세트에 비해 현실적인 사용 사례가 아닐 수 있음). 삼나무 홈페이지의 내용과 비슷합니다.
벤치마크 프로토콜은 Wikipedia 데이터 세트와 동일합니다.
도서관 | 데이터 구조 | 최대 메모리(MiB) | 메모리(MiB) | 삽입(ns/키) | 읽기(ns/키) |
---|---|---|---|---|---|
tsl::htrie_map | HAT 트리 | 604.76 | 601.79 | 485.45 | 77.80 |
tsl::htrie_map max_load_factor=4 | HAT 트리 | 768.10 | 764.98 | 491.78 | 75.48 |
tsl::htrie_map max_load_factor=2 | HAT 트리 | 1002.42 | 999.34 | 496.78 | 72.53 |
tsl::htrie_map max_load_factor=1 | HAT 트리 | 1344.98 | 1341.97 | 520.66 | 72.45 |
삼나무::다 | 이중 어레이 트라이 | 1105.45 | 1100.05 | 682.25 | 71.98 |
삼나무::다 주문됨=false | 이중 어레이 트라이 | 1105.47 | 1100.05 | 668.75 | 71.95 |
삼나무::다 | 이중 배열 감소 트라이 | 941.16 | 926.04 | 684.38 | 79.11 |
삼나무::다 주문됨=false | 이중 배열 감소 트라이 | 941.16 | 925.98 | 672.14 | 79.02 |
삼나무::다 | 이중 배열 접두사 트라이 | 714.58 | 712.59 | 831.71 | 75.83 |
삼나무::다 주문됨=false | 이중 배열 접두사 트라이 | 714.66 | 712.31 | 786.93 | 75.89 |
해트트리 3 (C) | HAT 트리 | 786.93 | 784.32 | 743.34 | 93.58 |
qp 트리(C) | QP 트라이 | 1800.02 | 1797.21 | 987.95 | 428.51 |
크리티컬 트라이(C) | 크리티컬 비트 트라이 | 2210.52 | 2207.64 | 1986.19 | 1109.88 |
주디SL(C) | 주디 배열 | 1025.59 | 1023.11 | 535.02 | 202.36 |
주디HS(C) | 주디 배열 | 1002.50 | 999.97 | 456.09 | 148.36 |
tsl::배열_맵 | 배열 해시 테이블 | 1308.08 | 1031.67 | 545.82 | 46.41 |
tsl::배열_맵 예비로 | 배열 해시 테이블 | 979.44 | 921.363 | 244.19 | 45.74 |
tsl::hopscotch_map | 해시 테이블 | 2336.39 | 1611.54 | 288.70 | 47.05 |
tsl::hopscotch_map 예비로 | 해시 테이블 | 1614.22 | 1611.64 | 220.67 | 46.39 |
google::dense_hash_map | 해시 테이블 | 3913.64 | 2636.31 | 317.66 | 43.62 |
google::dense_hash_map 예비로 | 해시 테이블 | 2638.19 | 2635.68 | 227.58 | 43.09 |
spp::sparse_hash_map | 희소 해시 테이블 | 1419.69 | 1417.61 | 586.26 | 56.00 |
spp::sparse_hash_map 예비로 | 희소 해시 테이블 | 1424.21 | 1421.69 | 392.76 | 55.73 |
표준::순서가 없는_맵 | 해시 테이블 | 2112.66 | 2110.19 | 554.02 | 105.05 |
표준::순서가 없는_맵 예비로 | 해시 테이블 | 2053.95 | 2051.67 | 309.06 | 109.89 |
라이브러리를 사용하려면 포함 경로에 포함 디렉터리를 추가하기만 하면 됩니다. 헤더 전용 라이브러리입니다.
CMake를 사용하는 경우 target_link_libraries
와 함께 CMakeLists.txt에서 내보낸 tsl::hat_trie
대상을 사용할 수도 있습니다.
# Example where the hat-trie project is stored in a third-party directory
add_subdirectory (third-party/hat-trie)
target_link_libraries (your_target PRIVATE tsl::hat_trie)
코드는 모든 C++11 표준 호환 컴파일러에서 작동해야 하며 GCC 4.8.4, Clang 3.5.0 및 Visual Studio 2015에서 테스트되었습니다.
테스트를 실행하려면 Boost Test 라이브러리와 CMake가 필요합니다.
git clone https://github.com/Tessil/hat-trie.git
cd hat-trie/tests
mkdir build
cd build
cmake ..
cmake --build .
./tsl_hat_trie_tests
API는 여기에서 찾을 수 있습니다. std::string_view
사용할 수 있는 경우 API가 약간 변경되며 여기에서 찾을 수 있습니다.
# include < iostream >
# include < string >
# include < tsl/htrie_map.h >
# include < tsl/htrie_set.h >
int main () {
/*
* Map of strings to int having char as character type.
* There is no support for wchar_t, char16_t or char32_t yet,
* but UTF-8 strings will work fine.
*/
tsl::htrie_map< char , int > map = {{ " one " , 1 }, { " two " , 2 }};
map[ " three " ] = 3 ;
map[ " four " ] = 4 ;
map. insert ( " five " , 5 );
map. insert_ks ( " six_with_extra_chars_we_ignore " , 3 , 6 );
map. erase ( " two " );
/*
* Due to the compression on the common prefixes, the letters of the string
* are not always stored contiguously. When we retrieve the key, we have to
* construct it.
*
* To avoid a heap-allocation at each iteration (when SSO doesn't occur),
* we reuse the key_buffer to construct the key.
*/
std::string key_buffer;
for ( auto it = map. begin (); it != map. end (); ++it) {
it. key (key_buffer);
std::cout << " { " << key_buffer << " , " << it. value () << " } " << std::endl;
}
/*
* If you don't care about the allocation.
*/
for ( auto it = map. begin (); it != map. end (); ++it) {
std::cout << " { " << it. key () << " , " << *it << " } " << std::endl;
}
tsl::htrie_map< char , int > map2 = {{ " apple " , 1 }, { " mango " , 2 }, { " apricot " , 3 },
{ " mandarin " , 4 }, { " melon " , 5 }, { " macadamia " , 6 }};
// Prefix search
auto prefix_range = map2. equal_prefix_range ( " ma " );
// {mandarin, 4} {mango, 2} {macadamia, 6}
for ( auto it = prefix_range. first ; it != prefix_range. second ; ++it) {
std::cout << " { " << it. key () << " , " << *it << " } " << std::endl;
}
// Find longest match prefix.
auto longest_prefix = map2. longest_prefix ( " apple juice " );
if (longest_prefix != map2. end ()) {
// {apple, 1}
std::cout << " { " << longest_prefix. key () << " , "
<< *longest_prefix << " } " << std::endl;
}
// Prefix erase
map2. erase_prefix ( " ma " );
// {apricot, 3} {melon, 5} {apple, 1}
for ( auto it = map2. begin (); it != map2. end (); ++it) {
std::cout << " { " << it. key () << " , " << *it << " } " << std::endl;
}
tsl::htrie_set< char > set = { " one " , " two " , " three " };
set. insert ({ " four " , " five " });
// {one} {two} {five} {four} {three}
for ( auto it = set. begin (); it != set. end (); ++it) {
it. key (key_buffer);
std::cout << " { " << key_buffer << " } " << std::endl;
}
}
라이브러리는 지도나 세트를 직렬화 및 역직렬화하여 파일에 저장하거나 네트워크를 통해 전송할 수 있는 효율적인 방법을 제공합니다. 이렇게 하려면 사용자가 직렬화 및 역직렬화를 위한 함수 개체를 제공해야 합니다.
struct serializer {
// Must support the following types for U: std::uint64_t, float and T if a map is used.
template < typename U>
void operator ()( const U& value);
void operator ()( const CharT* value, std:: size_t value_size);
};
struct deserializer {
// Must support the following types for U: std::uint64_t, float and T if a map is used.
template < typename U>
U operator ()();
void operator ()(CharT* value_out, std:: size_t value_size);
};
구현 시 호환성이 필요한 경우 제공된 함수 객체에 직렬화/역직렬화하는 유형의 이진 호환성(엔디안, 부동 소수점 이진 표현, int 크기 등)이 남아 있습니다.
serialize
및 deserialize
메서드에 대한 자세한 내용은 API에서 확인할 수 있습니다.
# include < cassert >
# include < cstdint >
# include < fstream >
# include < type_traits >
# include < tsl/htrie_map.h >
class serializer {
public:
serializer ( const char * file_name) {
m_ostream. exceptions (m_ostream. badbit | m_ostream. failbit );
m_ostream. open (file_name);
}
template < class T ,
typename std::enable_if<std::is_arithmetic<T>::value>::type* = nullptr >
void operator ()( const T& value) {
m_ostream. write ( reinterpret_cast < const char *>(&value), sizeof (T));
}
void operator ()( const char * value, std:: size_t value_size) {
m_ostream. write (value, value_size);
}
private:
std::ofstream m_ostream;
};
class deserializer {
public:
deserializer ( const char * file_name) {
m_istream. exceptions (m_istream. badbit | m_istream. failbit | m_istream. eofbit );
m_istream. open (file_name);
}
template < class T ,
typename std::enable_if<std::is_arithmetic<T>::value>::type* = nullptr >
T operator ()() {
T value;
m_istream. read ( reinterpret_cast < char *>(&value), sizeof (T));
return value;
}
void operator ()( char * value_out, std:: size_t value_size) {
m_istream. read (value_out, value_size);
}
private:
std::ifstream m_istream;
};
int main () {
const tsl::htrie_map< char , std:: int64_t > map = {{ " one " , 1 }, { " two " , 2 },
{ " three " , 3 }, { " four " , 4 }};
const char * file_name = " htrie_map.data " ;
{
serializer serial (file_name);
map. serialize (serial);
}
{
deserializer dserial (file_name);
auto map_deserialized = tsl::htrie_map< char , std:: int64_t >:: deserialize (dserial);
assert (map == map_deserialized);
}
{
deserializer dserial (file_name);
/* *
* If the serialized and deserialized map are hash compatibles (see conditions in API),
* setting the argument to true speed-up the deserialization process as we don't have
* to recalculate the hash of each key. We also know how much space each bucket needs.
*/
const bool hash_compatible = true ;
auto map_deserialized =
tsl::htrie_map< char , std:: int64_t >:: deserialize (dserial, hash_compatible);
assert (map == map_deserialized);
}
}
직렬화할 유형이 더 복잡한 경우 일부 상용구를 피하기 위해 직렬화 라이브러리를 사용할 수 있습니다.
다음 예에서는 Boost zlib 압축 스트림과 함께 Boost 직렬화를 사용하여 직렬화된 결과 파일의 크기를 줄입니다.
# include < boost/archive/binary_iarchive.hpp >
# include < boost/archive/binary_oarchive.hpp >
# include < boost/iostreams/filter/zlib.hpp >
# include < boost/iostreams/filtering_stream.hpp >
# include < boost/serialization/split_free.hpp >
# include < boost/serialization/utility.hpp >
# include < cassert >
# include < cstdint >
# include < fstream >
# include < tsl/htrie_map.h >
template < typename Archive>
struct serializer {
Archive& ar;
template < typename T>
void operator ()( const T& val) { ar & val; }
template < typename CharT>
void operator ()( const CharT* val, std:: size_t val_size) {
ar. save_binary ( reinterpret_cast < const void *>(val), val_size* sizeof (CharT));
}
};
template < typename Archive>
struct deserializer {
Archive& ar;
template < typename T>
T operator ()() { T val; ar & val; return val; }
template < typename CharT>
void operator ()(CharT* val_out, std:: size_t val_size) {
ar. load_binary ( reinterpret_cast < void *>(val_out), val_size* sizeof (CharT));
}
};
namespace boost { namespace serialization {
template < class Archive , class CharT , class T >
void serialize (Archive & ar, tsl::htrie_map<CharT, T>& map, const unsigned int version) {
split_free (ar, map, version);
}
template < class Archive , class CharT , class T >
void save (Archive & ar, const tsl::htrie_map<CharT, T>& map, const unsigned int version) {
serializer<Archive> serial{ar};
map. serialize (serial);
}
template < class Archive , class CharT , class T >
void load (Archive & ar, tsl::htrie_map<CharT, T>& map, const unsigned int version) {
deserializer<Archive> deserial{ar};
map = tsl::htrie_map<CharT, T>:: deserialize (deserial);
}
}}
int main () {
const tsl::htrie_map< char , std:: int64_t > map = {{ " one " , 1 }, { " two " , 2 },
{ " three " , 3 }, { " four " , 4 }};
const char * file_name = " htrie_map.data " ;
{
std::ofstream ofs;
ofs. exceptions (ofs. badbit | ofs. failbit );
ofs. open (file_name, std::ios::binary);
boost::iostreams::filtering_ostream fo;
fo. push ( boost::iostreams::zlib_compressor ());
fo. push (ofs);
boost::archive::binary_oarchive oa (fo);
oa << map;
}
{
std::ifstream ifs;
ifs. exceptions (ifs. badbit | ifs. failbit | ifs. eofbit );
ifs. open (file_name, std::ios::binary);
boost::iostreams::filtering_istream fi;
fi. push ( boost::iostreams::zlib_decompressor ());
fi. push (ifs);
boost::archive::binary_iarchive ia (fi);
tsl::htrie_map< char , std:: int64_t > map_deserialized;
ia >> map_deserialized;
assert (map == map_deserialized);
}
}
코드는 MIT 라이선스에 따라 라이선스가 부여됩니다. 자세한 내용은 LICENSE 파일을 참조하세요.