Skip to content

Commit 6c3ee47

Browse files
committed
re-add <rsl/trie>
1 parent 6b729a4 commit 6c3ee47

4 files changed

Lines changed: 223 additions & 1 deletion

File tree

include/rsl/trie

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
#pragma once
2+
3+
#include <algorithm>
4+
#include <string_view>
5+
#include <string>
6+
#include <cstddef>
7+
#include <vector>
8+
#include <utility>
9+
#include <iterator>
10+
#include <meta>
11+
12+
#include <rsl/string_constant>
13+
#include <rsl/string_view>
14+
#include <rsl/span>
15+
#include <rsl/assert>
16+
17+
namespace rsl {
18+
namespace _trie_impl {
19+
template <auto... Transitions>
20+
struct State {
21+
rsl::string_view prefix;
22+
int word_index;
23+
24+
[[clang::always_inline]]
25+
constexpr int visit(std::string_view str) const {
26+
// assumes the prefix is never empty
27+
//? empty prefixes are only allowed at the root note
28+
if (str.empty() || str[0] != prefix[0] || str.size() < prefix.size()) {
29+
return 0;
30+
}
31+
32+
if (str.size() == prefix.size()) {
33+
// str would become empty after removing the prefix
34+
return word_index;
35+
}
36+
37+
str.remove_prefix(prefix.size());
38+
template for (constexpr auto T : {Transitions...}) {
39+
if (int result = T.visit(str); result) {
40+
return result;
41+
}
42+
}
43+
return 0;
44+
}
45+
};
46+
47+
template <std::meta::info Words, auto... Transitions>
48+
struct Root {
49+
static constexpr auto&& words = [:Words:];
50+
51+
constexpr static bool matches(std::string_view str) {
52+
if (str.empty()) {
53+
return false;
54+
}
55+
return find(str) >= 0;
56+
}
57+
58+
constexpr static int find(std::string_view str) {
59+
int result = -1;
60+
if (str.empty()) {
61+
return -1;
62+
}
63+
template for (constexpr auto T : {Transitions...}) {
64+
if (int result = T.visit(str); result && str == words[result - 1]) {
65+
return result - 1;
66+
}
67+
}
68+
return -1;
69+
}
70+
};
71+
72+
template <auto... Transitions>
73+
constexpr inline Root<Transitions...> make_trie{};
74+
75+
template <string_constant transition, int word_index, auto... Transitions>
76+
constexpr inline auto make_state = State<Transitions...>{
77+
{transition.data, transition.size},
78+
word_index
79+
};
80+
81+
struct ParsedState {
82+
std::string prefix;
83+
std::vector<ParsedState> transitions;
84+
rsl::span<rsl::string_view> words;
85+
int word_index = 0;
86+
87+
static constexpr ParsedState make(rsl::span<rsl::string_view> words) {
88+
std::vector<rsl::string_view> sorted_words{};
89+
sorted_words.assign_range(words);
90+
std::ranges::sort(sorted_words);
91+
92+
ParsedState root{"", {}, words};
93+
94+
for (auto word : sorted_words) {
95+
ParsedState* node = &root;
96+
std::size_t i = 0;
97+
98+
while (i < word.size()) {
99+
auto it = std::ranges::find_if(node->transitions, [&](const ParsedState& s) {
100+
return s.prefix.starts_with(word[i]);
101+
});
102+
103+
if (it == node->transitions.end()) {
104+
int word_idx = std::ranges::distance(words.begin(), std::ranges::find(words, word)) + 1;
105+
106+
node->transitions.emplace_back(std::string(word.substr(i)),
107+
std::vector<ParsedState>{
108+
{"", {}, {}, word_idx}
109+
});
110+
break;
111+
}
112+
113+
std::size_t const match_len =
114+
std::mismatch(it->prefix.begin(), it->prefix.end(), word.begin() + i).first -
115+
it->prefix.begin();
116+
117+
if (match_len < it->prefix.size()) {
118+
ParsedState split_node{it->prefix.substr(match_len), std::move(it->transitions)};
119+
it->prefix.resize(match_len);
120+
it->transitions = {std::move(split_node)};
121+
}
122+
123+
i += match_len;
124+
node = &*it;
125+
}
126+
}
127+
return root;
128+
}
129+
130+
explicit(false) consteval operator std::meta::info() const {
131+
std::vector<std::meta::info> states = {};
132+
int index = 0;
133+
for (auto&& state : transitions) {
134+
if (state.prefix.empty()) {
135+
index = state.word_index;
136+
continue;
137+
}
138+
states.push_back(state);
139+
}
140+
141+
if (!transitions.empty() && prefix.empty()) {
142+
// prevent aggressive inlining at the root level
143+
std::vector<rsl::string_view> word_list;
144+
for (auto word : words) {
145+
word_list.emplace_back(define_static_string(word));
146+
}
147+
states.insert(states.begin(), reflect_constant(std::meta::reflect_constant_array(word_list)));
148+
return substitute(^^make_trie, states);
149+
}
150+
151+
std::vector args = {std::meta::reflect_constant_string(prefix),
152+
std::meta::reflect_constant(index)};
153+
for (auto state : states) {
154+
args.push_back(state);
155+
}
156+
return substitute(^^make_state, args);
157+
}
158+
};
159+
160+
constexpr bool has_duplicates(std::span<rsl::string_view> wordlist) {
161+
std::ranges::sort(wordlist);
162+
return std::ranges::adjacent_find(wordlist) != wordlist.end();
163+
}
164+
} // namespace _trie_impl
165+
166+
struct trie {
167+
bool (*matches)(std::string_view str) = nullptr;
168+
int (*find)(std::string_view str) = nullptr;
169+
170+
explicit consteval trie(std::vector<rsl::string_view> words) {
171+
constexpr_assert(!words.empty(), "An empty word list is not allowed.");
172+
constexpr_assert(!_trie_impl::has_duplicates(words),
173+
"Duplicates in the word list are not allowed.");
174+
175+
(this->*extract<void (trie::*)()>(
176+
substitute(^^assign_handlers, {_trie_impl::ParsedState::make(words)})))();
177+
}
178+
179+
private:
180+
template <auto Parser>
181+
constexpr void assign_handlers() {
182+
matches = &Parser.matches;
183+
find = &Parser.find;
184+
}
185+
};
186+
} // namespace rsl::cli

test/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ add_subdirectory(string_view)
1010
add_subdirectory(span)
1111
add_subdirectory(format)
1212
add_subdirectory(kwargs)
13-
add_subdirectory(enum)
13+
add_subdirectory(enum)
14+
add_subdirectory(trie)

test/trie/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
target_sources(rsl-util-test PRIVATE trie.cpp)

test/trie/trie.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#include <gtest/gtest.h>
2+
3+
#include <string>
4+
#include <rsl/trie>
5+
6+
7+
TEST(Trie, Matches) {
8+
auto trie = rsl::trie{
9+
{"apple", "banana", "cherry"}
10+
};
11+
12+
ASSERT_TRUE(trie.matches("apple"));
13+
ASSERT_TRUE(trie.matches("banana"));
14+
ASSERT_TRUE(trie.matches("cherry"));
15+
16+
ASSERT_FALSE(trie.matches("pear"));
17+
ASSERT_FALSE(trie.matches(""));
18+
ASSERT_FALSE(trie.matches("APPLE"));
19+
}
20+
21+
22+
TEST(Trie, Find) {
23+
auto trie = rsl::trie{
24+
{"apple", "banana", "cherry"}
25+
};
26+
27+
ASSERT_EQ(trie.find("apple"), 0);
28+
ASSERT_EQ(trie.find("banana"), 1);
29+
ASSERT_EQ(trie.find("cherry"), 2);
30+
31+
ASSERT_EQ(trie.find("pear"), -1);
32+
ASSERT_EQ(trie.find(""), -1);
33+
ASSERT_EQ(trie.find("APPLE"), -1);
34+
}

0 commit comments

Comments
 (0)