diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ee940c59..964f846aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,8 @@ option(BUILD_EXAMPLES "Build examples" OFF) option(BUILD_TESTING "Build tests" ON) option(CODE_COVERAGE "Build faker-cxx with coverage support" OFF) +SET(CMAKE_EXPORT_COMPILE_COMMANDS ON) + if (MSVC) set(CMAKE_REQUIRED_FLAGS /std:c++20) else() diff --git a/include/faker-cxx/helper.h b/include/faker-cxx/helper.h index 94ded24bb..423b470b6 100644 --- a/include/faker-cxx/helper.h +++ b/include/faker-cxx/helper.h @@ -1,74 +1,48 @@ #pragma once -#include #include -#include +#include #include -#include #include "number.h" namespace faker::helper { -/** - * @brief Get a random element from an STL container. - * - * @tparam T an element type of the container. - * - * @param data The container. - * - * @return T a random element from the container. - * - * @code - * faker::helper::randomElement(std::string{"abcd"}) // "b" - * faker::helper::randomElement(std::vector{{"hello"}, {"world"}}) // "hello" - * @endcode - */ -template -T randomElement(std::span data) -{ - if (data.empty()) - { - throw std::invalid_argument{"Data is empty."}; - } - - const auto index = number::integer(data.size() - 1); - return data[index]; -} +template +concept input_range_with_faster_size_compute_than_linear_rng = + std::ranges::input_range // must still be an input range no matter what, but additionally + && (std::ranges::sized_range // either knows its size in constant time, or + || std::ranges::forward_range); // can multipass to compute the size -template -T randomElement(const std::array& data) +template +decltype(auto) randomElement(Range&& range) { - if (data.empty()) - { - throw std::invalid_argument{"Data is empty."}; - } - - const auto index = number::integer(data.size() - 1); - - return data[index]; -} - -template -auto randomElement(It start, It end) -{ - if (start == end) + if (std::ranges::empty(range)) { throw std::invalid_argument{"Range [start, end) is empty."}; } - auto size = end - start; + auto size = std::ranges::distance(range); + const auto index = number::integer(size - 1); - return start[index]; + return (*std::ranges::next(range.begin(), index)); } - -template -auto& randomElement(It start, It end) +template +auto randomElement(Range&& range) { - if (start == end) + auto const end = range.end(); + auto itr = range.begin(); + + // Note: std::ranges::empty in general may need to grab begin/end + // then drop the iterator/sentinel, which can invalidate being, so + // it's not always usable with input_range's that aren't forward_range's + // we're going to "consume" the iterators ourselves so we can manually + // emptiness check by taking the iterator/sentinel pair and not dropping + // them + if (itr == end) { throw std::invalid_argument{"Range [start, end) is empty."}; } @@ -76,101 +50,32 @@ auto& randomElement(It start, It end) std::random_device rd; std::mt19937 gen(rd()); - std::reference_wrapper::value_type> result = *start; - ++start; - size_t count = 1; - - while (start != end) { - std::uniform_int_distribution distrib(0, count - 1); - if (distrib(gen) == 0) { - result = *start; - } - ++start; - ++count; - } - - return result.get(); -} - -template -auto randomElement(It start, It end) -{ - if (start == end) + using RangeValue = std::ranges::range_value_t; + auto consume_itr = [&itr]() -> decltype(auto) + { + using reference_type = std::ranges::range_reference_t; + if constexpr (std::is_reference_v) + return std::move(*itr); + else + return *itr; + }; + + RangeValue result = consume_itr(); + ++itr; + std::size_t count = 1; + + for (; itr != end; ++itr, ++count) { - throw std::invalid_argument{"Range [start, end) is empty."}; - } - - std::random_device rd; - std::mt19937 gen(rd()); - - auto result = std::move(*start); - ++start; - size_t count = 1; - - while (start != end) { std::uniform_int_distribution distrib(0, count); - if (distrib(gen) == 0) { - result = std::move(*start); + if (distrib(gen) == 0) + { + result = consume_itr(); } - ++start; - ++count; } return result; } -/** - * @brief Get a random element from a vector. - * - * @tparam T an element type of the vector. - * - * @param data vector of elements. - * - * @return T a random element from the vector. - * - * @code - * faker::helper::randomElement(std::vector{{"hello"}, {"world"}}) // "hello" - * @endcode - */ -template -T randomElement(const std::vector& data) -{ - if (data.empty()) - { - throw std::invalid_argument{"Data is empty."}; - } - - const auto index = number::integer(data.size() - 1); - - return data[index]; -} - -/** - * @brief Get a random element from an initializer list. - * - * @tparam T an element type of the initializer list. - * - * @param data initializer list of elements. - * - * @return T a random element from the initializer list. - * - * @code - * faker::helper::randomElement(std::initializer_list{{"hello"}, {"world"}}) // "hello" - * @endcode - */ -template -T randomElement(const std::initializer_list& data) -{ - if (data.size() == 0) - { - throw std::invalid_argument{"Data is empty."}; - } - - const auto index = number::integer(data.size() - 1); - - return *(data.begin() + index); -} - /** * @brief Get a random element by weight from a vector. * diff --git a/include/faker-cxx/word.h b/include/faker-cxx/word.h index 607207df8..5ec10277e 100644 --- a/include/faker-cxx/word.h +++ b/include/faker-cxx/word.h @@ -144,22 +144,37 @@ FAKER_CXX_EXPORT std::string_view preposition(std::optional length = s */ FAKER_CXX_EXPORT std::string_view verb(std::optional length = std::nullopt); -template -auto sortedSizeRandomElement(std::optional length, It start, It end) -> - typename std::iterator_traits::value_type +/** + * @brief Returns random element of length + * + * @param length The length of the elements to be picked from + * + * @ range The range of elements + * + * @returns An element of the range value type + * + * @code + * faker::word::sortedSizeRandomElement(3, {"hi, "hello", "hey"}) // "hey" - Since "hey" is the only element of length 3 + * @endcode + */ +template +auto sortedSizeRandomElement(std::optional length, Range&& range) -> decltype(auto) { if (!length) { - return helper::randomElement(start, end); + return helper::randomElement(range); } size_t length_64 = *length; + auto start = range.begin(); + auto end = range.end(); + auto lower_it = ::std::lower_bound(start, end, length_64, [](const auto& lhs, const auto& value) { return lhs.size() < value; }); if (lower_it == end) { - return helper::randomElement(start, end); + return helper::randomElement(range); } if (lower_it->size() != length) @@ -177,6 +192,6 @@ auto sortedSizeRandomElement(std::optional length, It start, It en } } - return helper::randomElement(lower_it, upper_it); + return helper::randomElement(std::ranges::subrange(lower_it, upper_it)); } } diff --git a/src/common/algo_helper.h b/src/common/algo_helper.h index f676b939d..48c221c78 100644 --- a/src/common/algo_helper.h +++ b/src/common/algo_helper.h @@ -2,11 +2,11 @@ #include #include +#include #include #include #include #include -#include #include "faker-cxx/datatype.h" #include "faker-cxx/export.h" @@ -44,9 +44,10 @@ static T::key_type objectKey(const T& object) std::vector keys; keys.reserve(object.size()); - std::transform(object.begin(), object.end(), std::back_inserter(keys), [](const auto& entry) { return entry.first; }); + std::transform(object.begin(), object.end(), std::back_inserter(keys), + [](const auto& entry) { return entry.first; }); - return randomElement(keys); + return randomElement(keys); } template diff --git a/src/modules/airline.cpp b/src/modules/airline.cpp index 47f864c2c..19c1338f1 100644 --- a/src/modules/airline.cpp +++ b/src/modules/airline.cpp @@ -33,7 +33,7 @@ Airport airport() std::string seat(AircraftType aircraftType) { return std::to_string(number::integer(1, aircraftTypeMaxRows.at(aircraftType))) + - helper::randomElement(aircraftTypeSeatLetters.at(aircraftType)); + helper::randomElement(aircraftTypeSeatLetters.at(aircraftType)); } std::string recordLocator(bool allowNumerics) diff --git a/src/modules/internet.cpp b/src/modules/internet.cpp index d98f819ec..f1832e07c 100644 --- a/src/modules/internet.cpp +++ b/src/modules/internet.cpp @@ -100,8 +100,7 @@ std::string username(std::optional firstName, std::optional{".", "_", ""}), - firstNameLower); + helper::randomElement(std::vector{".", "_", ""}), firstNameLower); break; } @@ -149,7 +148,7 @@ std::string password(int length, const PasswordOptions& options) for (int i = 0; i < length; ++i) { - password += helper::randomElement(characters); + password += helper::randomElement(characters); } return password; diff --git a/src/modules/science.cpp b/src/modules/science.cpp index 4d54a0d85..2ba902831 100644 --- a/src/modules/science.cpp +++ b/src/modules/science.cpp @@ -20,7 +20,7 @@ Unit unit() units.insert(units.end(), currentUnits.begin(), currentUnits.end()); units.insert(units.end(), temperatureUnits.begin(), temperatureUnits.end()); - return helper::randomElement(units); + return helper::randomElement(units); } Unit distanceUnit() diff --git a/src/modules/string.cpp b/src/modules/string.cpp index 0624a1853..e2890c627 100644 --- a/src/modules/string.cpp +++ b/src/modules/string.cpp @@ -155,7 +155,7 @@ std::string fromCharacters(const std::string& characters, unsigned int length) for (unsigned i = 0; i < length; i++) { - result += helper::randomElement(characters); + result += helper::randomElement(characters); } return result; @@ -196,7 +196,7 @@ std::string alpha(unsigned length, StringCasing casing, const std::string& exclu for (unsigned i = 0; i < length; i++) { - alpha += helper::randomElement(targetCharacters); + alpha += helper::randomElement(targetCharacters); } return alpha; @@ -232,7 +232,7 @@ std::string alphanumeric(unsigned int length, StringCasing casing, const std::st for (unsigned i = 0; i < length; i++) { - alphanumeric += helper::randomElement(targetCharacters); + alphanumeric += helper::randomElement(targetCharacters); } return alphanumeric; @@ -263,11 +263,11 @@ std::string numeric(unsigned int length, bool allowLeadingZeros) { if (i == 0 && allowLeadingZeros) { - alphanumericStr.push_back(helper::randomElement(numericCharacters)); + alphanumericStr.push_back(helper::randomElement(numericCharacters)); } else { - alphanumericStr.push_back(helper::randomElement(numericCharactersWithoutZero)); + alphanumericStr.push_back(helper::randomElement(numericCharactersWithoutZero)); } } @@ -321,7 +321,7 @@ std::string hexadecimal(unsigned int length, HexCasing casing, HexPrefix prefix) for (unsigned i = 0; i < length; i++) { - hexadecimal += helper::randomElement(hexadecimalCharacters); + hexadecimal += helper::randomElement(hexadecimalCharacters); } return hexadecimal; diff --git a/src/modules/system.cpp b/src/modules/system.cpp index 7340aa50b..7c71ce83d 100644 --- a/src/modules/system.cpp +++ b/src/modules/system.cpp @@ -267,6 +267,6 @@ std::string cron(const CronOptions& options) "@reboot", "@weekly", "@yearly"}; return (!includeNonStandard || datatype::boolean(0)) ? standardExpression : - helper::randomElement(nonStandardExpressions); + helper::randomElement(nonStandardExpressions); } } diff --git a/src/modules/word.cpp b/src/modules/word.cpp index 5a84ead33..27b012fc3 100644 --- a/src/modules/word.cpp +++ b/src/modules/word.cpp @@ -1,16 +1,17 @@ +#include "faker-cxx/word.h" + #include #include #include #include -#include "faker-cxx/word.h" #include "word_data.h" namespace faker::word { std::string_view sample(std::optional length) { - return sortedSizeRandomElement(length, _allWords.cbegin(), _allWords.cend()); + return sortedSizeRandomElement(length, _allWords); } std::string words(unsigned numberOfWords) @@ -64,36 +65,36 @@ std::string words(unsigned numberOfWords) std::string_view adjective(std::optional length) { - return sortedSizeRandomElement(length, _adjectives_sorted.cbegin(), _adjectives_sorted.cend()); + return sortedSizeRandomElement(length, _adjectives_sorted); } std::string_view adverb(std::optional length) { - return sortedSizeRandomElement(length, _adverbs_sorted.cbegin(), _adverbs_sorted.cend()); + return sortedSizeRandomElement(length, _adverbs_sorted); } std::string_view conjunction(std::optional length) { - return sortedSizeRandomElement(length, _conjunctions_sorted.cbegin(), _conjunctions_sorted.cend()); + return sortedSizeRandomElement(length, _conjunctions_sorted); } std::string_view interjection(std::optional length) { - return sortedSizeRandomElement(length, _interjections_sorted.cbegin(), _interjections_sorted.cend()); + return sortedSizeRandomElement(length, _interjections_sorted); } std::string_view noun(std::optional length) { - return sortedSizeRandomElement(length, _nouns_sorted.cbegin(), _nouns_sorted.cend()); + return sortedSizeRandomElement(length, _nouns_sorted); } std::string_view preposition(std::optional length) { - return sortedSizeRandomElement(length, _prepositions_sorted.cbegin(), _prepositions_sorted.cend()); + return sortedSizeRandomElement(length, _prepositions_sorted); } std::string_view verb(std::optional length) { - return sortedSizeRandomElement(length, _verbs_sorted.cbegin(), _verbs_sorted.cend()); + return sortedSizeRandomElement(length, _verbs_sorted); } } diff --git a/tests/modules/word_test.cpp b/tests/modules/word_test.cpp index d6c770dd8..bf8d64f17 100644 --- a/tests/modules/word_test.cpp +++ b/tests/modules/word_test.cpp @@ -1,5 +1,3 @@ -#include "faker-cxx/word.h" - #include #include #include @@ -7,6 +5,7 @@ #include "gtest/gtest.h" #include "common/string_helper.h" +#include "faker-cxx/word.h" #include "word_data.h" using namespace faker::word; @@ -223,16 +222,18 @@ TEST_F(WordTest, shouldGenerateWords) TEST_F(WordTest, shouldReturnRandomElementWhenExactLengthNotFound) { const unsigned int existingLength = 5; - + std::vector matchingAdjectives; - for (const auto& adj : _adjectives_sorted) { - if (adj.size() == existingLength) { + for (const auto& adj : _adjectives_sorted) + { + if (adj.size() == existingLength) + { matchingAdjectives.push_back(adj); } } - + const auto generatedAdjective = adjective(existingLength + 1); - + ASSERT_TRUE(std::ranges::find(_adjectives_sorted, generatedAdjective) != _adjectives_sorted.end()); ASSERT_TRUE(std::ranges::find(matchingAdjectives, generatedAdjective) == matchingAdjectives.end()); } @@ -248,9 +249,10 @@ TEST_F(WordTest, shouldGenerateLargeNumberOfWords) const unsigned int largeWordCount = 300; const auto generatedWords = words(largeWordCount); const auto separatedWords = common::split(generatedWords, " "); - + ASSERT_EQ(separatedWords.size(), largeWordCount); - for (const auto& word : separatedWords) { + for (const auto& word : separatedWords) + { ASSERT_TRUE(std::ranges::find(_allWords, word) != _allWords.end()); } } @@ -260,7 +262,7 @@ TEST_F(WordTest, returnsRandomElementWhenAllElementsLessthanGivenLength) std::vector words = {"one", "three", "five"}; std::optional length = 6; - auto result = sortedSizeRandomElement(length, words.begin(), words.end()); + auto result = sortedSizeRandomElement(length, words); ASSERT_TRUE(result == "one" || result == "three" || result == "five"); } @@ -270,7 +272,7 @@ TEST_F(WordTest, returnsFirstElementWhenNoLengthMatch) std::vector words = {"one", "three", "five"}; std::optional length = 4; - auto result = sortedSizeRandomElement(length, words.begin(), words.end()); + auto result = sortedSizeRandomElement(length, words); ASSERT_TRUE(result == "three"); -} \ No newline at end of file +}