Skip to content

Commit 744665b

Browse files
committed
simplify randomization
1 parent 14d48eb commit 744665b

1 file changed

Lines changed: 82 additions & 82 deletions

File tree

src/randomization.cpp

Lines changed: 82 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -11,57 +11,54 @@
1111
#include <oryx/chron/details/string_cast.hpp>
1212
#include <oryx/chron/details/to_underlying.hpp>
1313
#include <oryx/chron/details/parser.hpp>
14+
#include "oryx/chron/details/in_range.hpp"
1415

1516
namespace oryx::chron {
1617
namespace {
1718

19+
constexpr int kSelectedValueNone = -1;
20+
1821
template <typename T>
1922
auto GetRandomInRange(std::string_view section,
2023
int& selected_value,
2124
std::mt19937& twister,
22-
std::pair<int, int> limit = std::make_pair(-1, -1)) -> std::pair<bool, std::string> {
23-
auto result = std::make_pair(true, std::string{});
24-
selected_value = -1;
25-
26-
if (auto match = ctre::match<R"#([rR]\((\d+)\-(\d+)\))#">(section)) {
27-
// Random range, parse left and right numbers
28-
auto left = details::StringCast<int>(match.get<1>().to_view());
29-
auto right = details::StringCast<int>(match.get<2>().to_view());
30-
31-
// Apply limit if provided
32-
if (limit.first != -1 && limit.second != -1) {
33-
left = std::clamp(left, limit.first, limit.second);
34-
right = std::clamp(right, limit.first, limit.second);
35-
}
25+
std::pair<int, int> limit = std::make_pair(-1, -1)) -> std::optional<std::string> {
26+
selected_value = kSelectedValueNone;
3627

37-
std::set<T> numbers;
38-
result.first = details::Parser::ConvertFromStringRangeToNumberRange(std::format("{}-{}", left, right), numbers);
39-
40-
// Remove items outside the limit
41-
if (limit.first != -1 && limit.second != -1) {
42-
for (auto it = numbers.begin(); it != numbers.end();) {
43-
if (details::to_underlying(*it) < limit.first || details::to_underlying(*it) > limit.second) {
44-
it = numbers.erase(it);
45-
} else {
46-
++it;
47-
}
48-
}
49-
}
28+
auto match = ctre::match<R"#([rR]\((\d+)\-(\d+)\))#">(section);
29+
if (!match) {
30+
return std::string(section);
31+
}
5032

51-
if (result.first && !numbers.empty()) {
52-
// Select a random value from the valid numbers
53-
std::uniform_int_distribution<> distribution(0, static_cast<int>(numbers.size() - 1));
54-
auto it = numbers.begin();
55-
std::advance(it, distribution(twister));
56-
selected_value = details::to_underlying(*it);
57-
result.second = std::to_string(selected_value);
58-
}
59-
} else {
60-
// Not a random section, return as-is
61-
result.second = section;
33+
// Random range, parse left and right numbers
34+
auto left = details::StringCast<int>(match.get<1>().to_view());
35+
auto right = details::StringCast<int>(match.get<2>().to_view());
36+
37+
// Apply limit if provided
38+
if (limit.first != -1 && limit.second != -1) {
39+
left = std::clamp(left, limit.first, limit.second);
40+
right = std::clamp(right, limit.first, limit.second);
41+
}
42+
43+
std::set<T> numbers;
44+
bool success = details::Parser::ConvertFromStringRangeToNumberRange(std::format("{}-{}", left, right), numbers);
45+
46+
// Remove items outside the limit
47+
if (limit.first != -1 && limit.second != -1) {
48+
std::erase_if(numbers, [&limit](T val) -> bool {
49+
return !details::InRange<int>(details::to_underlying(val), limit.first, limit.second);
50+
});
51+
}
52+
53+
if (!success || numbers.empty()) {
54+
return std::nullopt;
6255
}
6356

64-
return result;
57+
std::uniform_int_distribution distribution(0, static_cast<int>(numbers.size() - 1));
58+
auto it = numbers.begin();
59+
std::advance(it, distribution(twister));
60+
selected_value = details::to_underlying(*it);
61+
return std::to_string(selected_value);
6562
}
6663

6764
auto DayLimiter(const std::set<Months>& months) -> std::pair<int, int> {
@@ -86,11 +83,9 @@ Randomization::Randomization()
8683
: twister_(random_device_()) {}
8784

8885
auto Randomization::Parse(std::string_view cron_schedule) -> std::optional<std::string> {
89-
// Split on space to get each separate part, six parts expected
90-
auto matcher = ctre::match<R"#(^\s*(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s*$)#">;
91-
// Replace text with numbers
92-
std::string working_copy{};
86+
const auto matcher = ctre::match<R"#(^\s*(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s*$)#">;
9387

88+
std::string preprocessed_schedule{};
9489
if (auto match = matcher(cron_schedule)) {
9590
// Replace month and day names first
9691
auto month = match.get<5>().to_string();
@@ -100,53 +95,58 @@ auto Randomization::Parse(std::string_view cron_schedule) -> std::optional<std::
10095
details::ReplaceDayNameWithNumeric(dow);
10196

10297
// Merge all sections into one string
103-
working_copy = std::format("{} {} {} {} {} {}", match.get<1>().to_view(), match.get<2>().to_view(),
104-
match.get<3>().to_view(), match.get<4>().to_view(), month, dow);
98+
preprocessed_schedule = std::format("{} {} {} {} {} {}", match.get<1>().to_view(), match.get<2>().to_view(),
99+
match.get<3>().to_view(), match.get<4>().to_view(), month, dow);
105100
}
106101

107-
std::string final_cron_schedule{};
108-
bool success{};
109-
if (auto match = matcher(working_copy)) {
110-
int selected_value = -1;
111-
auto second = GetRandomInRange<Seconds>(match.get<1>().to_view(), selected_value, twister_);
112-
success = second.first;
113-
final_cron_schedule = second.second;
114-
115-
auto minute = GetRandomInRange<Minutes>(match.get<2>().to_view(), selected_value, twister_);
116-
success &= minute.first;
117-
final_cron_schedule += " " + minute.second;
118-
119-
auto hour = GetRandomInRange<Hours>(match.get<3>().to_view(), selected_value, twister_);
120-
success &= hour.first;
121-
final_cron_schedule += " " + hour.second;
122-
123-
// Do Month before MonthDays to allow capping the allowed range.
124-
auto month = GetRandomInRange<Months>(match.get<5>().to_view(), selected_value, twister_);
125-
success &= month.first;
126-
127-
std::set<Months> month_range{};
128-
if (selected_value == -1) {
129-
// Month is not specific, get the range.
130-
success &= details::Parser::ConvertFromStringRangeToNumberRange(match.get<5>().to_string(), month_range);
131-
} else {
132-
month_range.emplace(static_cast<Months>(selected_value));
133-
}
102+
auto match = matcher(preprocessed_schedule);
103+
if (!match) [[unlikely]] {
104+
return std::nullopt;
105+
}
106+
107+
int selected_value = kSelectedValueNone;
108+
auto second = GetRandomInRange<Seconds>(match.get<1>().to_view(), selected_value, twister_);
109+
if (!second) [[unlikely]] {
110+
return std::nullopt;
111+
}
112+
113+
auto minute = GetRandomInRange<Minutes>(match.get<2>().to_view(), selected_value, twister_);
114+
if (!minute) [[unlikely]] {
115+
return std::nullopt;
116+
}
134117

135-
auto limits = DayLimiter(month_range);
118+
auto hour = GetRandomInRange<Hours>(match.get<3>().to_view(), selected_value, twister_);
119+
if (!hour) [[unlikely]] {
120+
return std::nullopt;
121+
}
136122

137-
auto day_of_month = GetRandomInRange<MonthDays>(match.get<4>().to_view(), selected_value, twister_, limits);
123+
// Do Month before MonthDays to allow capping the allowed range.
124+
auto month = GetRandomInRange<Months>(match.get<5>().to_view(), selected_value, twister_);
125+
if (!month) [[unlikely]] {
126+
return std::nullopt;
127+
}
138128

139-
success &= day_of_month.first;
140-
final_cron_schedule += " " + day_of_month.second + " " + month.second;
129+
std::set<Months> month_range{};
130+
if (selected_value == kSelectedValueNone) {
131+
// Month is not specific, get the range.
132+
bool success = details::Parser::ConvertFromStringRangeToNumberRange(match.get<5>().to_view(), month_range);
133+
if (!success) return std::nullopt;
134+
} else {
135+
month_range.emplace(static_cast<Months>(selected_value));
136+
}
141137

142-
auto day_of_week = GetRandomInRange<Weekdays>(match.get<6>().to_view(), selected_value, twister_);
143-
success &= day_of_week.first;
144-
final_cron_schedule += " " + day_of_week.second;
138+
auto limits = DayLimiter(month_range);
139+
auto day_of_month = GetRandomInRange<MonthDays>(match.get<4>().to_view(), selected_value, twister_, limits);
140+
if (!day_of_month) [[unlikely]] {
141+
return std::nullopt;
145142
}
146-
if (success) {
147-
return final_cron_schedule;
143+
144+
auto day_of_week = GetRandomInRange<Weekdays>(match.get<6>().to_view(), selected_value, twister_);
145+
if (!day_of_week) [[unlikely]] {
146+
return std::nullopt;
148147
}
149-
return std::nullopt;
148+
149+
return std::format("{} {} {} {} {} {}", *second, *minute, *hour, *day_of_month, *month, *day_of_week);
150150
}
151151

152152
} // namespace oryx::chron

0 commit comments

Comments
 (0)