#pragma once
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <filesystem>
#include <string>
#include <vector>
#include "base.hpp"
#include "Optional.hpp"
#undef min
NAMESPACE_SOUP
{
class string
{
private:
template <typename Str, typename Int, uint8_t Base>
[[nodiscard]] static Str fromIntImplAscii(Int _i)
{
if (_i == 0)
{
return Str(1, '0');
}
using UInt = std::make_unsigned_t<Int>;
UInt i;
bool neg = false;
if constexpr (std::is_signed_v<Int>)
{
neg = (_i < 0);
if (neg)
{
i = (_i * -1);
}
else
{
i = _i;
}
}
else
{
i = _i;
}
Str res{};
for (; i != 0; i /= Base)
{
const auto digit = (i % Base);
res.insert(0, 1, static_cast<typename Str::value_type>('0' + digit));
}
if (neg)
{
res.insert(0, 1, '-');
}
return res;
}
public:
template <typename Str = std::string, typename Int>
[[nodiscard]] static Str decimal(Int i) {
return fromIntImplAscii<Str, Int, 10>(i);
}
template <typename Float>
[[nodiscard]] static std::string fdecimal(Float f) SOUP_EXCAL
{
if (std::fmod(f, 1) == 0)
{
std::string str = std::to_string((long long)f);
str.append(".0");
return str;
}
else
{
std::string str = std::to_string(f);
while (str.back() == '0')
{
str.pop_back();
}
if (str.back() == '.')
{
str.push_back('0');
}
return str;
}
}
template <typename Str = std::string, typename Int>
[[nodiscard]] static Str binary(Int i)
{
return fromIntImplAscii<Str, Int, 2>(i);
}
template <typename Int>
[[nodiscard]] static std::string hex(Int i)
{
return fromIntWithMap<std::string, Int, 16>(i, charset_hex);
}
template <typename Int>
[[nodiscard]] static std::string hexLower(Int i)
{
return fromIntWithMap<std::string, Int, 16>(i, charset_hex_lower);
}
static constexpr const char* charset_hex = "0123456789ABCDEF";
static constexpr const char* charset_hex_lower = "0123456789abcdef";
template <typename Str, typename Int, uint8_t Base>
[[nodiscard]] static Str fromIntWithMap(Int i, const typename Str::value_type* map)
{
if (i == 0)
{
return Str(1, map[0]);
}
const bool neg = (i < 0);
if (neg)
{
i = i * -1;
}
Str res{};
for (; i != 0; i /= Base)
{
const auto digit = (i % Base);
res.insert(0, 1, map[digit]);
}
if (neg)
{
res.insert(0, 1, '-');
}
return res;
}
template <typename T>
[[nodiscard]] static constexpr bool isUppercaseLetter(const T c) noexcept
{
return c >= 'A' && c <= 'Z';
}
template <typename T>
[[nodiscard]] static constexpr bool isLowercaseLetter(const T c) noexcept
{
return c >= 'a' && c <= 'z';
}
template <typename T>
[[nodiscard]] static constexpr bool isLetter(const T c) noexcept
{
return isUppercaseLetter(c) || isLowercaseLetter(c);
}
template <typename T>
[[nodiscard]] static constexpr bool isSpace(const T c) noexcept
{
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
}
template <typename T>
[[nodiscard]] static constexpr bool isNumberChar(const T c) noexcept
{
return c >= '0' && c <= '9';
}
template <typename T>
[[nodiscard]] static constexpr bool isAlphaNum(const T c) noexcept
{
return isLetter(c) || isNumberChar(c);
}
template <typename T>
[[nodiscard]] static constexpr bool isHexDigitChar(const T c) noexcept
{
return isNumberChar(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
template <typename T>
[[nodiscard]] static constexpr bool isWordChar(const T c) noexcept
{
return isLetter(c) || isNumberChar(c) || c == '_';
}
template <typename T>
[[nodiscard]] static bool isNumeric(const T& str)
{
for (const auto& c : str)
{
if (!isNumberChar(c))
{
return false;
}
}
return true;
}
template <typename T>
[[nodiscard]] static bool containsWord(const T& haystack, const T& needle)
{
if (!needle.empty())
{
for (size_t i, off = 0; i = haystack.find(needle, off), i != T::npos; off = i + needle.size())
{
if ((i == 0 || !isLetter(haystack.at(i - 1)))
&& (i + needle.size() == haystack.size() || !isLetter(haystack.at(i + needle.size())))
)
{
return true;
}
}
}
return false;
}
template <typename T = std::string>
[[nodiscard]] static bool equalsIgnoreCase(const T& a, const T& b)
{
if (a.size() != b.size())
{
return false;
}
for (size_t i = 0; i != a.size(); ++i)
{
if (std::tolower(a.at(i)) != std::tolower(b.at(i)))
{
return false;
}
}
return true;
}
template <typename T = std::string>
[[nodiscard]] static size_t levenshtein(const T& a, const T& b)
{
size_t n = a.size() + 1;
size_t m = b.size() + 1;
auto d = new size_t[n * m];
for (size_t i = 0; i != n; ++i)
{
d[0 * n + i] = i;
}
for (size_t j = 0; j != m; ++j)
{
d[j * n + 0] = j;
}
for (size_t i = 1, im = 0; i < m; ++i, ++im)
{
for (size_t j = 1, jn = 0; j < n; ++j, ++jn)
{
size_t cost = (a[jn] == b[im]) ? 0 : 1;
if (a[jn] == b[im])
{
d[(i * n) + j] = d[((i - 1) * n) + (j - 1)];
}
else
{
d[(i * n) + j] = std::min(
std::min(d[(i - 1) * n + j], d[i * n + (j - 1)]) + 1,
d[(i - 1) * n + (j - 1)] + cost
);
}
}
}
const auto r = d[n * m - 1];
delete[] d;
return r;
}
[[nodiscard]] static std::string bin2hex(const std::string& str, bool spaces = false) SOUP_EXCAL
{
return bin2hexImpl(str.data(), str.size(), spaces, charset_hex);
}
[[nodiscard]] static std::string bin2hex(const char* data, size_t size, bool spaces = false) SOUP_EXCAL
{
return bin2hexImpl(data, size, spaces, charset_hex);
}
[[nodiscard]] static std::string bin2hexLower(const std::string& str, bool spaces = false) SOUP_EXCAL
{
return bin2hexImpl(str.data(), str.size(), spaces, charset_hex_lower);
}
[[nodiscard]] static std::string bin2hexLower(const char* data, size_t size, bool spaces = false) SOUP_EXCAL
{
return bin2hexImpl(data, size, spaces, charset_hex_lower);
}
[[nodiscard]] static std::string bin2hexImpl(const char* data, size_t size, bool spaces, const char* map) SOUP_EXCAL
{
std::string res{};
res.reserve(size * (2 + spaces));
for (; size; ++data, --size)
{
res.push_back(map[(unsigned char)(*data) >> 4]);
res.push_back(map[(*data) & 0b1111]);
if (spaces)
{
res.push_back(' ');
}
}
if (spaces && !res.empty())
{
res.pop_back();
}
return res;
}
static void bin2hexAt(char* out, const char* data, size_t size, const char* map) noexcept
{
for (; size; ++data, --size)
{
*out++ = map[(unsigned char)(*data) >> 4];
*out++ = map[(*data) & 0b1111];
}
}
[[nodiscard]] static constexpr size_t bin2hexWithSpacesSize(size_t size) noexcept
{
return size != 0 ? (size * 3) - 1 : 0;
}
static void bin2hexWithSpaces(char* out, const char* data, size_t size, const char* map) noexcept
{
for (; size; ++data)
{
*out++ = map[(unsigned char)(*data) >> 4];
*out++ = map[(*data) & 0b1111];
if (--size)
{
*out++ = ' ';
}
}
}
[[nodiscard]] static std::string hex2bin(const std::string& hex) SOUP_EXCAL { return hex2bin(hex.data(), hex.size()); }
[[nodiscard]] static std::string hex2bin(const char* data, size_t size) SOUP_EXCAL;
enum ToIntFlags : uint8_t
{
TI_FULL = 1 << 0, };
template <typename IntT, uint8_t Base = 10, typename CharT>
[[nodiscard]] static Optional<IntT> toIntEx(const CharT* it, uint8_t flags = 0, const CharT** end = nullptr) noexcept
{
bool neg = false;
if (*it == '\0')
{
_fail:
if (end)
{
*end = it;
}
return std::nullopt;
}
switch (*it)
{
case '-':
if constexpr (std::is_unsigned_v<IntT>)
{
goto _fail;
}
neg = true;
[[fallthrough]];
case '+':
if (*++it == '\0')
{
goto _fail;
}
}
IntT val = 0;
{
bool had_number_char = false;
IntT max = 0;
IntT prev_max = 0;
while (true)
{
if constexpr (std::is_unsigned_v<IntT>)
{
max *= Base;
max += (Base - 1);
SOUP_IF_UNLIKELY (!(max > prev_max))
{
break;
}
prev_max = max;
}
const CharT c = *it;
if (isNumberChar(c))
{
val *= Base;
val += (c - '0');
}
else if (Base > 10 && c >= 'a' && c <= ('a' + Base - 11))
{
val *= Base;
val += 0xA + (c - 'a');
}
else if (Base > 10 && c >= 'A' && c <= ('A' + Base - 11))
{
val *= Base;
val += 0xA + (c - 'A');
}
else
{
break;
}
++it;
had_number_char = true;
if constexpr (std::is_signed_v<IntT>)
{
max *= Base;
max += (Base - 1);
SOUP_IF_UNLIKELY (max < prev_max)
{
break;
}
prev_max = max;
}
}
if (!had_number_char)
{
goto _fail;
}
}
if (flags & TI_FULL)
{
if (*it != '\0')
{
goto _fail;
}
}
if constexpr (std::is_signed_v<IntT>)
{
if (neg)
{
val *= -1;
}
}
if (end)
{
*end = it;
}
return Optional<IntT>(val);
}
template <typename IntT>
[[nodiscard]] static Optional<IntT> toIntOpt(const std::string& str, uint8_t flags = 0) noexcept
{
return toIntEx<IntT>(str.c_str(), flags);
}
template <typename IntT>
[[nodiscard]] static Optional<IntT> toIntOpt(const std::wstring& str, uint8_t flags = 0) noexcept
{
return toIntEx<IntT>(str.c_str(), flags);
}
template <typename IntT>
[[nodiscard]] static IntT toInt(const char* str, IntT fallback, uint8_t flags = 0) noexcept
{
return toIntEx<IntT>(str, flags).value_or(fallback);
}
template <typename IntT>
[[nodiscard]] static IntT toInt(const std::string& str, IntT fallback, uint8_t flags = 0) noexcept
{
return toIntEx<IntT>(str.c_str(), flags).value_or(fallback);
}
template <typename IntT>
[[nodiscard]] static IntT toInt(const wchar_t* str, IntT fallback, uint8_t flags = 0) noexcept
{
return toIntEx<IntT>(str, flags).value_or(fallback);
}
template <typename IntT>
[[nodiscard]] static IntT toInt(const std::wstring& str, IntT fallback, uint8_t flags = 0) noexcept
{
return toIntEx<IntT>(str.c_str(), flags).value_or(fallback);
}
template <typename IntT>
[[nodiscard]] static Optional<IntT> hexToIntOpt(const char* str, uint8_t flags = 0) noexcept
{
return toIntEx<IntT, 0x10>(str, flags);
}
template <typename IntT>
[[nodiscard]] static Optional<IntT> hexToIntOpt(const std::string& str, uint8_t flags = 0) noexcept
{
return toIntEx<IntT, 0x10>(str.c_str(), flags);
}
template <typename IntT>
[[nodiscard]] static Optional<IntT> hexToIntOpt(const std::wstring& str, uint8_t flags = 0) noexcept
{
return toIntEx<IntT, 0x10>(str.c_str(), flags);
}
template <typename IntT>
[[nodiscard]] static IntT hexToInt(const char* str, IntT fallback, uint8_t flags = 0) noexcept
{
return toIntEx<IntT, 0x10>(str, flags).value_or(fallback);
}
template <typename IntT>
[[nodiscard]] static IntT hexToInt(const std::string& str, IntT fallback, uint8_t flags = 0) noexcept
{
return toIntEx<IntT, 0x10>(str.c_str(), flags).value_or(fallback);
}
template <typename IntT>
[[nodiscard]] static IntT hexToInt(const wchar_t* str, IntT fallback, uint8_t flags = 0) noexcept
{
return toIntEx<IntT, 0x10>(str, flags).value_or(fallback);
}
template <typename IntT>
[[nodiscard]] static IntT hexToInt(const std::wstring& str, IntT fallback, uint8_t flags = 0) noexcept
{
return toIntEx<IntT, 0x10>(str.c_str(), flags).value_or(fallback);
}
template <class S>
static void replaceAll(S& str, const S& from, const S& to) SOUP_EXCAL
{
size_t start_pos = 0;
while ((start_pos = str.find(from, start_pos)) != S::npos)
{
str.replace(start_pos, from.length(), to);
start_pos += to.length();
}
}
static void replaceAll(std::string& str, const std::string& from, const std::string& to) SOUP_EXCAL
{
return replaceAll<std::string>(str, from, to);
}
static void replaceAll(std::wstring& str, const std::wstring& from, const std::wstring& to) SOUP_EXCAL
{
return replaceAll<std::wstring>(str, from, to);
}
template <class S>
[[nodiscard]] static S replaceAll(const S& str, const S& from, const S& to) SOUP_EXCAL
{
S cpy(str);
replaceAll(cpy, from, to);
return cpy;
}
[[nodiscard]] static std::string replaceAll(const std::string& str, const std::string& from, const std::string& to) SOUP_EXCAL
{
return replaceAll<std::string>(str, from, to);
}
[[nodiscard]] static std::wstring replaceAll(const std::wstring& str, const std::wstring& from, const std::wstring& to) SOUP_EXCAL
{
return replaceAll<std::wstring>(str, from, to);
}
[[nodiscard]] static std::string escape(const std::string& str);
template <typename S>
static constexpr size_t len(S str) noexcept
{
if constexpr (std::is_same_v<S, char> || std::is_same_v<S, wchar_t>)
{
return 1;
}
else if constexpr (std::is_pointer_v<S>)
{
size_t len = 0;
while (str[len] != 0)
{
++len;
}
return len;
}
else
{
return str.size();
}
}
template <typename S, typename D>
[[nodiscard]] static std::vector<S> explode(const S& str, D delim) SOUP_EXCAL
{
std::vector<S> res{};
if (!str.empty())
{
res.reserve(5);
size_t prev = 0;
size_t del_pos;
while ((del_pos = str.find(delim, prev)) != std::string::npos)
{
res.emplace_back(str.substr(prev, del_pos - prev));
prev = del_pos + len(delim);
}
auto remain_len = (str.length() - prev);
res.emplace_back(str.substr(prev, remain_len));
}
return res;
}
[[nodiscard]] static std::string join(const std::vector<std::string>& arr, const char glue);
[[nodiscard]] static std::string join(const std::vector<std::string>& arr, const std::string& glue);
template <typename S, typename C>
static S lpad(S&& str, size_t desired_len, C pad_char)
{
lpad(str, desired_len, std::move(pad_char));
return str;
}
template <typename S, typename C>
static void lpad(S& str, size_t desired_len, C pad_char)
{
if (auto diff = desired_len - str.length(); diff > 0)
{
str.insert(0, diff, pad_char);
}
}
template <typename S, typename C>
static S rpad(S&& str, size_t desired_len, C pad_char)
{
rpad(str, desired_len, std::move(pad_char));
return str;
}
template <typename S, typename C>
static void rpad(S& str, size_t desired_len, C pad_char)
{
if (auto diff = desired_len - str.length(); diff > 0)
{
str.append(diff, pad_char);
}
}
template <typename T>
static void erase(T& str, const T& target)
{
for (size_t i = 0; i = str.find(target, i), i != T::npos; )
{
str.erase(i, target.size());
}
}
template <typename T>
static void limit(T& str, const T& target)
{
if (size_t i = str.find(target); i != T::npos)
{
str.erase(i);
}
}
template <typename T>
static void limitLast(T& str, const T& target)
{
if (size_t i = str.find_last_of(target); i != T::npos)
{
str.erase(0, i);
}
}
static void listAppend(std::string& str, std::string&& add);
template <typename T>
static void trim(T& str)
{
ltrim(str);
rtrim(str);
}
template <typename T>
static void ltrim(T& str)
{
while (!str.empty())
{
auto i = str.begin();
const char c = *i;
if (!isSpace(c))
{
return;
}
str.erase(i);
}
}
template <typename T>
static void rtrim(T& str)
{
while (!str.empty())
{
auto i = (str.end() - 1);
const char c = *i;
if (!isSpace(c))
{
return;
}
str.erase(i);
}
}
[[nodiscard]] static std::string _xor(const std::string& l, const std::string& r); [[nodiscard]] static std::string xorSameLength(const std::string& l, const std::string& r);
#if SOUP_CPP20
[[nodiscard]] static std::string fixType(std::u8string str) noexcept
{
std::string fixed = std::move(*reinterpret_cast<std::string*>(&str));
return fixed;
}
[[nodiscard]] static std::u8string toUtf8Type(std::string str) noexcept
{
std::u8string u8 = std::move(*reinterpret_cast<std::u8string*>(&str));
return u8;
}
#else
[[nodiscard]] static std::string fixType(std::string str) noexcept
{
return str;
}
#endif
template <typename T>
static void truncateWithEllipsis(T& str, size_t max_len)
{
SOUP_DEBUG_ASSERT(max_len >= 3);
if (str.size() > max_len)
{
str.resize(max_len);
auto* data = str.data();
data[max_len - 3] = '.';
data[max_len - 2] = '.';
data[max_len - 1] = '.';
}
}
template <typename T>
[[nodiscard]] static T truncateWithEllipsis(T&& str, size_t max_len)
{
truncateWithEllipsis(str, max_len);
return str;
}
template <typename Char>
[[nodiscard]] static Char lower_char(Char c) noexcept
{
if (c >= 'A' && c <= 'Z')
{
return c - 'A' + 'a';
}
return c;
}
template <typename Str>
static void lower(Str& str) noexcept
{
std::transform(str.begin(), str.end(), str.begin(), &lower_char<typename Str::value_type>);
}
template <typename Str>
[[nodiscard]] static Str lower(Str&& str) noexcept
{
lower(str);
return str;
}
template <typename Char>
[[nodiscard]] static Char upper_char(Char c) noexcept
{
if (c >= 'a' && c <= 'z')
{
return c - 'a' + 'A';
}
return c;
}
template <typename Str>
static void upper(Str& str) noexcept
{
std::transform(str.begin(), str.end(), str.begin(), &upper_char<typename Str::value_type>);
}
template <typename Str>
[[nodiscard]] static Str upper(Str&& str) noexcept
{
upper(str);
return str;
}
template <typename Str>
static void title(Str& str)
{
bool first = true;
for (auto& c : str)
{
if (first)
{
first = false;
c = upper_char(c);
}
else
{
c = lower_char(c);
}
if (isSpace(c))
{
first = true;
}
}
}
template <typename Str>
[[nodiscard]] static Str title(Str&& str)
{
title(str);
return str;
}
[[nodiscard]] static constexpr char rot13(char c) noexcept
{
if (isUppercaseLetter(c))
{
char val = (c - 'A');
val += 13;
if (val >= 26)
{
val -= 26;
}
return (val + 'A');
}
if (isLowercaseLetter(c))
{
char val = (c - 'a');
val += 13;
if (val >= 26)
{
val -= 26;
}
return (val + 'a');
}
return c;
}
[[nodiscard]] static std::string fromFile(const char* file);
[[nodiscard]] static std::string fromFile(const std::string& file);
[[nodiscard]] static std::string fromFile(const std::filesystem::path& file);
[[deprecated("Replace 'fromFilePath' with 'fromFile'")]] inline static std::string fromFilePath(const std::filesystem::path& file) { return fromFile(file); }
static void toFile(const char* file, const std::string& contents);
static void toFile(const std::string& file, const std::string& contents);
static void toFile(const std::filesystem::path& file, const std::string& contents);
};
}