Skip to content

Commit

Permalink
feature: Implemented generation guarantees for String::octal() (#311)
Browse files Browse the repository at this point in the history
* feature: Implemented string guarantee for `String::octal()`

User can now pass `GuaranteeMap` to `String::octal()` to constraint
count of specific chars to certain range

* tests: Added tests for string guarantee in `String::octal()`

* fix: Fixed apple-clang++ build error
  • Loading branch information
braw-lee authored Nov 22, 2023
1 parent b1fc9b8 commit c08609a
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 47 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,5 @@ build-linux-gxx
build-linux-clang
#clangd index files
.cache/
#vim swap files
*.sw?
12 changes: 9 additions & 3 deletions include/faker-cxx/String.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ std::string generateAtleastString(const GuaranteeMap& guarantee);

class String
{
private:
static std::string generateStringWithGuarantee(GuaranteeMap& guarantee, std::set<char>& targetCharacters,
unsigned int length);

public:
/**
* @brief Generates an Universally Unique Identifier with version 4.
Expand Down Expand Up @@ -224,27 +228,29 @@ class String
/**
* @brief Generates a binary string.
*
* @param guarantee A map specifying char count constraints if any
* @param length The number of digits to generate. Defaults to `1`.
*
* @returns Binary string.
*
* @code
* String::binary(8) // "0b01110101"
* String::binary({}, 8) // "0b01110101"
* @endcode
*/
static std::string binary(GuaranteeMap&& guarantee = {}, unsigned length = 1);

/**
* @brief Generates an octal string.
*
* @param guarantee A map specifying char count constraints if any
* @param length The number of digits to generate. Defaults to `1`.
*
* @returns Octal string.
*
* @code
* String::octal(8) // "0o52561721"
* String::octal({}, 8) // "0o52561721"
* @endcode
*/
static std::string octal(unsigned length = 1);
static std::string octal(GuaranteeMap&& guarantee = {}, unsigned length = 1);
};
}
94 changes: 51 additions & 43 deletions src/modules/string/String.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,48 @@ std::string generateAtleastString(const GuaranteeMap& guarantee)
return result;
}

std::string String::generateStringWithGuarantee(GuaranteeMap& guarantee, std::set<char>& targetCharacters,
unsigned int length)
{
std::string output{};
output += generateAtleastString(guarantee);
// string with least required chars cannot be greater than the total length
assert(output.size() <= length);
// we will generate chars for remaining length only
length -= static_cast<unsigned>(output.size());
for (unsigned i = 0; i < length; ++i)
{
char generatedChar;
// generate chars till we find a usable char
while (true)
{
// pick random char from targetCharacters
generatedChar = Helper::setElement(targetCharacters);

auto it = guarantee.find(generatedChar);
// if no constraint on generated char, break out of loop
if (it == guarantee.end())
break;
auto remainingUses = it->second.atmostCount - it->second.atleastCount;
if (remainingUses > 0)
{
// decrement no of possible uses as we will use it right now
--it->second.atmostCount;
break;
}
// remove this char from targetCharacters as it is no longer valid and regenerate char
else
{
targetCharacters.erase(it->first);
}
}
output += generatedChar;
}
// shuffle the generated string as the atleast string generated earlier was not generated randomly
output = Helper::shuffleString(output);
return output;
}

std::string String::sample(unsigned int length)
{
std::string sample;
Expand Down Expand Up @@ -166,61 +208,27 @@ std::string String::hexadecimal(unsigned int length, HexCasing casing, HexPrefix

std::string String::binary(GuaranteeMap&& guarantee, unsigned int length)
{
// numbers used by binary representation
std::set<char> targetCharacters{'0', '1'};
// throw if guarantee is invalid
if (!isValidGuarantee(guarantee, targetCharacters, length))
{
throw std::invalid_argument{"Invalid guarantee."};
}

std::string binary{};
binary += generateAtleastString(guarantee);
// string with least required chars cannot be greater than the total length
assert(binary.size() <= length);
// we will generate chars for remaining length only
length -= static_cast<unsigned>(binary.size());
for (unsigned i = 0; i < length; ++i)
{
char generatedChar;
// generate chars till we find a usable char
while (true)
{
// pick random char from targetCharacters
generatedChar = Helper::setElement(targetCharacters);

auto it = guarantee.find(generatedChar);
// if no constraint on generated char, break out of loop
if (it == guarantee.end())
break;
auto remainingUses = it->second.atmostCount - it->second.atleastCount;
if (remainingUses > 0)
{
// decrement no of possible uses as we will use it right now
--it->second.atmostCount;
break;
}
// remove this char from targetCharacters as it is no longer valid and regenerate char
else
{
targetCharacters.erase(it->first);
}
}
binary += generatedChar;
}
// shuffle the generated string as the atleast string generated earlier was not generated randomly
binary = Helper::shuffleString(binary);
return "0b" + binary;
return "0b" + generateStringWithGuarantee(guarantee, targetCharacters, length);
}

std::string String::octal(unsigned int length)
std::string String::octal(GuaranteeMap&& guarantee, unsigned int length)
{
std::string octal{"0o"};

for (unsigned i = 0; i < length; i++)
// numbers used by octal representation
std::set<char> targetCharacters{'0', '1', '2', '3', '4', '5', '6', '7'};
// throw if guarantee is invalid
if (!isValidGuarantee(guarantee, targetCharacters, length))
{
octal += static_cast<char>(Number::integer(7));
throw std::invalid_argument{"Invalid guarantee."};
}

return octal;
return "0o" + generateStringWithGuarantee(guarantee, targetCharacters, length);
}
}
102 changes: 101 additions & 1 deletion src/modules/string/StringTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ TEST_F(StringTest, shouldGenerateOctalWithPrefix)
{
const auto octalLength = 8;

const auto octal = String::octal(octalLength);
const auto octal = String::octal({}, octalLength);

const auto prefix = octal.substr(0, 2);
const auto octalNumber = octal.substr(2);
Expand All @@ -488,3 +488,103 @@ TEST_F(StringTest, shouldGenerateOctalWithPrefix)
std::ranges::any_of(octal, [](char octalNumberCharacter)
{ return std::string("01234567").find(octalNumberCharacter) != std::string::npos; }));
}

TEST_F(StringTest, shouldGenerateOctalWithGuarantee1)
{
const auto octalLength = 10;
// exactly 2 '3' - 0 '5'
// atleast 2 '0' - 3 '6' - 1 '7'
// atmost 10 '6' - 10 '7'
GuaranteeMap guarantee{{'0', {2}}, {'3', {2, 2}}, {'5', {0, 0}}, {'6', {3, 10}}, {'7', {1, 10}}};
// it is a random function so lets test for 20 random generations
for (int i = 0; i < 20; ++i)
{
const auto octal = String::octal(std::move(guarantee), octalLength);

const auto prefix = octal.substr(0, 2);
const auto octalNumber = octal.substr(2);

ASSERT_EQ(octalNumber.size(), octalLength);
ASSERT_EQ(prefix, "0o");
ASSERT_TRUE(
std::ranges::any_of(octal, [](char octalNumberCharacter)
{ return std::string("01234567").find(octalNumberCharacter) != std::string::npos; }));
auto count_0 = std::ranges::count(octalNumber, '0');
auto count_3 = std::ranges::count(octalNumber, '3');
auto count_5 = std::ranges::count(octalNumber, '5');
auto count_6 = std::ranges::count(octalNumber, '6');
auto count_7 = std::ranges::count(octalNumber, '7');
ASSERT_TRUE(count_0 >= 2);
ASSERT_TRUE(count_3 == 2);
ASSERT_TRUE(count_5 == 0);
ASSERT_TRUE(count_6 >= 3 && count_6 <= 10);
ASSERT_TRUE(count_7 >= 1 && count_7 <= 10);
}
}

TEST_F(StringTest, shouldGenerateOctalWithGuarantee2)
{
const auto octalLength = 20;
// exactly 0 '2' '3' '4' '5' '6' '7'
// atleast 18 '0'
GuaranteeMap guarantee{{'0', {18}}, {'2', {0, 0}}, {'3', {0, 0}}, {'4', {0, 0}},
{'5', {0, 0}}, {'6', {0, 0}}, {'7', {0, 0}}};
// it is a random function so lets test for 20 random generations
for (int i = 0; i < 20; ++i)
{
const auto octal = String::octal(std::move(guarantee), octalLength);

const auto prefix = octal.substr(0, 2);
const auto octalNumber = octal.substr(2);

ASSERT_EQ(octalNumber.size(), octalLength);
ASSERT_EQ(prefix, "0o");
ASSERT_TRUE(
std::ranges::any_of(octal, [](char octalNumberCharacter)
{ return std::string("01234567").find(octalNumberCharacter) != std::string::npos; }));
auto count_0 = std::ranges::count(octalNumber, '0');
auto count_2 = std::ranges::count(octalNumber, '2');
auto count_3 = std::ranges::count(octalNumber, '3');
auto count_4 = std::ranges::count(octalNumber, '4');
auto count_5 = std::ranges::count(octalNumber, '5');
auto count_6 = std::ranges::count(octalNumber, '6');
auto count_7 = std::ranges::count(octalNumber, '7');

ASSERT_TRUE(count_0 >= 18);
ASSERT_TRUE(count_2 == 0);
ASSERT_TRUE(count_3 == 0);
ASSERT_TRUE(count_4 == 0);
ASSERT_TRUE(count_5 == 0);
ASSERT_TRUE(count_6 == 0);
ASSERT_TRUE(count_7 == 0);
}
}

TEST_F(StringTest, invalidGuaranteeForOctal1)
{
const auto octalLength = 10;
// exactly 0 '4'
// atleast 8 '0' - 9 '2' 9 '3' // invalid // total string size will be atleast 26 which is wrong
// atmost
GuaranteeMap guarantee{{'0', {8}}, {'2', {9}}, {'3', {9}}, {'4', {0, 0}}};
ASSERT_THROW(String::octal(std::move(guarantee), octalLength), std::invalid_argument);
}

TEST_F(StringTest, invalidGuaranteeForOctal2)
{
const auto octalLength = 20;
// atmost 2 '0' '1' '2' '3' '4' '5' '6' '7' // invalid // octal string won't exceed 16 which is wrong
GuaranteeMap guarantee{{'0', {0, 2}}, {'1', {0, 2}}, {'2', {0, 2}}, {'3', {0, 2}},
{'4', {0, 2}}, {'5', {0, 2}}, {'6', {0, 2}}, {'7', {0, 2}}};
ASSERT_THROW(String::octal(std::move(guarantee), octalLength), std::invalid_argument);
}

TEST_F(StringTest, invalidGuaranteeForOctal3)
{
const auto octalLength = 20;

// atleast 2 '8' // invalid // octal numbers cannot have '8'
// atmost 3 '8'
GuaranteeMap guarantee{{'0', {0, 2}}, {'1', {0, 2}}, {'8', {2, 3}}, {'2', {0, 2}}, {'3', {0, 2}}};
ASSERT_THROW(String::octal(std::move(guarantee), octalLength), std::invalid_argument);
}

0 comments on commit c08609a

Please sign in to comment.