Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Refactored all of randomElement related functions and iterators to use ranges instead #843

Merged
merged 4 commits into from
Aug 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
179 changes: 42 additions & 137 deletions include/faker-cxx/helper.h
Original file line number Diff line number Diff line change
@@ -1,176 +1,81 @@
#pragma once

#include <initializer_list>
#include <numeric>
#include <span>
#include <random>
#include <vector>
#include <iostream>

#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<char>(std::string{"abcd"}) // "b"
* faker::helper::randomElement<std::string>(std::vector<std::string>{{"hello"}, {"world"}}) // "hello"
* @endcode
*/
template <class T>
T randomElement(std::span<const T> data)
{
if (data.empty())
{
throw std::invalid_argument{"Data is empty."};
}

const auto index = number::integer<size_t>(data.size() - 1);

return data[index];
}
template <typename T>
concept input_range_with_faster_size_compute_than_linear_rng =
std::ranges::input_range<T> // must still be an input range no matter what, but additionally
&& (std::ranges::sized_range<T> // either knows its size in constant time, or
|| std::ranges::forward_range<T>); // can multipass to compute the size

template <typename T, std::size_t N>
T randomElement(const std::array<T, N>& data)
template <input_range_with_faster_size_compute_than_linear_rng Range>
decltype(auto) randomElement(Range&& range)
{
if (data.empty())
{
throw std::invalid_argument{"Data is empty."};
}

const auto index = number::integer<size_t>(data.size() - 1);

return data[index];
}

template <std::random_access_iterator It>
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 <std::forward_iterator It>
auto& randomElement(It start, It end)
template <std::ranges::input_range Range>
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."};
}

std::random_device rd;
std::mt19937 gen(rd());

std::reference_wrapper<typename std::iterator_traits<It>::value_type> result = *start;
++start;
size_t count = 1;

while (start != end) {
std::uniform_int_distribution<size_t> distrib(0, count - 1);
if (distrib(gen) == 0) {
result = *start;
}
++start;
++count;
}

return result.get();
}

template <std::input_iterator It>
auto randomElement(It start, It end)
{
if (start == end)
using RangeValue = std::ranges::range_value_t<decltype(range)>;
auto consume_itr = [&itr]() -> decltype(auto)
{
using reference_type = std::ranges::range_reference_t<decltype(range)>;
if constexpr (std::is_reference_v<reference_type>)
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<size_t> 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::string>(std::vector<std::string>{{"hello"}, {"world"}}) // "hello"
* @endcode
*/
template <class T>
T randomElement(const std::vector<T>& data)
{
if (data.empty())
{
throw std::invalid_argument{"Data is empty."};
}

const auto index = number::integer<size_t>(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::string>(std::initializer_list<std::string>{{"hello"}, {"world"}}) // "hello"
* @endcode
*/
template <class T>
T randomElement(const std::initializer_list<T>& data)
{
if (data.size() == 0)
{
throw std::invalid_argument{"Data is empty."};
}

const auto index = number::integer<size_t>(data.size() - 1);

return *(data.begin() + index);
}

/**
* @brief Get a random element by weight from a vector.
*
Expand Down
27 changes: 21 additions & 6 deletions include/faker-cxx/word.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,22 +144,37 @@ FAKER_CXX_EXPORT std::string_view preposition(std::optional<unsigned> length = s
*/
FAKER_CXX_EXPORT std::string_view verb(std::optional<unsigned> length = std::nullopt);

template <std::input_iterator It>
auto sortedSizeRandomElement(std::optional<unsigned int> length, It start, It end) ->
typename std::iterator_traits<It>::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 <std::ranges::range Range>
auto sortedSizeRandomElement(std::optional<unsigned int> 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)
Expand All @@ -177,6 +192,6 @@ auto sortedSizeRandomElement(std::optional<unsigned int> length, It start, It en
}
}

return helper::randomElement(lower_it, upper_it);
return helper::randomElement(std::ranges::subrange(lower_it, upper_it));
}
}
7 changes: 4 additions & 3 deletions src/common/algo_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

#include <algorithm>
#include <functional>
#include <iterator>
#include <random>
#include <set>
#include <stdexcept>
#include <string>
#include <iterator>

#include "faker-cxx/datatype.h"
#include "faker-cxx/export.h"
Expand Down Expand Up @@ -44,9 +44,10 @@ static T::key_type objectKey(const T& object)
std::vector<typename T::key_type> 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<typename T::key_type>(keys);
return randomElement(keys);
}

template <typename TResult>
Expand Down
2 changes: 1 addition & 1 deletion src/modules/airline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Airport airport()
std::string seat(AircraftType aircraftType)
{
return std::to_string(number::integer(1, aircraftTypeMaxRows.at(aircraftType))) +
helper::randomElement<char>(aircraftTypeSeatLetters.at(aircraftType));
helper::randomElement(aircraftTypeSeatLetters.at(aircraftType));
}

std::string recordLocator(bool allowNumerics)
Expand Down
5 changes: 2 additions & 3 deletions src/modules/internet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,7 @@ std::string username(std::optional<std::string> firstName, std::optional<std::st
break;
case 2:
username = common::format("{}{}{}", lastNameLower,
helper::randomElement(std::vector<std::string>{".", "_", ""}),
firstNameLower);
helper::randomElement(std::vector<std::string>{".", "_", ""}), firstNameLower);
break;
}

Expand Down Expand Up @@ -149,7 +148,7 @@ std::string password(int length, const PasswordOptions& options)

for (int i = 0; i < length; ++i)
{
password += helper::randomElement<char>(characters);
password += helper::randomElement(characters);
}

return password;
Expand Down
2 changes: 1 addition & 1 deletion src/modules/science.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Unit>(units);
return helper::randomElement(units);
}

Unit distanceUnit()
Expand Down
12 changes: 6 additions & 6 deletions src/modules/string.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ std::string fromCharacters(const std::string& characters, unsigned int length)

for (unsigned i = 0; i < length; i++)
{
result += helper::randomElement<char>(characters);
result += helper::randomElement(characters);
}

return result;
Expand Down Expand Up @@ -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<char>(targetCharacters);
alpha += helper::randomElement(targetCharacters);
}

return alpha;
Expand Down Expand Up @@ -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<char>(targetCharacters);
alphanumeric += helper::randomElement(targetCharacters);
}

return alphanumeric;
Expand Down Expand Up @@ -263,11 +263,11 @@ std::string numeric(unsigned int length, bool allowLeadingZeros)
{
if (i == 0 && allowLeadingZeros)
{
alphanumericStr.push_back(helper::randomElement<char>(numericCharacters));
alphanumericStr.push_back(helper::randomElement(numericCharacters));
}
else
{
alphanumericStr.push_back(helper::randomElement<char>(numericCharactersWithoutZero));
alphanumericStr.push_back(helper::randomElement(numericCharactersWithoutZero));
}
}

Expand Down Expand Up @@ -321,7 +321,7 @@ std::string hexadecimal(unsigned int length, HexCasing casing, HexPrefix prefix)

for (unsigned i = 0; i < length; i++)
{
hexadecimal += helper::randomElement<char>(hexadecimalCharacters);
hexadecimal += helper::randomElement(hexadecimalCharacters);
}

return hexadecimal;
Expand Down
2 changes: 1 addition & 1 deletion src/modules/system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,6 @@ std::string cron(const CronOptions& options)
"@reboot", "@weekly", "@yearly"};

return (!includeNonStandard || datatype::boolean(0)) ? standardExpression :
helper::randomElement<std::string>(nonStandardExpressions);
helper::randomElement(nonStandardExpressions);
}
}
Loading
Loading