Skip to content

Commit

Permalink
case-insensitive map_string_key
Browse files Browse the repository at this point in the history
  • Loading branch information
lihuiba committed Sep 27, 2024
1 parent 82d06bc commit c12f913
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 2 deletions.
30 changes: 30 additions & 0 deletions common/hash_combine.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
Copyright 2022 The Photon Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#pragma once
#include <unordered_set>

namespace photon {

inline size_t hash_combine(size_t a) { return a; }

template<typename...Ts> inline
size_t hash_combine(size_t a, size_t b, Ts...xs) {
auto x = (a<<6) + (a>>2) + 0x9e3779b9 + b;
return hash_combine(x, xs...);
}

} // namespace photon
72 changes: 70 additions & 2 deletions common/string-keyed.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
#pragma once

#include <photon/common/string_view.h>
#include <photon/common/hash_combine.h>
#include <cstring>
#include <unordered_map>
#include <map>
Expand Down Expand Up @@ -156,6 +157,43 @@ template<class T,
using unordered_map_string_key = basic_map_string_key<
std::unordered_map<string_key, T, Hasher, KeyEqual, Alloc>>;

class Hasher_CaseInsensitive {
const size_t BUF_CAP = 64;
size_t partial_hash(std::string_view sv, size_t i, size_t n) const {
char buf[BUF_CAP];
sv = sv.substr(i, n);
assert(sv.size() <= BUF_CAP);
for (size_t j = 0; j < sv.size(); ++j)
buf[j] = (char)tolower(sv[j]);
return std::hash<std::string_view>()({buf, n});
}
public:
size_t operator()(std::string_view sv) const {
size_t h = 0;
for (size_t i = 0; i < sv.size(); i += BUF_CAP) {
auto len = std::min(BUF_CAP, sv.size() - i);
auto ph = partial_hash(sv, i, len);
h = photon::hash_combine(h, ph);
}
return h;
}
};

class Equal_CaseInsensitive {
public:
bool operator()(std::string_view a, std::string_view b) const {
return a.size() == b.size() && strncasecmp(
a.begin(), b.begin(), a.size()) == 0;
}
};

template<class T,
class Hasher = Hasher_CaseInsensitive,
class KeyEqual = Equal_CaseInsensitive,
class Alloc = std::allocator<std::pair<const string_key, T>>>
using unordered_map_string_key_case_insensitive = basic_map_string_key<
std::unordered_map<string_key, T, Hasher, KeyEqual, Alloc>>;

template<class T,
class Pred = std::less<string_key>,
class Alloc = std::allocator<std::pair<const string_key,T>>>
Expand Down Expand Up @@ -189,6 +227,23 @@ class map_string_key : public basic_map_string_key<
}
};

class Less_CaseInsensitive {
public:
bool operator()(std::string_view a, std::string_view b) const {
auto len = std::min(a.size(), b.size());
auto cmp = strncasecmp(a.begin(), b.begin(), len);
if (cmp < 0) return true;
if (cmp > 0) return false;
return a.size() < b.size();
}
};

template<class T,
class Pred = Less_CaseInsensitive,
class Alloc = std::allocator<std::pair<const string_key,T>>>
using map_string_key_case_insensitive = basic_map_string_key<
std::map<string_key, T, Pred, Alloc>>;

// the String Key-Value (Mutable), stored together
// in a consecutive area, so as to save one allocation
class skvm : public string_key {
Expand Down Expand Up @@ -418,9 +473,16 @@ class basic_map_string_kv : public M
using unordered_map_string_kv = basic_map_string_kv<std::unordered_map<
skvm, size_t, std::hash<std::string_view>>>;

class map_string_kv : public basic_map_string_kv<std::map<skvm, size_t>> {
using unordered_map_string_kv_case_insensitive = basic_map_string_kv<
std::unordered_map<skvm, size_t, Hasher_CaseInsensitive,
Equal_CaseInsensitive>>;

template<class Compare = std::less<skvm>, class Allocator =
std::allocator<std::pair<const skvm, size_t>>>
class __basic_map_string_kv : public basic_map_string_kv<
std::map<skvm, size_t, Compare, Allocator>> {
public:
using base = basic_map_string_kv<std::map<skvm, size_t>>;
using base = basic_map_string_kv<std::map<skvm, size_t, Compare, Allocator>>;
using typename base::key_type;
using typename base::const_iterator;
using typename base::iterator;
Expand All @@ -445,3 +507,9 @@ class map_string_kv : public basic_map_string_kv<std::map<skvm, size_t>> {
return {base::upper_bound((const skvm&)k)};
}
};

using map_string_kv = __basic_map_string_kv<>;

using map_string_kv_case_insensitive =
__basic_map_string_kv<Less_CaseInsensitive>;

17 changes: 17 additions & 0 deletions common/test/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1204,6 +1204,23 @@ TEST(string_key, unordered_map_string_kv_perf) {
basic_map_test(test_map);
}

template<typename M> static
void test_map_case_insensitive() {
M m;
m.emplace("asdf", "jkl;");
auto it = m.find("ASDF");
EXPECT_NE(it, m.end());
EXPECT_EQ(it->second, "jkl;");
EXPECT_EQ(m.count("kuherqf"), 0);
}

TEST(string_key, case_insensitive) {
test_map_case_insensitive<unordered_map_string_key_case_insensitive<estring>>();
test_map_case_insensitive<unordered_map_string_kv_case_insensitive>();
test_map_case_insensitive<map_string_key_case_insensitive<estring>>();
test_map_case_insensitive<map_string_kv_case_insensitive>();
}

TEST(RangeLock, Basic) {
RangeLock m;

Expand Down
1 change: 1 addition & 0 deletions include/photon/common/hash_combine.h

0 comments on commit c12f913

Please sign in to comment.