High-precision calculations for one- and two-valence atomic systems
String.hpp
1#pragma once
2#include "fmt/color.hpp"
3#include <algorithm>
4#include <cctype>
5#include <cctype> //char from string
6#include <cstdarg>
7#include <cstdint>
8#include <functional>
9#include <sstream>
10#include <string>
11#include <string_view>
12#include <vector>
13
14namespace qip {
15
16//==============================================================================
17
18/*!
19 @brief Returns a formatted string using printf-style format specifiers.
20
21 @details Maximum string length is 256 characters.
22 Use the size overload for longer strings.
23*/
24inline std::string fstring(const std::string format, ...) {
25 constexpr std::size_t size = 256;
26 std::string fmt_str;
27 fmt_str.resize(size + 1); // allow for null
28
29 // C-style variadic param-list, to call c function vsnprintf (varidatic
30 // snprintf)
31 va_list args;
32 // note: format in va_start mist not be a reference type.. so copy the string?
33 va_start(args, format);
34 vsnprintf(&fmt_str[0], fmt_str.size(), format.c_str(), args);
35 va_end(args);
36
37 // resize string, remove part after the buffer (not needed)
38 fmt_str.erase(std::find(fmt_str.begin(), fmt_str.end(), '\0'), fmt_str.end());
39
40 return fmt_str;
41}
42
43//! Overload of fstring with explicit buffer size (maximum string length).
44inline std::string fstring(const std::size_t size, const std::string format,
45 ...) {
46 // nb: cannot just call other overload, since using c-style variadic function
47 // (I think?) - so a copy-paste re-implementation
48 std::string fmt_str;
49 fmt_str.resize(size + 1); // allow for null
50
51 // C-style variadic param-list, to call c function vsnprintf (varidatic
52 // snprintf)
53 va_list args;
54 // note: format in va_start mist not be a reference type.. so copy the string?
55 va_start(args, format);
56 vsnprintf(&fmt_str[0], fmt_str.size(), format.c_str(), args);
57 va_end(args);
58
59 // resize string, remove part after the buffer (not needed)
60 fmt_str.erase(std::find(fmt_str.begin(), fmt_str.end(), '\0'), fmt_str.end());
61
62 return fmt_str;
63}
64
65//==============================================================================
66
67/*!
68 @brief Compares s1 against pattern s2, where s2 may contain one wildcard '*'
69 that matches any substring.
70*/
71inline bool wildcard_compare(std::string_view s1, std::string_view s2) {
72 // look for wildcard:
73 const auto wc = std::find(s2.cbegin(), s2.cend(), '*');
74 if (wc == s2.cend())
75 return s1 == s2;
76
77 const auto pos_wc = std::size_t(std::distance(s2.cbegin(), wc));
78
79 const auto s1_front = s1.substr(0, pos_wc);
80 const auto s2_front = s2.substr(0, pos_wc);
81
82 // number of characters following the '*'
83 const auto len_back = std::size_t(std::distance(wc + 1, s2.cend()));
84
85 const auto pos_1_back = s1.length() > len_back ? s1.length() - len_back : 0;
86 const auto s1_back = s1.substr(pos_1_back, std::string::npos);
87 const auto s2_back = s2.substr(pos_wc + 1, std::string::npos);
88
89 return s1_front == s2_front && s1_back == s2_back;
90}
91
92//==============================================================================
93
94//! Conversion of a single character to lowercase.
95inline char tolower(char ch) {
96 // https://en.cppreference.com/w/cpp/string/byte/tolower
97 return static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
98}
99
100//! Returns a lowercase copy of the string.
101inline std::string tolower(std::string t_string) {
102 for (auto &c : t_string) {
103 c = qip::tolower(c);
104 }
105 return t_string;
106}
107
108//==============================================================================
109
110//! Returns true if the_string contains sub_string.
111inline bool contains(std::string_view the_string, std::string_view sub_string) {
112 return the_string.find(sub_string) != std::string::npos;
113}
114
115//! Returns true if the_string contains sub_string (case insensitive).
116inline bool ci_contains(const std::string &the_string,
117 const std::string &sub_string) {
118 return tolower(the_string).find(tolower(sub_string)) != std::string::npos;
119}
120
121//! Returns true if the_string contains any of the sub_strings.
122inline bool contains(const std::string &the_string,
123 const std::vector<std::string> &sub_strings) {
124 for (const auto &substr : sub_strings) {
125 if (contains(the_string, substr))
126 return true;
127 }
128 return false;
129}
130
131//! Returns true if the_string contains any of the sub_strings (case insensitive).
132inline bool ci_contains(const std::string &the_string,
133 const std::vector<std::string> &sub_strings) {
134 for (const auto &substr : sub_strings) {
135 if (ci_contains(the_string, substr))
136 return true;
137 }
138 return false;
139}
140
141//==============================================================================
142
143//! Case-insensitive string comparison; equivalent to tolower(s1) == tolower(s2).
144inline bool ci_compare(std::string_view s1, std::string_view s2) {
145 return std::equal(
146 s1.cbegin(), s1.cend(), s2.cbegin(), s2.cend(),
147 [](char c1, char c2) { return qip::tolower(c1) == qip::tolower(c2); });
148}
149
150/*!
151 @brief Case-insensitive version of @ref wildcard_compare.
152
153 @details Compares s1 against pattern s2, where s2 may contain one wildcard
154 '*' that matches any substring.
155*/
156inline bool ci_wc_compare(std::string_view s1, std::string_view s2) {
157 // look for wildcard:
158 const auto wc = std::find(s2.cbegin(), s2.cend(), '*');
159 if (wc == s2.cend())
160 return ci_compare(s1, s2);
161
162 const auto pos_wc = std::size_t(std::distance(s2.cbegin(), wc));
163
164 const auto s1_front = s1.substr(0, pos_wc);
165 const auto s2_front = s2.substr(0, pos_wc);
166
167 // number of characters following the '*'
168 const auto len_back = std::size_t(std::distance(wc + 1, s2.cend()));
169
170 const auto pos_1_back = s1.length() > len_back ? s1.length() - len_back : 0;
171 const auto s1_back = s1.substr(pos_1_back, std::string::npos);
172 const auto s2_back = s2.substr(pos_wc + 1, std::string::npos);
173
174 return ci_compare(s1_front, s2_front) && ci_compare(s1_back, s2_back);
175}
176
177//==============================================================================
178
179//! Returns the Levenshtein edit distance between strings a and b.
180inline auto Levenstein(std::string_view a, std::string_view b) {
181 // https://en.wikipedia.org/wiki/Levenshtein_distance
182 // https://stackoverflow.com/a/70237726/8446770
183 std::vector<size_t> d_t((a.size() + 1) * (b.size() + 1), size_t(-1));
184 auto d = [&](size_t ia, size_t ib) -> size_t & {
185 return d_t[ia * (b.size() + 1) + ib];
186 };
187 std::function<size_t(size_t, size_t)> LevensteinInt =
188 [&](size_t ia, size_t ib) -> size_t {
189 if (d(ia, ib) != size_t(-1))
190 return d(ia, ib);
191 size_t dist = 0;
192 if (ib >= b.size())
193 dist = a.size() - ia;
194 else if (ia >= a.size())
195 dist = b.size() - ib;
196 else if (a[ia] == b[ib])
197 dist = LevensteinInt(ia + 1, ib + 1);
198 else
199 dist = 1 + std::min(std::min(LevensteinInt(ia, ib + 1),
200 LevensteinInt(ia + 1, ib)),
201 LevensteinInt(ia + 1, ib + 1));
202 d(ia, ib) = dist;
203 return dist;
204 };
205 return LevensteinInt(0, 0);
206}
207
208//! Case-insensitive version of @ref Levenstein.
209inline auto ci_Levenstein(std::string_view a, std::string_view b) {
210 std::vector<size_t> d_t((a.size() + 1) * (b.size() + 1), size_t(-1));
211 auto d = [&](size_t ia, size_t ib) -> size_t & {
212 return d_t[ia * (b.size() + 1) + ib];
213 };
214 std::function<size_t(size_t, size_t)> LevensteinInt =
215 [&](size_t ia, size_t ib) -> size_t {
216 if (d(ia, ib) != size_t(-1))
217 return d(ia, ib);
218 size_t dist = 0;
219 if (ib >= b.size())
220 dist = a.size() - ia;
221 else if (ia >= a.size())
222 dist = b.size() - ib;
223 else if (qip::tolower(a[ia]) == qip::tolower(b[ib]))
224 dist = LevensteinInt(ia + 1, ib + 1);
225 else
226 dist = 1 + std::min(std::min(LevensteinInt(ia, ib + 1),
227 LevensteinInt(ia + 1, ib)),
228 LevensteinInt(ia + 1, ib + 1));
229 d(ia, ib) = dist;
230 return dist;
231 };
232 return LevensteinInt(0, 0);
233}
234
235//! Returns an iterator to the closest match to test_string in list,
236//! using @ref Levenstein distance.
237inline auto closest_match(std::string_view test_string,
238 const std::vector<std::string> &list) {
239 auto compare = [&test_string](const auto &s1, const auto &s2) {
240 return qip::Levenstein(s1, test_string) < qip::Levenstein(s2, test_string);
241 };
242 return std::min_element(list.cbegin(), list.cend(), compare);
243}
244
245//! Returns the closest match (case insensitive) to test_string in list,
246//! using @ref ci_Levenstein distance.
247inline std::string ci_closest_match(const std::string_view test_string,
248 const std::vector<std::string> &list) {
249 auto compare = [&test_string](const auto &s1, const auto &s2) {
250 return qip::ci_Levenstein(s1, test_string) <
251 qip::ci_Levenstein(s2, test_string);
252 };
253 using namespace std::string_literals;
254 return list.empty() ? ""s :
255 *std::min_element(list.cbegin(), list.cend(), compare);
256}
257
258//==============================================================================
259
260/*!
261 @brief Returns true if the string represents an integer.
262
263 @details
264 Accepts an optional leading '+' or '-'. e.g., "16" and "-12" return true;
265 "12x" and "12.5" return false.
266*/
267inline bool string_is_integer(std::string_view s) {
268 return !s.empty() &&
269 // checks if all non-leading characters are integer digits
270 std::find_if(s.cbegin() + 1, s.cend(),
271 [](auto c) { return !std::isdigit(c); }) == s.end() &&
272 // checks if leading character is one of: digit, '+', or '-'
273 (std::isdigit(s[0]) || ((s[0] == '-' || s[0] == '+') && s.size() > 1));
274}
275
276//==============================================================================
277
278//! Splits a string by delimiter into a vector of substrings.
279inline std::vector<std::string> split(const std::string &s, char delim = ' ') {
280 std::vector<std::string> out;
281 std::stringstream ss(s);
282 std::string tmp;
283 while (getline(ss, tmp, delim)) {
284 out.push_back(tmp);
285 }
286 return out;
287}
288
289//! Concatenates a vector of strings into one, with an optional delimiter.
290inline std::string concat(const std::vector<std::string> &v,
291 const std::string &delim = "") {
292 std::string out;
293 for (std::size_t i = 0; i < v.size(); ++i) {
294 out += v[i];
295 if (i != v.size() - 1)
296 out += delim;
297 }
298 return out;
299}
300
301//==============================================================================
302
303/*!
304 @brief Word-wraps input at column at, optionally prefixing each line.
305
306 @details Does not split words unless unavoidable.
307*/
308inline std::string wrap(const std::string &input, std::size_t at = 80,
309 const std::string &prefix = "") {
310 std::string output;
311 const auto length = at - prefix.size();
312 std::size_t ipos = 0;
313 std::size_t fpos = length;
314 while (ipos < input.length()) {
315 if (!output.empty())
316 output += '\n';
317
318 auto temp_pos_nl = input.find('\n', ipos);
319 if (temp_pos_nl > ipos && temp_pos_nl < fpos &&
320 temp_pos_nl != std::string::npos) {
321 output += prefix + input.substr(ipos, temp_pos_nl - ipos);
322 ipos = temp_pos_nl + 1;
323 fpos = ipos + length;
324 continue;
325 }
326
327 if (fpos >= input.length()) {
328 output += prefix + input.substr(ipos, fpos - ipos);
329 break;
330 }
331
332 auto temp_pos = input.rfind(' ', fpos);
333 if (temp_pos <= ipos || temp_pos == std::string::npos) {
334 output += prefix + input.substr(ipos, fpos - ipos);
335 ipos = fpos;
336 fpos = ipos + length;
337 } else {
338 output += prefix + input.substr(ipos, temp_pos - ipos);
339 ipos = temp_pos + 1;
340 fpos = ipos + length;
341 }
342 }
343 return output;
344}
345
346//==============================================================================
347
348//! Converts an integer to a Roman numeral string. Assumes |a| <= 3999.
349inline std::string int_to_roman(int a) {
350 if (a < 0)
351 return "-" + int_to_roman(-a);
352 if (a > 3999)
353 return std::to_string(a);
354 static const std::string M[] = {"", "M", "MM", "MMM"};
355 static const std::string C[] = {"", "C", "CC", "CCC", "CD",
356 "D", "DC", "DCC", "DCCC", "CM"};
357 static const std::string X[] = {"", "X", "XX", "XXX", "XL",
358 "L", "LX", "LXX", "LXXX", "XC"};
359 static const std::string I[] = {"", "I", "II", "III", "IV",
360 "V", "VI", "VII", "VIII", "IX"};
361 return M[a / 1000] + C[(a % 1000) / 100] + X[(a % 100) / 10] + I[(a % 10)];
362}
363
364//==============================================================================
365
366//! 64-bit FNV-1a hash of a string.
367inline std::uint64_t hash(std::string_view string) {
368 std::uint64_t h = 14695981039346656037ull;
369 for (const auto c : string) {
370 h = (h ^ static_cast<unsigned char>(c)) * 1099511628211ull;
371 }
372 return h;
373}
374
375/*!
376 @brief Short base-36 (0-9, a-z) form of @ref hash; intended for filenames.
377
378 @details With the default 6 characters, there are 36^6 ~ 2e9 possible tags:
379 enough that accidental collisions between different settings will not happen
380 in practice.
381
382 @param string Text to be hashed.
383 @param n_chars Length of the returned tag (1 to 12).
384*/
385inline std::string hash_string(std::string_view string,
386 std::size_t n_chars = 6) {
387 static const std::string digits = "0123456789abcdefghijklmnopqrstuvwxyz";
388 auto h = hash(string);
389 std::string out(n_chars, '0');
390 for (std::size_t i = n_chars; i > 0; --i) {
391 out.at(i - 1) = digits.at(h % 36);
392 h /= 36;
393 }
394 return out;
395}
396
397} // namespace qip
General-purpose utility library.
Definition Array.hpp:23
std::uint64_t hash(std::string_view string)
64-bit FNV-1a hash of a string.
Definition String.hpp:367
auto closest_match(std::string_view test_string, const std::vector< std::string > &list)
Returns an iterator to the closest match to test_string in list, using Levenstein distance.
Definition String.hpp:237
std::string concat(const std::vector< std::string > &v, const std::string &delim="")
Concatenates a vector of strings into one, with an optional delimiter.
Definition String.hpp:290
auto Levenstein(std::string_view a, std::string_view b)
Returns the Levenshtein edit distance between strings a and b.
Definition String.hpp:180
std::string ci_closest_match(const std::string_view test_string, const std::vector< std::string > &list)
Returns the closest match (case insensitive) to test_string in list, using ci_Levenstein distance.
Definition String.hpp:247
bool string_is_integer(std::string_view s)
Returns true if the string represents an integer.
Definition String.hpp:267
std::string hash_string(std::string_view string, std::size_t n_chars=6)
Short base-36 (0-9, a-z) form of hash; intended for filenames.
Definition String.hpp:385
std::vector< std::string > split(const std::string &s, char delim=' ')
Splits a string by delimiter into a vector of substrings.
Definition String.hpp:279
bool ci_contains(const std::string &the_string, const std::string &sub_string)
Returns true if the_string contains sub_string (case insensitive).
Definition String.hpp:116
auto ci_Levenstein(std::string_view a, std::string_view b)
Case-insensitive version of Levenstein.
Definition String.hpp:209
bool ci_wc_compare(std::string_view s1, std::string_view s2)
Case-insensitive version of wildcard_compare.
Definition String.hpp:156
std::string int_to_roman(int a)
Converts an integer to a Roman numeral string. Assumes |a| <= 3999.
Definition String.hpp:349
std::string fstring(const std::string format,...)
Returns a formatted string using printf-style format specifiers.
Definition String.hpp:24
bool ci_compare(std::string_view s1, std::string_view s2)
Case-insensitive string comparison; equivalent to tolower(s1) == tolower(s2).
Definition String.hpp:144
bool wildcard_compare(std::string_view s1, std::string_view s2)
Compares s1 against pattern s2, where s2 may contain one wildcard '*' that matches any substring.
Definition String.hpp:71
bool contains(std::string_view the_string, std::string_view sub_string)
Returns true if the_string contains sub_string.
Definition String.hpp:111
auto compare(const std::vector< T > &first, const std::vector< T > &second)
Compares two arithmetic vectors element-wise; returns {max_delta, iterator}.
Definition Vector.hpp:41
std::string wrap(const std::string &input, std::size_t at=80, const std::string &prefix="")
Word-wraps input at column at, optionally prefixing each line.
Definition String.hpp:308
char tolower(char ch)
Conversion of a single character to lowercase.
Definition String.hpp:95