├── .gitignore
├── CMakeLists.txt
├── LICENSE
├── README.md
├── include
├── words-extract-match.h
├── words-extract-small.h
├── words-extract.h
└── words.h
├── main.cpp
└── switch_fnv1a.h
/.gitignore:
--------------------------------------------------------------------------------
1 | # Prerequisites
2 | *.d
3 |
4 | # Compiled Object files
5 | *.slo
6 | *.lo
7 | *.o
8 | *.obj
9 |
10 | # Precompiled Headers
11 | *.gch
12 | *.pch
13 |
14 | # Compiled Dynamic libraries
15 | *.so
16 | *.dylib
17 | *.dll
18 |
19 | # Fortran module files
20 | *.mod
21 | *.smod
22 |
23 | # Compiled Static libraries
24 | *.lai
25 | *.la
26 | *.a
27 | *.lib
28 |
29 | # Executables
30 | *.exe
31 | *.out
32 | *.app
33 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.10)
2 | project(stringswitch)
3 |
4 | if(NOT CMAKE_BUILD_TYPE)
5 | set(CMAKE_BUILD_TYPE Release)
6 | endif()
7 | set(CMAKE_CXX_FLAGS "-Wall -Wextra")
8 | set(CMAKE_CXX_FLAGS_DEBUG "-g")
9 | set(CMAKE_CXX_FLAGS_RELEASE "-Ofast")
10 |
11 | add_executable(demo main.cpp)
12 | set_property(TARGET demo PROPERTY CMAKE_CXX_STANDARD_REQUIRED ON)
13 | set_property(TARGET demo PROPERTY CXX_STANDARD 17)
14 | set_property(TARGET demo PROPERTY CMAKE_CXX_EXTENSIONS OFF)
15 |
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Xavier Roche
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Switch/Case strings using constexpr long hash
2 |
3 | ## The Problem
4 |
5 | * To avoid questionable code like this:
6 |
7 | ```c++
8 | } else if (key == "poney") {
9 | } else if (key == "elephant") {
10 | } else if (key == "dog") {
11 | } else if (key == "kitten") {
12 | ```
13 |
14 | ie.: *cumbersome, and not quite good in term of performances*
15 |
16 | * We basically want to do that in `C++`:
17 |
18 | ```c++
19 | switch(argv[i]) {
20 | case "poney":
21 | break;
22 | case "elephant":
23 | break;
24 | case "dog":
25 | break;
26 | case "kitten":
27 | break;
28 | }
29 | ```
30 |
31 | ## The Solution
32 |
33 | We use a `constexpr` metaprogrammed long hash, with fancy custom string prefixes, allowing to do *nearly* what we want:
34 |
35 | ```c++
36 | switch(fnv1a128::hash(argv[i])) {
37 | case "poney"_fnv1a128:
38 | break;
39 | case "elephant"_fnv1a128:
40 | break;
41 | case "dog"_fnv1a128:
42 | break;
43 | case "kitten"_fnv1a128:
44 | break;
45 | }
46 | ```
47 |
48 | 👉 See the [`switch_fnv1a.h`](switch_fnv1a.h) source code and the [`main.cpp`](main.cpp) sample.
49 |
50 | ## Discussion
51 |
52 | ### Is This Good Enough ?
53 |
54 | #### Simplicity
55 |
56 | Nearly what we want, only need a call to `fnv1a128::hash` in the `switch`, and `_fnv1a128` string prefix in `case`.
57 |
58 | #### Collisions
59 |
60 | Using a rather known *128-bit* hashing (Fnv1-a) *should* limit the risks of accidental collisions (odds are `1/2^64` if the hash is truly distributed). FNV hashes are typically [designed to be fast while maintaining a low collision rate](https://tools.ietf.org/html/draft-eastlake-fnv-16).
61 |
62 | Collision rate of `1/2^64` is considered low enough not to worry about them. Risks of being killed by a meteor each year (> `1/2^27`) is several orders of magnitude more worrying, actually ([The Economist](https://www.cnet.com/news/odds-of-dying-from-an-asteroid-strike-1-in-74817414/) and [W. Casey, *Cybersecurity and Applied Mathematics*](https://www.sciencedirect.com/book/9780128044520/cybersecurity-and-applied-mathematics)).
63 |
64 | #### Attacks
65 |
66 | Question remains on *non-accidental* collisions (ie. *attacks*). One possible solution would be to use a stronger hash (ie. cryptographic hash) such as SHA256 (~~XOR-folded~~ [truncated](https://csrc.nist.gov/csrc/media/events/first-cryptographic-hash-workshop/documents/kelsey_truncation.pdf) to 128-bits), such as [this one](https://github.com/aguinet/sha256_literal) or [this one](https://github.com/elbeno/constexpr/), but this would make the initial string computation much slower, not mentioning the build time.
67 |
68 | As [suggested](https://tools.ietf.org/html/draft-eastlake-fnv-16#section-2.2), mitigating possible attacks could involve a initial salt, such as `fnv1a128::hash(__FILE__)` or `fnv1a128::hash(__DATE__)` (with non-reproducible build), but this would not allow to mitigate attacks when compiled code itself is known.
69 |
70 | #### Performances
71 |
72 | * All hash string literal are computed at **build-time**
73 | * Hash is computed once at runtime (for the input to be compared) with a reasonable cost (ie. one 128-bit multiply and one 8-bit xor per character)
74 | * The `switch/case` construct is rather well-optimized (using binary search-like dispatch, ie. `O(log(N))`)
75 | * 128-bit `__uint128_t` are rather well-handled for this usage (thanks to AVX extensions)
76 |
77 | Three `std::string::operator==` calls are typically as slow as a complete `switch/case` set containing 1,000 different cases.
78 |
79 | *All these results need to be taken with a grain of salt, being based on highly volatile micro-benchmarks, you have been warned.*
80 |
81 | #### Benchmarks (micro-benchmarks only)
82 |
83 | Quick benchmarks with output emitted (redirected to `/dev/null`)
84 |
85 | * Matching 100,000 times 100 different strings with **10 cases**, printing them to stdout: **1.5x** faster
86 | * Matching 100,000 times 100 different strings with **100 cases**, printing them to stdout: **3.9x** faster
87 | * Matching 100,000 times 100 different strings with **1000 cases**, printing them to stdout: **120x** faster
88 |
89 | Quick benchmarks with no output emitted (pure computation)
90 |
91 | * Matching 100,000 times 100 different strings with **10 cases**: **2.3x** faster
92 | * Matching 100,000 times 100 different strings with **100 cases**: **9x** faster
93 | * Matching 100,000 times 100 different strings with **1000 cases**: **400x** faster
94 |
95 | Further tests are needed to assess the impact on performances on real-world code (such as configuration parsing code typically).
96 |
97 | #### Unit Tests
98 |
99 | The wonders of meta-programming allows you to actually integrate unit tests in the code itself:
100 |
101 | ```c++
102 | // Static unit tests:
103 | static_assert("hello"_fnv1a32 == 0x4f9f2cab);
104 | static_assert("hello"_fnv1a64 == 0xa430d84680aabd0b);
105 | static_assert("hello"_fnv1a128 == Pack128(0xe3e1efd54283d94f, 0x7081314b599d31b3));
106 | ```
107 |
108 | ### Disasembled Example
109 |
110 | As an example, let's see how the compiler dispatches four `case`:
111 |
112 | ```c++
113 | const char* dispatch(const __uint128_t match)
114 | {
115 | switch (match) {
116 | case "poney"_fnv1a128:
117 | return "I want one, too!";
118 | case "elephant"_fnv1a128:
119 | return "Not in my apartment please!";
120 | case "dog"_fnv1a128:
121 | return "Good puppy!";
122 | case "kitten"_fnv1a128:
123 | return "Aawwwwwwww!";
124 | default:
125 | return "Don't know this animal!";
126 | }
127 | }
128 | ```
129 |
130 | Even for four cases (and a default), the compiler optimized already the tests by with binary search: the "dog" and poney cases blocks are split from the "kitten" and "elephant" ones.
131 |
132 | The first test is done through two regular 64-bit comparisons (and immediate values), the remaining ones are using AVX extensions (comparisons with PC-relative indexed address).
133 |
134 | ```asm
135 | movabs $0x6efcb17ab10f2a3d,%rax # ("kitten"_fnv1a128 - 1)&FFFFFFFF
136 | cmp %rdi,%rax
137 | movabs $0xfcd05704e13c64bf,%rax # ("kitten"_fnv1a128 - 1)>> 64
138 | movq %rdi,%xmm0
139 | movq %rsi,%xmm1
140 | punpcklqdq %xmm1,%xmm0
141 | sbb %rsi,%rax
142 | jl 0x400bba
143 |
144 | movdqa 0x17bce(%rip),%xmm1 # "dog"_fnv1a128 (__uint128_t)
145 | pcmpeqb %xmm0,%xmm1
146 | pmovmskb %xmm1,%eax
147 | cmp $0xffff,%eax
148 | je 0x400bf0
149 |
150 | pcmpeqb 0x17bc7(%rip),%xmm0 # "poney"_fnv1a128 (__uint128_t)
151 | pmovmskb %xmm0,%eax
152 | cmp $0xffff,%eax
153 | jne 0x400bea
154 |
155 | mov $0x41c6a0,%eax # "I want one, too!" (const char*)
156 | retq
157 |
158 | test_kitten:
159 | movdqa 0x17b7e(%rip),%xmm1 # "kitten"_fnv1a128 (__uint128_t)
160 | pcmpeqb %xmm0,%xmm1
161 | pmovmskb %xmm1,%eax
162 | cmp $0xffff,%eax
163 | je 0x400bf6
164 |
165 | pcmpeqb 0x17b77(%rip),%xmm0 # "elephant"_fnv1a128 (__uint128_t)
166 | pmovmskb %xmm0,%eax
167 | cmp $0xffff,%eax
168 |
169 | jne 0x400bea
170 | mov $0x41c6b1,%eax # "Not in my apartment please!" (const char*)
171 | retq
172 |
173 | unknown:
174 | mov $0x41c6e6,%eax # "Don't know this animal!" (const char*)
175 | retq
176 |
177 | puppy:
178 | mov $0x41c6ce,%eax # "Good puppy!" (const char*)
179 | retq
180 |
181 | kitten:
182 | mov $0x41c6da,%eax # "Aawwwwwwww! (const char*)
183 | retq
184 | ```
185 |
186 | The initial hash computation itself is rather well-optimized, as the 128-bit prime number used (0x1000000000000000000013b) is well-fit for split 64-bit `mul`.
187 |
188 | ```asm
189 | test %rsi,%rsi
190 | je 0x400de9
191 |
192 | movabs $0x6c62272e07bb0142,%rcx
193 | movabs $0x62b821756295c58d,%rax
194 | lea -0x1(%rsi),%rdx
195 | mov %esi,%r9d
196 | and $0x3,%r9d
197 | cmp $0x3,%rdx
198 | jae 0x400dfe
199 | xor %edx,%edx
200 | xor %r10d,%r10d
201 | test %r9,%r9
202 | jne 0x400ea9
203 |
204 | label_ret:
205 | retq
206 |
207 | empty_string:
208 | movabs $0x6c62272e07bb0142,%rdx
209 | movabs $0x62b821756295c58d,%rax
210 | retq
211 |
212 | label2:
213 | push %rbx
214 | sub %r9,%rsi
215 | xor %r10d,%r10d
216 | mov $0x13b,%r11d
217 | nopl 0x0(%rax,%rax,1)
218 |
219 | label3:
220 | movzbl (%rdi,%r10,1),%r8d
221 | xor %rax,%r8
222 | mov %r8,%rax
223 | mul %r11
224 | shl $0x18,%r8
225 | add %rdx,%r8
226 | imul $0x13b,%rcx,%rbx
227 | add %r8,%rbx
228 | movzbl 0x1(%rdi,%r10,1),%ecx
229 | xor %rax,%rcx
230 | mov %rcx,%rax
231 | mul %r11
232 | shl $0x18,%rcx
233 | add %rdx,%rcx
234 | imul $0x13b,%rbx,%rbx
235 | add %rcx,%rbx
236 | movzbl 0x2(%rdi,%r10,1),%ecx
237 | xor %rax,%rcx
238 | mov %rcx,%rax
239 | mul %r11
240 | shl $0x18,%rcx
241 | add %rdx,%rcx
242 | imul $0x13b,%rbx,%rdx
243 | add %rcx,%rdx
244 | movzbl 0x3(%rdi,%r10,1),%ecx
245 | xor %rax,%rcx
246 | imul $0x13b,%rdx,%rbx
247 | mov %rcx,%rax
248 | mul %r11
249 | shl $0x18,%rcx
250 | add %rdx,%rcx
251 | add %rbx,%rcx
252 | add $0x4,%r10
253 | cmp %r10,%rsi
254 | jne 0x400e10
255 | mov %rcx,%rdx
256 | pop %rbx
257 | test %r9,%r9
258 | je 0x400de8
259 |
260 | label4:
261 | add %r10,%rdi
262 | neg %r9
263 | mov $0x13b,%r8d
264 | data16 nopw %cs:0x0(%rax,%rax,1)
265 | label5:
266 | movzbl (%rdi),%esi
267 | xor %rax,%rsi
268 | mov %rsi,%rax
269 | mul %r8
270 | shl $0x18,%rsi
271 | add %rdx,%rsi
272 | imul $0x13b,%rcx,%rcx
273 | add %rsi,%rcx
274 | add $0x1,%rdi
275 | add $0x1,%r9
276 | jne 0x400ec0
277 | mov %rcx,%rdx
278 | retq
279 | ```
280 |
281 | ## Thanks
282 |
283 | Thanks to [Algolia](https://www.algolia.com/) for giving me the opportunity to play with those kind of stuff during my work!
284 |
--------------------------------------------------------------------------------
/include/words-extract-match.h:
--------------------------------------------------------------------------------
1 | /* tail -100 include/words-extract.h
2 | */
3 |
4 | WORD("tobaccos");
5 | WORD("plannings");
6 | WORD("press");
7 | WORD("muggers");
8 | WORD("fortress");
9 | WORD("reappoints");
10 | WORD("scruples");
11 | WORD("gnomes");
12 | WORD("apocalyptic");
13 | WORD("moderates");
14 | WORD("halving");
15 | WORD("weathering");
16 | WORD("blowouts");
17 | WORD("distinction");
18 | WORD("dumping");
19 | WORD("poultry");
20 | WORD("therapists");
21 | WORD("maximum");
22 | WORD("defers");
23 | WORD("lankier");
24 | WORD("approached");
25 | WORD("winked");
26 | WORD("construing");
27 | WORD("paradoxical");
28 | WORD("orthogonality");
29 | WORD("localizing");
30 | WORD("fleecing");
31 | WORD("delimit");
32 | WORD("footlights");
33 | WORD("blessedly");
34 | WORD("interrogated");
35 | WORD("fantasy");
36 | WORD("foxtrots");
37 | WORD("hit");
38 | WORD("malaise");
39 | WORD("invisibility");
40 | WORD("correcter");
41 | WORD("prolongs");
42 | WORD("supporters");
43 | WORD("hubbub");
44 | WORD("audible");
45 | WORD("scrutiny");
46 | WORD("smells");
47 | WORD("detaching");
48 | WORD("castings");
49 | WORD("nominatives");
50 | WORD("solemnly");
51 | WORD("babysitters");
52 | WORD("morbid");
53 | WORD("invariable");
54 | WORD("kinswoman");
55 | WORD("moles");
56 | WORD("cowardice");
57 | WORD("prawning");
58 | WORD("falloff");
59 | WORD("bulimia");
60 | WORD("aptitude");
61 | WORD("diploma");
62 | WORD("scholars");
63 | WORD("broadens");
64 | WORD("persecutes");
65 | WORD("retrospection");
66 | WORD("palms");
67 | WORD("daughter");
68 | WORD("misgiving");
69 | WORD("variations");
70 | WORD("underclassmen");
71 | WORD("coronae");
72 | WORD("rivets");
73 | WORD("bipartisan");
74 | WORD("blow");
75 | WORD("repairing");
76 | WORD("dourer");
77 | WORD("scants");
78 | WORD("moms");
79 | WORD("sloshed");
80 | WORD("equalizer");
81 | WORD("caddied");
82 | WORD("flattery");
83 | WORD("meritorious");
84 | WORD("propagates");
85 | WORD("unsympathetic");
86 | WORD("casuals");
87 | WORD("gobbing");
88 | WORD("aground");
89 | WORD("excavator");
90 | WORD("possessiveness");
91 | WORD("cracked");
92 | WORD("canneries");
93 | WORD("gambles");
94 | WORD("nightclubbed");
95 | WORD("catchwords");
96 | WORD("quashing");
97 | WORD("summons");
98 | WORD("fizzing");
99 | WORD("noisiness");
100 | WORD("shapeliness");
101 | WORD("ligatures");
102 | WORD("aphrodisiac");
103 | WORD("quicker");
104 |
--------------------------------------------------------------------------------
/include/words-extract-small.h:
--------------------------------------------------------------------------------
1 | WORD("tobaccos");
2 | WORD("plannings");
3 | WORD("press");
4 | WORD("muggers");
5 | WORD("fortress");
6 | WORD("reappoints");
7 | WORD("scruples");
8 | WORD("gnomes");
9 | WORD("apocalyptic");
10 | WORD("moderates");
11 | WORD("halving");
12 | WORD("weathering");
13 | WORD("blowouts");
14 | WORD("distinction");
15 | WORD("dumping");
16 | WORD("poultry");
17 | WORD("therapists");
18 | WORD("maximum");
19 | WORD("defers");
20 | WORD("lankier");
21 | WORD("approached");
22 | WORD("winked");
23 | WORD("construing");
24 | WORD("paradoxical");
25 | WORD("orthogonality");
26 | WORD("localizing");
27 | WORD("fleecing");
28 | WORD("delimit");
29 | WORD("footlights");
30 | WORD("blessedly");
31 | WORD("interrogated");
32 | WORD("fantasy");
33 | WORD("foxtrots");
34 | WORD("hit");
35 | WORD("malaise");
36 | WORD("invisibility");
37 | WORD("correcter");
38 | WORD("prolongs");
39 | WORD("supporters");
40 | WORD("hubbub");
41 | WORD("audible");
42 | WORD("scrutiny");
43 | WORD("smells");
44 | WORD("detaching");
45 | WORD("castings");
46 | WORD("nominatives");
47 | WORD("solemnly");
48 | WORD("babysitters");
49 | WORD("morbid");
50 | WORD("invariable");
51 | WORD("kinswoman");
52 | WORD("moles");
53 | WORD("cowardice");
54 | WORD("prawning");
55 | WORD("falloff");
56 | WORD("bulimia");
57 | WORD("aptitude");
58 | WORD("diploma");
59 | WORD("scholars");
60 | WORD("broadens");
61 | WORD("persecutes");
62 | WORD("retrospection");
63 | WORD("palms");
64 | WORD("daughter");
65 | WORD("misgiving");
66 | WORD("variations");
67 | WORD("underclassmen");
68 | WORD("coronae");
69 | WORD("rivets");
70 | WORD("bipartisan");
71 | WORD("blow");
72 | WORD("repairing");
73 | WORD("dourer");
74 | WORD("scants");
75 | WORD("moms");
76 | WORD("sloshed");
77 | WORD("equalizer");
78 | WORD("caddied");
79 | WORD("flattery");
80 | WORD("meritorious");
81 | WORD("propagates");
82 | WORD("unsympathetic");
83 | WORD("casuals");
84 | WORD("gobbing");
85 | WORD("aground");
86 | WORD("excavator");
87 | WORD("possessiveness");
88 | WORD("cracked");
89 | WORD("canneries");
90 | WORD("gambles");
91 | WORD("nightclubbed");
92 | WORD("catchwords");
93 | WORD("quashing");
94 | WORD("summons");
95 | WORD("fizzing");
96 | WORD("noisiness");
97 | WORD("shapeliness");
98 | WORD("ligatures");
99 | WORD("aphrodisiac");
100 | WORD("quicker");
101 |
--------------------------------------------------------------------------------
/include/words-extract.h:
--------------------------------------------------------------------------------
1 | /* grep -v "'" /usr/share/dict/words|grep -E '^[a-z]'|sort -R|head -1000|sed -e 's/^/WORD("/' -e 's/$/");/' > include/words-extract.h
2 | */
3 |
4 | WORD("tapped");
5 | WORD("existing");
6 | WORD("brightest");
7 | WORD("spirituals");
8 | WORD("frenziedly");
9 | WORD("uptown");
10 | WORD("cankers");
11 | WORD("our");
12 | WORD("supermodel");
13 | WORD("haste");
14 | WORD("oakum");
15 | WORD("inventing");
16 | WORD("flotilla");
17 | WORD("dopes");
18 | WORD("rowboats");
19 | WORD("cider");
20 | WORD("bulletining");
21 | WORD("bungs");
22 | WORD("privatizes");
23 | WORD("canvasback");
24 | WORD("widespread");
25 | WORD("piled");
26 | WORD("featherweights");
27 | WORD("hides");
28 | WORD("paternal");
29 | WORD("palliates");
30 | WORD("rockers");
31 | WORD("thunders");
32 | WORD("fogeys");
33 | WORD("probationer");
34 | WORD("nifty");
35 | WORD("sunny");
36 | WORD("prattle");
37 | WORD("idealistic");
38 | WORD("tether");
39 | WORD("stragglers");
40 | WORD("nocturne");
41 | WORD("flusters");
42 | WORD("monotheism");
43 | WORD("jealously");
44 | WORD("afghan");
45 | WORD("dropping");
46 | WORD("customers");
47 | WORD("provincial");
48 | WORD("donut");
49 | WORD("unintentional");
50 | WORD("decals");
51 | WORD("regenerates");
52 | WORD("vengeful");
53 | WORD("aping");
54 | WORD("smoker");
55 | WORD("racketeer");
56 | WORD("transfuses");
57 | WORD("drawls");
58 | WORD("spins");
59 | WORD("tight");
60 | WORD("levee");
61 | WORD("sidestep");
62 | WORD("coaxes");
63 | WORD("nationalization");
64 | WORD("movables");
65 | WORD("vibrant");
66 | WORD("caramel");
67 | WORD("sympathies");
68 | WORD("middles");
69 | WORD("annoy");
70 | WORD("quit");
71 | WORD("pizzas");
72 | WORD("bookworms");
73 | WORD("bushwhackers");
74 | WORD("weighs");
75 | WORD("hourly");
76 | WORD("typhoid");
77 | WORD("landslidden");
78 | WORD("spending");
79 | WORD("mizzenmast");
80 | WORD("inundation");
81 | WORD("frocks");
82 | WORD("lanes");
83 | WORD("annexes");
84 | WORD("shading");
85 | WORD("cynicism");
86 | WORD("henchman");
87 | WORD("edifice");
88 | WORD("backpack");
89 | WORD("unexpectedly");
90 | WORD("cradled");
91 | WORD("alloying");
92 | WORD("rattans");
93 | WORD("reasonably");
94 | WORD("emancipate");
95 | WORD("allergens");
96 | WORD("revivified");
97 | WORD("chiselled");
98 | WORD("jitters");
99 | WORD("latitudinal");
100 | WORD("frighting");
101 | WORD("coruscating");
102 | WORD("cambers");
103 | WORD("gummiest");
104 | WORD("pelvic");
105 | WORD("dimension");
106 | WORD("contracting");
107 | WORD("pinhole");
108 | WORD("nefarious");
109 | WORD("straitens");
110 | WORD("hamper");
111 | WORD("distrust");
112 | WORD("dozing");
113 | WORD("reformat");
114 | WORD("photograph");
115 | WORD("decreed");
116 | WORD("steamed");
117 | WORD("eighth");
118 | WORD("diplomatic");
119 | WORD("mushrooms");
120 | WORD("flees");
121 | WORD("duplicated");
122 | WORD("felons");
123 | WORD("blacksmiths");
124 | WORD("gems");
125 | WORD("county");
126 | WORD("binge");
127 | WORD("expunges");
128 | WORD("lactates");
129 | WORD("tatty");
130 | WORD("overconfident");
131 | WORD("indecisive");
132 | WORD("inheritors");
133 | WORD("curtseyed");
134 | WORD("irks");
135 | WORD("backbit");
136 | WORD("cubes");
137 | WORD("interpreters");
138 | WORD("example");
139 | WORD("gravels");
140 | WORD("pagodas");
141 | WORD("unruffled");
142 | WORD("jaunty");
143 | WORD("sleepy");
144 | WORD("foppish");
145 | WORD("doff");
146 | WORD("give");
147 | WORD("launder");
148 | WORD("lemons");
149 | WORD("kiddos");
150 | WORD("unseemliness");
151 | WORD("majorities");
152 | WORD("genera");
153 | WORD("mains");
154 | WORD("telecommuting");
155 | WORD("naivest");
156 | WORD("achievable");
157 | WORD("irrigates");
158 | WORD("spellbinder");
159 | WORD("chroniclers");
160 | WORD("entanglement");
161 | WORD("cyanide");
162 | WORD("amity");
163 | WORD("vanities");
164 | WORD("brisker");
165 | WORD("tonsils");
166 | WORD("bluebirds");
167 | WORD("quaver");
168 | WORD("devastated");
169 | WORD("lynching");
170 | WORD("operational");
171 | WORD("augurs");
172 | WORD("industry");
173 | WORD("veering");
174 | WORD("stranger");
175 | WORD("gizmo");
176 | WORD("floodlighting");
177 | WORD("scooted");
178 | WORD("womanhood");
179 | WORD("windfalls");
180 | WORD("outsmart");
181 | WORD("propels");
182 | WORD("insemination");
183 | WORD("constantly");
184 | WORD("judged");
185 | WORD("bovine");
186 | WORD("touting");
187 | WORD("dissolute");
188 | WORD("esthete");
189 | WORD("machining");
190 | WORD("outrageously");
191 | WORD("obstetrical");
192 | WORD("religiously");
193 | WORD("recapitulated");
194 | WORD("sensible");
195 | WORD("parsonage");
196 | WORD("metamorphosing");
197 | WORD("momma");
198 | WORD("seaplanes");
199 | WORD("introduces");
200 | WORD("sachets");
201 | WORD("measurable");
202 | WORD("forsworn");
203 | WORD("savor");
204 | WORD("purulent");
205 | WORD("appetite");
206 | WORD("undergoing");
207 | WORD("interrogatory");
208 | WORD("untouched");
209 | WORD("unsigned");
210 | WORD("employers");
211 | WORD("hedonistic");
212 | WORD("wimples");
213 | WORD("betiding");
214 | WORD("melodiousness");
215 | WORD("helots");
216 | WORD("specifically");
217 | WORD("lawfully");
218 | WORD("sorters");
219 | WORD("selected");
220 | WORD("bespoken");
221 | WORD("nonentity");
222 | WORD("embezzlers");
223 | WORD("alcohol");
224 | WORD("affects");
225 | WORD("penknives");
226 | WORD("musketeers");
227 | WORD("cockades");
228 | WORD("ascendents");
229 | WORD("disinterment");
230 | WORD("overhearing");
231 | WORD("propping");
232 | WORD("ibices");
233 | WORD("tornados");
234 | WORD("digitalis");
235 | WORD("aquas");
236 | WORD("risers");
237 | WORD("lighters");
238 | WORD("mastodons");
239 | WORD("webcams");
240 | WORD("mavericks");
241 | WORD("mahogany");
242 | WORD("backstopping");
243 | WORD("wriggled");
244 | WORD("dolly");
245 | WORD("talkative");
246 | WORD("lunatics");
247 | WORD("hyphens");
248 | WORD("biologists");
249 | WORD("burrito");
250 | WORD("hypersensitivity");
251 | WORD("vex");
252 | WORD("ciphering");
253 | WORD("ferrets");
254 | WORD("comestible");
255 | WORD("divisive");
256 | WORD("cranny");
257 | WORD("outdated");
258 | WORD("gawky");
259 | WORD("interested");
260 | WORD("puffiness");
261 | WORD("despatch");
262 | WORD("trumping");
263 | WORD("divides");
264 | WORD("soughing");
265 | WORD("freelances");
266 | WORD("rimed");
267 | WORD("sobriquets");
268 | WORD("unbeliever");
269 | WORD("overcrowded");
270 | WORD("garter");
271 | WORD("laxer");
272 | WORD("disembodied");
273 | WORD("babushka");
274 | WORD("advertised");
275 | WORD("uninformed");
276 | WORD("minibikes");
277 | WORD("intensity");
278 | WORD("fashionistas");
279 | WORD("aphasia");
280 | WORD("smart");
281 | WORD("borrowing");
282 | WORD("director");
283 | WORD("covenant");
284 | WORD("minorities");
285 | WORD("castigating");
286 | WORD("chip");
287 | WORD("rarer");
288 | WORD("tantalizingly");
289 | WORD("flask");
290 | WORD("approving");
291 | WORD("pinups");
292 | WORD("whiteners");
293 | WORD("wittily");
294 | WORD("reaching");
295 | WORD("tams");
296 | WORD("digesting");
297 | WORD("altruistically");
298 | WORD("recite");
299 | WORD("ostracizes");
300 | WORD("improvidence");
301 | WORD("centipedes");
302 | WORD("escorting");
303 | WORD("advertises");
304 | WORD("rift");
305 | WORD("janitor");
306 | WORD("stent");
307 | WORD("presenter");
308 | WORD("diurnal");
309 | WORD("nectar");
310 | WORD("jockstraps");
311 | WORD("hissed");
312 | WORD("overrunning");
313 | WORD("supersizes");
314 | WORD("peanut");
315 | WORD("divider");
316 | WORD("sulkiest");
317 | WORD("effervescing");
318 | WORD("mucks");
319 | WORD("prejudges");
320 | WORD("stopgaps");
321 | WORD("panaceas");
322 | WORD("read");
323 | WORD("monstrances");
324 | WORD("lisle");
325 | WORD("devastate");
326 | WORD("loudmouth");
327 | WORD("martyred");
328 | WORD("traitor");
329 | WORD("pasting");
330 | WORD("fiats");
331 | WORD("underlie");
332 | WORD("egotism");
333 | WORD("collaboration");
334 | WORD("contemplates");
335 | WORD("fleece");
336 | WORD("lethal");
337 | WORD("coalitions");
338 | WORD("catnip");
339 | WORD("garbs");
340 | WORD("pushover");
341 | WORD("discrete");
342 | WORD("heroics");
343 | WORD("ovoids");
344 | WORD("set");
345 | WORD("bosun");
346 | WORD("mannishness");
347 | WORD("cutlery");
348 | WORD("sampled");
349 | WORD("manpower");
350 | WORD("sluice");
351 | WORD("sweepers");
352 | WORD("traced");
353 | WORD("pill");
354 | WORD("whores");
355 | WORD("ceramic");
356 | WORD("irrecoverable");
357 | WORD("repositories");
358 | WORD("hired");
359 | WORD("triennial");
360 | WORD("fattened");
361 | WORD("anagram");
362 | WORD("welshed");
363 | WORD("lacquers");
364 | WORD("requesting");
365 | WORD("crackerjacks");
366 | WORD("reeking");
367 | WORD("hangars");
368 | WORD("orgasm");
369 | WORD("echelon");
370 | WORD("pearliest");
371 | WORD("stomachaches");
372 | WORD("helixes");
373 | WORD("tapeworm");
374 | WORD("pirates");
375 | WORD("mismanagement");
376 | WORD("citrons");
377 | WORD("shunts");
378 | WORD("thoughtfully");
379 | WORD("moisturized");
380 | WORD("defaces");
381 | WORD("closeting");
382 | WORD("shameful");
383 | WORD("goblins");
384 | WORD("choices");
385 | WORD("bittersweets");
386 | WORD("construed");
387 | WORD("shimmers");
388 | WORD("roster");
389 | WORD("such");
390 | WORD("now");
391 | WORD("glad");
392 | WORD("flimsier");
393 | WORD("neither");
394 | WORD("quota");
395 | WORD("octane");
396 | WORD("magnetizing");
397 | WORD("deviating");
398 | WORD("slamming");
399 | WORD("milepost");
400 | WORD("waterfront");
401 | WORD("tolerant");
402 | WORD("capped");
403 | WORD("upbraiding");
404 | WORD("renounced");
405 | WORD("vetoed");
406 | WORD("creaky");
407 | WORD("blankest");
408 | WORD("regimen");
409 | WORD("demagogs");
410 | WORD("meddler");
411 | WORD("uniforms");
412 | WORD("trundles");
413 | WORD("slues");
414 | WORD("goldenest");
415 | WORD("scarceness");
416 | WORD("screwed");
417 | WORD("beggar");
418 | WORD("monopolies");
419 | WORD("occurred");
420 | WORD("directors");
421 | WORD("restatements");
422 | WORD("queasy");
423 | WORD("sparkling");
424 | WORD("grandstand");
425 | WORD("quarrelled");
426 | WORD("evils");
427 | WORD("cobbled");
428 | WORD("catapults");
429 | WORD("sheets");
430 | WORD("expenditures");
431 | WORD("allergenic");
432 | WORD("diamond");
433 | WORD("inequalities");
434 | WORD("topped");
435 | WORD("militarization");
436 | WORD("twos");
437 | WORD("remortgages");
438 | WORD("indigestion");
439 | WORD("encores");
440 | WORD("bulletproofed");
441 | WORD("adeptly");
442 | WORD("catechised");
443 | WORD("staggers");
444 | WORD("initiators");
445 | WORD("hedonism");
446 | WORD("shines");
447 | WORD("scourged");
448 | WORD("infuriates");
449 | WORD("abridged");
450 | WORD("vassals");
451 | WORD("manicuring");
452 | WORD("sang");
453 | WORD("dendrite");
454 | WORD("permeability");
455 | WORD("participle");
456 | WORD("boles");
457 | WORD("multiplies");
458 | WORD("dinnered");
459 | WORD("vigilantly");
460 | WORD("scatterbrains");
461 | WORD("typecast");
462 | WORD("backstretch");
463 | WORD("expostulates");
464 | WORD("thoroughly");
465 | WORD("clappers");
466 | WORD("convocation");
467 | WORD("lariat");
468 | WORD("angled");
469 | WORD("transmigration");
470 | WORD("generalissimos");
471 | WORD("licked");
472 | WORD("inert");
473 | WORD("accruals");
474 | WORD("brig");
475 | WORD("beakers");
476 | WORD("landslides");
477 | WORD("forsythias");
478 | WORD("recognized");
479 | WORD("hutzpa");
480 | WORD("cavort");
481 | WORD("betterment");
482 | WORD("maxillae");
483 | WORD("thunderhead");
484 | WORD("streetlights");
485 | WORD("panda");
486 | WORD("horrifying");
487 | WORD("predict");
488 | WORD("cultivates");
489 | WORD("telegrapher");
490 | WORD("catchy");
491 | WORD("alibiing");
492 | WORD("displacements");
493 | WORD("comfiest");
494 | WORD("seducer");
495 | WORD("convicted");
496 | WORD("topmost");
497 | WORD("razed");
498 | WORD("germinated");
499 | WORD("disparages");
500 | WORD("dabbles");
501 | WORD("spelled");
502 | WORD("assumes");
503 | WORD("scratches");
504 | WORD("prevaricates");
505 | WORD("mornings");
506 | WORD("mousses");
507 | WORD("formatting");
508 | WORD("theologies");
509 | WORD("ibises");
510 | WORD("sonnies");
511 | WORD("inspectors");
512 | WORD("teacups");
513 | WORD("ward");
514 | WORD("martyring");
515 | WORD("stepmom");
516 | WORD("slily");
517 | WORD("garages");
518 | WORD("secret");
519 | WORD("virginity");
520 | WORD("fuck");
521 | WORD("bricked");
522 | WORD("hued");
523 | WORD("calculation");
524 | WORD("firebombed");
525 | WORD("grossest");
526 | WORD("apace");
527 | WORD("suavest");
528 | WORD("towelings");
529 | WORD("cranking");
530 | WORD("hordes");
531 | WORD("oversimplifying");
532 | WORD("attach");
533 | WORD("defoliants");
534 | WORD("varmint");
535 | WORD("malnutrition");
536 | WORD("hematology");
537 | WORD("oscillators");
538 | WORD("hunts");
539 | WORD("bugs");
540 | WORD("filch");
541 | WORD("appellants");
542 | WORD("brink");
543 | WORD("structural");
544 | WORD("dale");
545 | WORD("tilled");
546 | WORD("bucketed");
547 | WORD("feds");
548 | WORD("imbecilities");
549 | WORD("jerk");
550 | WORD("contumacious");
551 | WORD("refulgent");
552 | WORD("transfixing");
553 | WORD("mappings");
554 | WORD("occupancy");
555 | WORD("gluts");
556 | WORD("nays");
557 | WORD("hitching");
558 | WORD("sleeveless");
559 | WORD("growth");
560 | WORD("libellous");
561 | WORD("blackballing");
562 | WORD("windjammers");
563 | WORD("snakebites");
564 | WORD("verbs");
565 | WORD("properties");
566 | WORD("hemorrhaging");
567 | WORD("arbitrariness");
568 | WORD("crossover");
569 | WORD("nigh");
570 | WORD("delves");
571 | WORD("diner");
572 | WORD("inflicting");
573 | WORD("yipping");
574 | WORD("walruses");
575 | WORD("zither");
576 | WORD("sauntered");
577 | WORD("artworks");
578 | WORD("troys");
579 | WORD("girt");
580 | WORD("boasters");
581 | WORD("infirmity");
582 | WORD("nineteen");
583 | WORD("malfunctions");
584 | WORD("laundry");
585 | WORD("belabors");
586 | WORD("poetesses");
587 | WORD("flagged");
588 | WORD("settled");
589 | WORD("burped");
590 | WORD("hyperventilates");
591 | WORD("seasoning");
592 | WORD("esquire");
593 | WORD("fledged");
594 | WORD("participation");
595 | WORD("brilliant");
596 | WORD("weddings");
597 | WORD("nectarines");
598 | WORD("shaded");
599 | WORD("themselves");
600 | WORD("operate");
601 | WORD("fuelling");
602 | WORD("delicious");
603 | WORD("laterals");
604 | WORD("buckboards");
605 | WORD("kidded");
606 | WORD("kneads");
607 | WORD("rightmost");
608 | WORD("jurisdiction");
609 | WORD("acorns");
610 | WORD("quenches");
611 | WORD("insults");
612 | WORD("unstructured");
613 | WORD("breakfasting");
614 | WORD("guesstimates");
615 | WORD("filleting");
616 | WORD("lasagnas");
617 | WORD("perjure");
618 | WORD("shinnied");
619 | WORD("strangulation");
620 | WORD("snoot");
621 | WORD("attunes");
622 | WORD("colonialism");
623 | WORD("frugal");
624 | WORD("punts");
625 | WORD("stampedes");
626 | WORD("downgrade");
627 | WORD("subsisting");
628 | WORD("scuppers");
629 | WORD("cigarillos");
630 | WORD("pantsuit");
631 | WORD("binging");
632 | WORD("grouts");
633 | WORD("recenter");
634 | WORD("finals");
635 | WORD("fairgrounds");
636 | WORD("snorers");
637 | WORD("valences");
638 | WORD("shinbone");
639 | WORD("gearwheels");
640 | WORD("interdicted");
641 | WORD("legislatures");
642 | WORD("girdled");
643 | WORD("dungeons");
644 | WORD("disciplining");
645 | WORD("vaporization");
646 | WORD("toothless");
647 | WORD("shimmying");
648 | WORD("brazens");
649 | WORD("extirpate");
650 | WORD("nullity");
651 | WORD("dressier");
652 | WORD("rectangular");
653 | WORD("knotty");
654 | WORD("category");
655 | WORD("coevals");
656 | WORD("meek");
657 | WORD("regulates");
658 | WORD("retardants");
659 | WORD("quadrants");
660 | WORD("juleps");
661 | WORD("scurrilously");
662 | WORD("thespians");
663 | WORD("obediently");
664 | WORD("repayments");
665 | WORD("delegates");
666 | WORD("rerunning");
667 | WORD("retaliates");
668 | WORD("flaw");
669 | WORD("pities");
670 | WORD("bibliographers");
671 | WORD("lawns");
672 | WORD("murkiness");
673 | WORD("worrywart");
674 | WORD("ornaments");
675 | WORD("sponsors");
676 | WORD("spigot");
677 | WORD("presences");
678 | WORD("vulgarizing");
679 | WORD("whiskered");
680 | WORD("kilned");
681 | WORD("breakups");
682 | WORD("befitted");
683 | WORD("pockmarked");
684 | WORD("moonlighting");
685 | WORD("electrical");
686 | WORD("speedometer");
687 | WORD("island");
688 | WORD("eminence");
689 | WORD("yeast");
690 | WORD("particle");
691 | WORD("theological");
692 | WORD("ceding");
693 | WORD("colonnade");
694 | WORD("slurs");
695 | WORD("sunfish");
696 | WORD("eh");
697 | WORD("railroads");
698 | WORD("strayed");
699 | WORD("sax");
700 | WORD("idiot");
701 | WORD("tireless");
702 | WORD("bushier");
703 | WORD("refugee");
704 | WORD("winks");
705 | WORD("dimming");
706 | WORD("bunks");
707 | WORD("agenda");
708 | WORD("paisleys");
709 | WORD("pontifical");
710 | WORD("cloaks");
711 | WORD("stomped");
712 | WORD("slated");
713 | WORD("priestesses");
714 | WORD("semester");
715 | WORD("sedately");
716 | WORD("barbarian");
717 | WORD("riddle");
718 | WORD("whitener");
719 | WORD("pushier");
720 | WORD("engorge");
721 | WORD("congregational");
722 | WORD("scoutmasters");
723 | WORD("mirth");
724 | WORD("piccalilli");
725 | WORD("sodden");
726 | WORD("brokenhearted");
727 | WORD("palmier");
728 | WORD("fingerprinting");
729 | WORD("quislings");
730 | WORD("caparisoning");
731 | WORD("recognizably");
732 | WORD("goslings");
733 | WORD("cuddled");
734 | WORD("agitates");
735 | WORD("jimmied");
736 | WORD("slumbering");
737 | WORD("bounders");
738 | WORD("barnstorms");
739 | WORD("piloting");
740 | WORD("enclave");
741 | WORD("commercials");
742 | WORD("sharia");
743 | WORD("autistic");
744 | WORD("truer");
745 | WORD("tidily");
746 | WORD("consciously");
747 | WORD("barred");
748 | WORD("temblor");
749 | WORD("camphor");
750 | WORD("abacuses");
751 | WORD("runny");
752 | WORD("papyrus");
753 | WORD("associates");
754 | WORD("perspectives");
755 | WORD("sheltering");
756 | WORD("countersigned");
757 | WORD("managerial");
758 | WORD("quarry");
759 | WORD("thoracic");
760 | WORD("massacred");
761 | WORD("impenetrable");
762 | WORD("encodes");
763 | WORD("halyards");
764 | WORD("lancing");
765 | WORD("gal");
766 | WORD("meatloaves");
767 | WORD("maverick");
768 | WORD("proselytizes");
769 | WORD("preliminary");
770 | WORD("repulsing");
771 | WORD("packaged");
772 | WORD("patriarchal");
773 | WORD("pretty");
774 | WORD("ineffable");
775 | WORD("embalm");
776 | WORD("precipice");
777 | WORD("shadow");
778 | WORD("love");
779 | WORD("lionizing");
780 | WORD("bedroll");
781 | WORD("crookedness");
782 | WORD("pinked");
783 | WORD("enlistments");
784 | WORD("trapezoidal");
785 | WORD("placebo");
786 | WORD("directly");
787 | WORD("salsas");
788 | WORD("proffers");
789 | WORD("brandishing");
790 | WORD("podiatrists");
791 | WORD("daintiest");
792 | WORD("recompense");
793 | WORD("porcupines");
794 | WORD("also");
795 | WORD("trellising");
796 | WORD("plotting");
797 | WORD("inaccuracy");
798 | WORD("bitterer");
799 | WORD("soap");
800 | WORD("hypothalamus");
801 | WORD("lexicography");
802 | WORD("ninnies");
803 | WORD("afternoon");
804 | WORD("eats");
805 | WORD("watercolors");
806 | WORD("membership");
807 | WORD("protectorates");
808 | WORD("impassivity");
809 | WORD("wondrous");
810 | WORD("eyeballed");
811 | WORD("gigabyte");
812 | WORD("gentlest");
813 | WORD("confirming");
814 | WORD("antagonism");
815 | WORD("broadcast");
816 | WORD("sage");
817 | WORD("joker");
818 | WORD("oppressors");
819 | WORD("interluding");
820 | WORD("fuze");
821 | WORD("launched");
822 | WORD("releasing");
823 | WORD("reestablishing");
824 | WORD("bigwigs");
825 | WORD("rheumatism");
826 | WORD("laborious");
827 | WORD("tactically");
828 | WORD("striping");
829 | WORD("eateries");
830 | WORD("fame");
831 | WORD("chauvinistic");
832 | WORD("sitter");
833 | WORD("camisole");
834 | WORD("conserving");
835 | WORD("champagne");
836 | WORD("abbeys");
837 | WORD("epitomizing");
838 | WORD("goulash");
839 | WORD("goatee");
840 | WORD("pained");
841 | WORD("hiker");
842 | WORD("pygmy");
843 | WORD("ware");
844 | WORD("polyhedron");
845 | WORD("carouse");
846 | WORD("gazetting");
847 | WORD("coconuts");
848 | WORD("dill");
849 | WORD("insensitively");
850 | WORD("vaulters");
851 | WORD("osiers");
852 | WORD("unmanning");
853 | WORD("worksheets");
854 | WORD("lavender");
855 | WORD("manhood");
856 | WORD("paraprofessionals");
857 | WORD("bowdlerized");
858 | WORD("dumfounding");
859 | WORD("waistline");
860 | WORD("rises");
861 | WORD("stickiness");
862 | WORD("envelope");
863 | WORD("unspoken");
864 | WORD("wowed");
865 | WORD("analyticalally");
866 | WORD("ulna");
867 | WORD("gist");
868 | WORD("jihads");
869 | WORD("stoker");
870 | WORD("nay");
871 | WORD("fogy");
872 | WORD("poled");
873 | WORD("evicted");
874 | WORD("goriest");
875 | WORD("buried");
876 | WORD("glades");
877 | WORD("comprehensive");
878 | WORD("reenters");
879 | WORD("caricaturing");
880 | WORD("dieting");
881 | WORD("diagnostic");
882 | WORD("replenishing");
883 | WORD("daydreamer");
884 | WORD("mutter");
885 | WORD("massage");
886 | WORD("alleluia");
887 | WORD("misrepresents");
888 | WORD("redefined");
889 | WORD("deprogramed");
890 | WORD("raconteurs");
891 | WORD("cunningest");
892 | WORD("placate");
893 | WORD("peach");
894 | WORD("cosmetic");
895 | WORD("grower");
896 | WORD("rheumatic");
897 | WORD("aficionados");
898 | WORD("hookworm");
899 | WORD("ovulates");
900 | WORD("freelancers");
901 | WORD("midsummer");
902 | WORD("admired");
903 | WORD("gargoyles");
904 | WORD("tobaccos");
905 | WORD("plannings");
906 | WORD("press");
907 | WORD("muggers");
908 | WORD("fortress");
909 | WORD("reappoints");
910 | WORD("scruples");
911 | WORD("gnomes");
912 | WORD("apocalyptic");
913 | WORD("moderates");
914 | WORD("halving");
915 | WORD("weathering");
916 | WORD("blowouts");
917 | WORD("distinction");
918 | WORD("dumping");
919 | WORD("poultry");
920 | WORD("therapists");
921 | WORD("maximum");
922 | WORD("defers");
923 | WORD("lankier");
924 | WORD("approached");
925 | WORD("winked");
926 | WORD("construing");
927 | WORD("paradoxical");
928 | WORD("orthogonality");
929 | WORD("localizing");
930 | WORD("fleecing");
931 | WORD("delimit");
932 | WORD("footlights");
933 | WORD("blessedly");
934 | WORD("interrogated");
935 | WORD("fantasy");
936 | WORD("foxtrots");
937 | WORD("hit");
938 | WORD("malaise");
939 | WORD("invisibility");
940 | WORD("correcter");
941 | WORD("prolongs");
942 | WORD("supporters");
943 | WORD("hubbub");
944 | WORD("audible");
945 | WORD("scrutiny");
946 | WORD("smells");
947 | WORD("detaching");
948 | WORD("castings");
949 | WORD("nominatives");
950 | WORD("solemnly");
951 | WORD("babysitters");
952 | WORD("morbid");
953 | WORD("invariable");
954 | WORD("kinswoman");
955 | WORD("moles");
956 | WORD("cowardice");
957 | WORD("prawning");
958 | WORD("falloff");
959 | WORD("bulimia");
960 | WORD("aptitude");
961 | WORD("diploma");
962 | WORD("scholars");
963 | WORD("broadens");
964 | WORD("persecutes");
965 | WORD("retrospection");
966 | WORD("palms");
967 | WORD("daughter");
968 | WORD("misgiving");
969 | WORD("variations");
970 | WORD("underclassmen");
971 | WORD("coronae");
972 | WORD("rivets");
973 | WORD("bipartisan");
974 | WORD("blow");
975 | WORD("repairing");
976 | WORD("dourer");
977 | WORD("scants");
978 | WORD("moms");
979 | WORD("sloshed");
980 | WORD("equalizer");
981 | WORD("caddied");
982 | WORD("flattery");
983 | WORD("meritorious");
984 | WORD("propagates");
985 | WORD("unsympathetic");
986 | WORD("casuals");
987 | WORD("gobbing");
988 | WORD("aground");
989 | WORD("excavator");
990 | WORD("possessiveness");
991 | WORD("cracked");
992 | WORD("canneries");
993 | WORD("gambles");
994 | WORD("nightclubbed");
995 | WORD("catchwords");
996 | WORD("quashing");
997 | WORD("summons");
998 | WORD("fizzing");
999 | WORD("noisiness");
1000 | WORD("shapeliness");
1001 | WORD("ligatures");
1002 | WORD("aphrodisiac");
1003 | WORD("quicker");
1004 |
--------------------------------------------------------------------------------
/main.cpp:
--------------------------------------------------------------------------------
1 | /**
2 | * Switch/Case strings using constexpr Fnv1-a.
3 | * @maintainer xavier dot roche at algolia.com
4 | */
5 |
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #include
14 | #include
15 |
16 | #include "switch_fnv1a.h"
17 |
18 | #define L_(L) #L
19 | #define L(L) L_(L)
20 |
21 | // Execute benchmarks ?
22 | #define ENABLE_BENCHS
23 |
24 | // Small (100 cases) benchs ?
25 | // #define SMALL_BENCHS
26 |
27 | // Do not print output for benchs ?
28 | // #define NOPRINT_BENCHS
29 |
30 | // Sample case
31 | const char* dispatch(const fnv1a128::Type match)
32 | {
33 | switch (match) {
34 | case "poney"_fnv1a128:
35 | return "I want one, too!";
36 | case "elephant"_fnv1a128:
37 | return "Not in my apartment please!";
38 | case "dog"_fnv1a128:
39 | return "Good puppy!";
40 | case "kitten"_fnv1a128:
41 | return "Aawwwwwwww!";
42 | default:
43 | return "Don't know this animal!";
44 | }
45 | }
46 |
47 | // Long switch of 1000 'case
48 | constexpr const char* dispatch_1000(const fnv1a128::Type match)
49 | {
50 | switch (match) {
51 | #define WORD(W) \
52 | case W##_fnv1a128: \
53 | return L(__LINE__)
54 | #ifndef SMALL_BENCHS
55 | #include "include/words-extract.h"
56 | #else
57 | #include "include/words-extract-small.h"
58 | #endif
59 | #undef WORD
60 |
61 | default:
62 | return "unknown!";
63 | }
64 | }
65 |
66 | // Hash-switch version
67 | auto compute_hash(const char *str, size_t size)
68 | {
69 | return fnv1a128::hash(str, size);
70 | }
71 |
72 | // Hash-switch version
73 | const char* match_case(const std::string& str)
74 | {
75 | return dispatch_1000(compute_hash(str.c_str(), str.size()));
76 | }
77 |
78 | // Pure-if version
79 | const char* match_if(const std::string& str)
80 | {
81 | if (str.size() == 0)
82 | return "";
83 | #define WORD(W) else if (str == W) return L(__LINE__)
84 | #ifndef SMALL_BENCHS
85 | #include "include/words-extract.h"
86 | #else
87 | #include "include/words-extract-small.h"
88 | #endif
89 | #undef WORD
90 | return "unknown!";
91 | }
92 |
93 | static void bench(void)
94 | {
95 | {
96 | // Small benchmarks
97 | constexpr size_t max = 100000;
98 | #ifdef NOPRINT_BENCHS
99 | volatile size_t calc = 1;
100 | #endif
101 |
102 | std::vector strings;
103 | #define WORD(W) strings.push_back(W)
104 | #include "include/words-extract-match.h"
105 | #undef WORD
106 |
107 | unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
108 | std::shuffle(strings.begin(), strings.end(), std::default_random_engine(seed));
109 |
110 | uint64_t elapsed_if;
111 | uint64_t elapsed_case;
112 |
113 | // Naive if
114 | {
115 | auto start = std::chrono::steady_clock::now();
116 | for (size_t i = 0; i < max; i++) {
117 | for (const auto& string : strings) {
118 | const char* const match = match_if(string.c_str());
119 | #ifndef NOPRINT_BENCHS
120 | std::cout << match << "\n";
121 | #else
122 | calc *= match[0];
123 | #endif
124 | }
125 | }
126 | auto end = std::chrono::steady_clock::now();
127 | auto elapsed = std::chrono::duration_cast(end - start).count();
128 | elapsed_if = elapsed;
129 | std::cerr << elapsed << "ms\n";
130 | }
131 |
132 | // Hashed switch
133 | {
134 | auto start = std::chrono::steady_clock::now();
135 | for (size_t i = 0; i < max; i++) {
136 | for (const auto& string : strings) {
137 | const char* const match = match_case(string.c_str());
138 | #ifndef NOPRINT_BENCHS
139 | std::cout << match << "\n";
140 | #else
141 | calc *= match[0];
142 | #endif
143 | }
144 | }
145 | auto end = std::chrono::steady_clock::now();
146 | auto elapsed = std::chrono::duration_cast(end - start).count();
147 | elapsed_case = elapsed;
148 | std::cerr << elapsed << "ms\n";
149 | }
150 |
151 | const auto factor = (elapsed_if * 100) / elapsed_case;
152 | std::cerr << "Factor: " << (factor / 100) << "." << (factor % 100) << "\n";
153 | }
154 | }
155 |
156 | int main(int argc, char** argv)
157 | {
158 | // Execute benchmarks ?
159 | #ifdef ENABLE_BENCHS
160 | if (argc == 1)
161 | bench();
162 | #endif
163 |
164 | // Demo
165 | for (int i = 1; i < argc; i++) {
166 | const auto hash = fnv1a128::hash(argv[i]);
167 |
168 | std::cout << dispatch(hash) << "\n";
169 | std::cout << "Hash value:" << std::hex << ((uint64_t)(hash >> 64)) << ((uint64_t)(hash)) << "\n";
170 | }
171 |
172 | return 0;
173 | }
174 |
--------------------------------------------------------------------------------
/switch_fnv1a.h:
--------------------------------------------------------------------------------
1 | /**
2 | * Switch/Case strings using constexpr Fnv1-a.
3 | * @comment References:
4 | * @comment References:
5 | * @maintainer xavier dot roche at algolia.com
6 | */
7 |
8 | #pragma once
9 |
10 | #include
11 | #include
12 | #include
13 | #include
14 |
15 | // Traits for FNV1a
16 | template
17 | struct fnv1a_traits
18 | {
19 | static constexpr bool Supported = false;
20 | };
21 |
22 | // Traits for 32-bit FNV1a
23 | template<>
24 | struct fnv1a_traits<32>
25 | {
26 | static constexpr bool Supported = true;
27 | using Type = uint32_t;
28 |
29 | static constexpr Type Prime = 0x1000193;
30 | static constexpr Type Offset = 0x811c9dc5;
31 | };
32 |
33 | // Traits for 64-bit FNV1a
34 | template<>
35 | struct fnv1a_traits<64>
36 | {
37 | static constexpr bool Supported = true;
38 | using Type = uint64_t;
39 |
40 | static constexpr Type Prime = 0x100000001b3;
41 | static constexpr Type Offset = 0xcbf29ce484222325;
42 | };
43 |
44 | static constexpr __uint128_t Pack128(uint64_t high, uint64_t low)
45 | {
46 | return ((__uint128_t)high << 64) + (__uint128_t)low;
47 | }
48 |
49 | // Traits for 128-bit FNV1a
50 | template<>
51 | struct fnv1a_traits<128>
52 | {
53 | static constexpr bool Supported = true;
54 | using Type = __uint128_t;
55 |
56 | static constexpr Type Prime = Pack128(0x1000000, 0x000000000000013b);
57 | static constexpr Type Offset = Pack128(0x6c62272e07bb0142, 0x62b821756295c58d);
58 | };
59 |
60 | // Generic FNV1a implementation
61 | template
62 | struct fnv1a
63 | {
64 | static_assert(fnv1a_traits::Supported);
65 | using Type = typename fnv1a_traits::Type;
66 |
67 | /**
68 | * Compute the Fowler–Noll–Vo hash
69 | * @comment stop An optional stop character
70 | * @param s The string
71 | * @param l The string size
72 | * @return The fnv-1a hash
73 | */
74 | template
75 | static constexpr Type hash_container(T s,
76 | const std::size_t l,
77 | L stopLen = nullptr,
78 | Type hash = fnv1a_traits::Offset)
79 | {
80 | // See
81 | std::size_t j = 0;
82 | for (; j < l; j++) {
83 | const uint8_t byte = s[j];
84 | if constexpr (stop != 0) {
85 | if (byte == stop) {
86 | if constexpr (!std::is_same::value) {
87 | *stopLen = j + 1;
88 | }
89 | break;
90 | }
91 | }
92 | hash ^= byte;
93 | hash *= fnv1a_traits::Prime;
94 | }
95 |
96 | return hash;
97 | }
98 |
99 | /**
100 | * Compute the Fowler–Noll–Vo hash
101 | * @comment stop An optional stop character
102 | * @param s The string
103 | * @param l The string size
104 | * @return The fnv-1a hash
105 | */
106 | template
107 | static constexpr Type hash(const C* s,
108 | const std::size_t l,
109 | L stopLen = nullptr,
110 | Type hash = fnv1a_traits::Offset)
111 | {
112 | // Accept [ unsigned | signed ] char
113 | static_assert(std::is_integral::value);
114 | static_assert(sizeof(C) == 1);
115 | return hash_container(s, l, stopLen, hash);
116 | }
117 |
118 | /**
119 | * Compute the Fowler–Noll–Vo hash
120 | * @param s The string
121 | * @return The fnv-1a hash
122 | */
123 | template
124 | static constexpr Type hash(const char (&s)[l])
125 | {
126 | return hash(&s[0], l - 1);
127 | }
128 |
129 | // Do not infer length for char arrays
130 | template
131 | static constexpr Type hash(char (&s)[l])
132 | {
133 | return hash(&s[0]);
134 | }
135 |
136 | /**
137 | * Compute the Fowler–Noll–Vo hash
138 | * @param s The string
139 | * @return The fnv-1a hash
140 | */
141 | static constexpr Type hash(const char* s) { return hash(s, __builtin_strlen(s)); }
142 |
143 | /**
144 | * Compute the Fowler–Noll–Vo hash
145 | * @param str The string
146 | * @return The fnv-1a hash
147 | */
148 | static constexpr Type hash(const std::string& str) { return hash(str.c_str(), str.size()); }
149 |
150 | /**
151 | * Compute the Fowler–Noll–Vo hash
152 | * @param str The string view
153 | * @return The fnv-1a hash
154 | */
155 | template
156 | static constexpr Type hash(const std::basic_string_view& str)
157 | {
158 | // Accept [ unsigned | signed ] char
159 | static_assert(std::is_integral::value);
160 | static_assert(sizeof(C) == 1);
161 | return hash(str.data(), str.size());
162 | }
163 | };
164 |
165 | using fnv1a32 = fnv1a<32>;
166 | using fnv1a64 = fnv1a<64>;
167 | using fnv1a128 = fnv1a<128>;
168 |
169 | constexpr fnv1a32::Type operator"" _fnv1a32(const char* s, const std::size_t l)
170 | {
171 | return fnv1a32::hash(s, l);
172 | }
173 | constexpr fnv1a64::Type operator"" _fnv1a64(const char* s, const std::size_t l)
174 | {
175 | return fnv1a64::hash(s, l);
176 | }
177 | constexpr fnv1a128::Type operator"" _fnv1a128(const char* s, const std::size_t l)
178 | {
179 | return fnv1a128::hash(s, l);
180 | }
181 |
182 | // Static unit tests:
183 | static_assert("hello"_fnv1a32 == 0x4f9f2cab);
184 | static_assert("hello"_fnv1a64 == 0xa430d84680aabd0b);
185 | static_assert("hello"_fnv1a128 == Pack128(0xe3e1efd54283d94f, 0x7081314b599d31b3));
186 |
187 | using strhash = fnv1a128;
188 | constexpr strhash::Type operator"" _strhash(const char* s, const std::size_t l)
189 | {
190 | return strhash::hash(s, l);
191 | }
192 |
193 | // Lowercase operator[] wrapper for any operator[]-aware types
194 | namespace strhash_lower {
195 | template
196 | class _lowercase_container
197 | {
198 | public:
199 | constexpr _lowercase_container(const T& container)
200 | : _container(container)
201 | {}
202 |
203 | constexpr auto operator[](std::size_t index) const
204 | {
205 | const auto c = _container[index];
206 | static_assert(std::is_integral::value);
207 | static_assert(sizeof(c) == 1);
208 | return c >= 'A' && c <= 'Z' ? (c + 'a' - 'A') : c;
209 | }
210 |
211 | private:
212 | const T& _container;
213 | };
214 |
215 | /**
216 | * Hash a generic container, using a lowercase modifier, providing 'size' 8-byte characters through operator[]
217 | */
218 | template
219 | constexpr strhash::Type hash(const T& container, std::size_t size)
220 | {
221 | return strhash::hash_container(strhash_lower::_lowercase_container(container), size);
222 | }
223 |
224 | /**
225 | * Hash a std::string, using a lowercase modifier
226 | */
227 | static constexpr strhash::Type hash(const std::string& str)
228 | {
229 | return hash(str.c_str(), str.size());
230 | }
231 |
232 | /**
233 | * Hash a std::string, using a lowercase modifier
234 | */
235 | template
236 | static constexpr strhash::Type hash(const std::basic_string_view& str)
237 | {
238 | // Accept [ unsigned | signed ] char
239 | static_assert(std::is_integral::value);
240 | static_assert(sizeof(C) == 1);
241 | return hash(str.data(), str.size());
242 | }
243 | } // namespace strhash_lower
244 |
245 | // Case-insensitive version
246 | constexpr strhash::Type operator"" _strhash_lower(const char* s, const std::size_t l)
247 | {
248 | return strhash_lower::hash(s, l);
249 | }
250 |
--------------------------------------------------------------------------------