Skip to content

Commit

Permalink
refactor: Refactored all of randomElement related functions and itera…
Browse files Browse the repository at this point in the history
…tors to use ranges instead (cieslarmichal#843)

* Added compiler_commands.json to be generated in CMake and removed unnecessary headers + range error

* Refactored randomElement to use ranges and optimized algo further

* Refactored sortedSizeRandomElement and randomElement's uses

Everything now uses ranges instead of iterators, much cleaner and easier

* Further replaced extra overloads and changed to range concept for sortedSizeRandomElement
  • Loading branch information
eric-bodhi authored Aug 3, 2024
1 parent 99dd6ec commit 723e362
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 179 deletions.
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

0 comments on commit 723e362

Please sign in to comment.