Skip to content

Commit 8c7a5be

Browse files
authored
Merge pull request #52 from popmonkey/refactor-string-use
Refactor string use and fix memory leaks
2 parents ddcb816 + 46cccbd commit 8c7a5be

29 files changed

Lines changed: 627 additions & 398 deletions

.github/workflows/linux-build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ jobs:
136136
- name: Run Integration Tests
137137
run: |
138138
chmod +x test/integration/run_test.sh
139+
chmod +x test/integration/test_formatter_stdout.sh
139140
./test/integration/run_test.sh
140141
141142
# ---------------------------------------------------------

CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,6 @@ add_library(jres_solver_lib
159159
src/jres_solver.cpp
160160
src/jres_json_converter.cpp
161161
src/jres_internal_types.cpp
162-
src/jres_solver_base.cpp
163162
src/jres_standard_solver.cpp
164163
src/analysis/capacity_analyzer.cpp
165164
src/analysis/solver_diagnostics.cpp

GEMINI.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ ctest
8181

8282
There is also a script to test the formatter's stdout functionality:
8383
```bash
84-
./test_formatter_stdout.sh
84+
./test/integration/test_formatter_stdout.sh
8585
```
8686
8787
### Running the CLI Tools

cmd/solver/cli.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,4 +212,4 @@ int main(int argc, char **argv)
212212
}
213213

214214
return returnCode;
215-
}
215+
}

include/jres_solver/jres_solver.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,4 +237,4 @@ JRES_SOLVER_API void free_json_string(char* json_string);
237237
} // extern "C"
238238
#endif
239239

240-
#endif // JRES_SOLVER_HPP
240+
#endif // JRES_SOLVER_HPP

src/analysis/capacity_analyzer.cpp

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,26 @@ CapacityAnalysis CapacityAnalyzer::calculate_max_potential_capacity(
1414
const std::vector<TeamMember>& participants,
1515
const SolverInput& input)
1616
{
17-
// Parse stint times once
18-
std::vector<std::chrono::system_clock::time_point> startTimes;
19-
std::vector<std::chrono::system_clock::time_point> endTimes;
17+
std::vector<std::time_t> startTimes;
18+
std::vector<std::time_t> endTimes;
2019
startTimes.reserve(input.stints.size());
2120
endTimes.reserve(input.stints.size());
2221

23-
std::chrono::system_clock::time_point raceStart;
24-
std::chrono::system_clock::time_point raceEnd;
22+
std::time_t raceStart;
23+
std::time_t raceEnd;
2524
bool raceTimesInit = false;
2625

2726
for (const auto& stint : input.stints) {
28-
auto s = TimeHelpers::stringToTimePoint(stint.startTime);
29-
auto e = TimeHelpers::stringToTimePoint(stint.endTime);
30-
startTimes.push_back(s);
31-
endTimes.push_back(e);
27+
startTimes.push_back(stint.startTime);
28+
endTimes.push_back(stint.endTime);
3229

3330
if(!raceTimesInit) {
34-
raceStart = s;
35-
raceEnd = e;
31+
raceStart = stint.startTime;
32+
raceEnd = stint.endTime;
3633
raceTimesInit = true;
3734
} else {
38-
if(s < raceStart) raceStart = s;
39-
if(e > raceEnd) raceEnd = e;
35+
if(stint.startTime < raceStart) raceStart = stint.startTime;
36+
if(stint.endTime > raceEnd) raceEnd = stint.endTime;
4037
}
4138
}
4239

@@ -47,10 +44,11 @@ CapacityAnalysis CapacityAnalyzer::calculate_max_potential_capacity(
4744
for (const auto& p : participants) {
4845
// Build Availability
4946
std::vector<bool> is_available(input.stints.size(), true);
50-
auto member_availability_it = input.availability.find(p.name);
47+
auto member_availability_it = input.availability.find(p.nameId);
5148
if (member_availability_it != input.availability.end()) {
5249
for (size_t s = 0; s < input.stints.size(); ++s) {
53-
std::string key = TimeHelpers::timePointToKey(startTimes[s]);
50+
// Check if this stint time is unavailable
51+
std::time_t key = TimeHelpers::roundToHour(startTimes[s]);
5452
auto time_it = member_availability_it->second.find(key);
5553
if (time_it != member_availability_it->second.end() &&
5654
time_it->second == Availability::Unavailable) {
@@ -68,24 +66,24 @@ CapacityAnalysis CapacityAnalyzer::calculate_max_potential_capacity(
6866
planned_drive[s] = true;
6967
base_capacity++;
7068

71-
auto duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(endTimes[s] - startTimes[s]).count();
72-
driver_total_hours += static_cast<double>(duration_ms) / 3600000.0;
69+
double h = std::difftime(endTimes[s], startTimes[s]) / 3600.0;
70+
driver_total_hours += h;
7371
}
7472
}
7573

7674
// Adjust for Global Minimum Rest (One Instance)
7775
int final_capacity = base_capacity;
7876
if (input.minimumRestHours > 0) {
79-
auto minRestDuration = std::chrono::hours(input.minimumRestHours);
77+
long long minRestSeconds = (long long)input.minimumRestHours * 3600;
8078
int min_loss = base_capacity;
8179
bool found_valid_window = false;
8280

83-
std::vector<std::chrono::system_clock::time_point> candidateStarts;
81+
std::vector<std::time_t> candidateStarts;
8482
candidateStarts.push_back(raceStart);
8583
for(const auto& t : endTimes) candidateStarts.push_back(t);
8684

8785
for(const auto& tStart : candidateStarts) {
88-
auto tEnd = tStart + minRestDuration;
86+
auto tEnd = tStart + minRestSeconds;
8987
if (tEnd > raceEnd) continue;
9088
found_valid_window = true;
9189

@@ -109,7 +107,8 @@ CapacityAnalysis CapacityAnalyzer::calculate_max_potential_capacity(
109107

110108
analysis.totalCapacity += final_capacity;
111109

112-
ss << "\n- " << p.name << ": " << final_capacity
110+
std::string name = input.strings.get_string(p.nameId);
111+
ss << "\n- " << name << ": " << final_capacity
113112
<< " stints (approx " << std::fixed << std::setprecision(1) << driver_total_hours
114113
<< "h, MinRest=" << input.minimumRestHours << "h)";
115114
}

src/analysis/solver_diagnostics.cpp

Lines changed: 45 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -18,59 +18,61 @@ namespace jres::analysis {
1818

1919
std::string explain_assignment_failure(
2020
int stintIndex,
21-
const std::string& violationDriver,
21+
jres::internal::ID violationDriverId,
2222
const jres::internal::SolverInput& input,
2323
const std::vector<jres::internal::TeamMember>& driverPool,
24-
const std::map<std::pair<std::string, int>, int>& driverWorkVars,
24+
const std::map<std::pair<jres::internal::ID, int>, int>& driverWorkVars,
2525
const std::vector<double>& colValues)
2626
{
2727
using namespace jres::internal;
2828
std::ostringstream ss;
2929

3030
// Time points for the target stint
31-
auto tStart = TimeHelpers::stringToTimePoint(input.stints[stintIndex].startTime);
32-
auto tEnd = TimeHelpers::stringToTimePoint(input.stints[stintIndex].endTime);
31+
auto tStart = input.stints[stintIndex].startTime;
32+
auto tEnd = input.stints[stintIndex].endTime;
3333

3434
// Identify which drivers are assigned to which stints in the CURRENT solution
35-
std::map<std::string, std::set<int>> driverAssignments;
35+
std::map<ID, std::set<int>> driverAssignments;
3636
for (size_t s = 0; s < input.stints.size(); ++s) {
3737
for (const auto& p : driverPool) {
38-
if (driverWorkVars.count({p.name, (int)s})) {
39-
int idx = driverWorkVars.at({p.name, (int)s});
38+
if (driverWorkVars.count({p.nameId, (int)s})) {
39+
int idx = driverWorkVars.at({p.nameId, (int)s});
4040
if (idx < (int)colValues.size() && colValues[idx] > 0.5) {
41-
driverAssignments[p.name].insert((int)s);
41+
driverAssignments[p.nameId].insert((int)s);
4242
}
4343
}
4444
}
4545
}
4646

4747
// Helper: Get stint duration in hours
4848
auto getDuration = [&](int sIdx) {
49-
auto s = TimeHelpers::stringToTimePoint(input.stints[sIdx].startTime);
50-
auto e = TimeHelpers::stringToTimePoint(input.stints[sIdx].endTime);
51-
return std::chrono::duration<double, std::ratio<3600>>(e - s).count();
49+
return std::difftime(input.stints[sIdx].endTime, input.stints[sIdx].startTime) / 3600.0;
5250
};
5351

5452
ss << " Alternatives Analysis:";
5553

5654
for (const auto& candidate : driverPool) {
57-
if (candidate.name == violationDriver) continue;
55+
if (candidate.nameId == violationDriverId) continue;
5856

59-
ss << "\n - " << candidate.name << ": ";
57+
std::string candidateName = input.strings.get_string(candidate.nameId);
58+
59+
ss << "\n - " << candidateName << ": ";
6060
std::vector<std::string> reasons;
6161

6262
// Check Availability
6363
bool isUnavailable = false;
64-
auto it = input.availability.find(candidate.name);
64+
auto it = input.availability.find(candidate.nameId);
6565
if (it != input.availability.end()) {
6666
auto cursor = tStart;
67+
// Align cursor to hour if not already (assuming availability is hourly)
68+
cursor = TimeHelpers::roundToHour(cursor);
69+
6770
while (cursor < tEnd) {
68-
std::string key = TimeHelpers::timePointToKey(cursor);
69-
if (it->second.count(key) && it->second.at(key) == Availability::Unavailable) {
71+
if (it->second.count(cursor) && it->second.at(cursor) == Availability::Unavailable) {
7072
isUnavailable = true;
7173
break;
7274
}
73-
cursor += std::chrono::hours(1);
75+
cursor += 3600;
7476
}
7577
}
7678
if (isUnavailable) {
@@ -80,11 +82,11 @@ std::string explain_assignment_failure(
8082
// Check Consecutive Stints
8183
int consecutiveCount = 1;
8284
for (int k = stintIndex - 1; k >= 0; --k) {
83-
if (driverAssignments[candidate.name].count(k)) consecutiveCount++;
85+
if (driverAssignments[candidate.nameId].count(k)) consecutiveCount++;
8486
else break;
8587
}
8688
for (size_t k = stintIndex + 1; k < input.stints.size(); ++k) {
87-
if (driverAssignments[candidate.name].count((int)k)) consecutiveCount++;
89+
if (driverAssignments[candidate.nameId].count((int)k)) consecutiveCount++;
8890
else break;
8991
}
9092

@@ -95,15 +97,15 @@ std::string explain_assignment_failure(
9597
// Check Minimum Rest
9698
if (input.minimumRestHours > 0) {
9799
double minRestSec = input.minimumRestHours * 3600.0;
98-
for (int assignedS : driverAssignments[candidate.name]) {
99-
auto s1_start = TimeHelpers::stringToTimePoint(input.stints[stintIndex].startTime);
100-
auto s1_end = TimeHelpers::stringToTimePoint(input.stints[stintIndex].endTime);
101-
auto s2_start = TimeHelpers::stringToTimePoint(input.stints[assignedS].startTime);
102-
auto s2_end = TimeHelpers::stringToTimePoint(input.stints[assignedS].endTime);
100+
for (int assignedS : driverAssignments[candidate.nameId]) {
101+
auto s1_start = input.stints[stintIndex].startTime;
102+
auto s1_end = input.stints[stintIndex].endTime;
103+
auto s2_start = input.stints[assignedS].startTime;
104+
auto s2_end = input.stints[assignedS].endTime;
103105

104106
double gap = 0.0;
105-
if (s1_end <= s2_start) gap = std::chrono::duration<double>(s2_start - s1_end).count();
106-
else if (s2_end <= s1_start) gap = std::chrono::duration<double>(s1_start - s2_end).count();
107+
if (s1_end <= s2_start) gap = std::difftime(s2_start, s1_end);
108+
else if (s2_end <= s1_start) gap = std::difftime(s1_start, s2_end);
107109
else gap = -1.0;
108110

109111
if (gap < minRestSec - 1.0) {
@@ -120,11 +122,11 @@ std::string explain_assignment_failure(
120122
if (input.maximumBusyHours > 0) {
121123
double busyDuration = getDuration(stintIndex);
122124
for (int k = stintIndex - 1; k >= 0; --k) {
123-
if (driverAssignments[candidate.name].count(k)) busyDuration += getDuration(k);
125+
if (driverAssignments[candidate.nameId].count(k)) busyDuration += getDuration(k);
124126
else break;
125127
}
126128
for (size_t k = stintIndex + 1; k < input.stints.size(); ++k) {
127-
if (driverAssignments[candidate.name].count((int)k)) busyDuration += getDuration(k);
129+
if (driverAssignments[candidate.nameId].count((int)k)) busyDuration += getDuration(k);
128130
else break;
129131
}
130132
if (busyDuration > input.maximumBusyHours) {
@@ -171,8 +173,8 @@ std::string formatStintList(const std::vector<int>& indices, const jres::interna
171173
std::vector<std::string> formatHumanDiagnostic(
172174
const std::map<int, jres::internal::SlackInfo>& slackInfo,
173175
const std::set<int>& unavailableVars,
174-
const std::map<std::pair<std::string, int>, int>& driverWorkVars,
175-
const std::map<std::pair<std::string, int>, int>& spotterWorkVars,
176+
const std::map<std::pair<jres::internal::ID, int>, int>& driverWorkVars,
177+
const std::map<std::pair<jres::internal::ID, int>, int>& spotterWorkVars,
176178
const std::vector<double>& colValues,
177179
const jres::internal::SolverInput& input,
178180
const std::vector<jres::internal::TeamMember>& driverPool,
@@ -206,14 +208,14 @@ std::vector<std::string> formatHumanDiagnostic(
206208
std::map<ViolationKey, std::vector<int>> groupedViolations;
207209
std::vector<std::string> globalViolations;
208210

209-
// Build reverse map for variable index -> (Member, Stint, Role)
211+
// Build reverse map for variable index -> (MemberName, Stint, Role)
210212
std::map<int, std::tuple<std::string, int, std::string>> varToInfo;
211213

212214
for(const auto& [key, varIdx] : driverWorkVars) {
213-
varToInfo[varIdx] = std::make_tuple(key.first, key.second, "Driver");
215+
varToInfo[varIdx] = std::make_tuple(input.strings.get_string(key.first), key.second, "Driver");
214216
}
215217
for(const auto& [key, varIdx] : spotterWorkVars) {
216-
varToInfo[varIdx] = std::make_tuple(key.first, key.second, "Spotter");
218+
varToInfo[varIdx] = std::make_tuple(input.strings.get_string(key.first), key.second, "Spotter");
217219
}
218220

219221
// Check Unavailable (Priority 0)
@@ -235,23 +237,25 @@ std::vector<std::string> formatHumanDiagnostic(
235237
if (info.type.find("Busy") != std::string::npos) priority = 1;
236238
else if (info.type.find("Rest") != std::string::npos) priority = 2;
237239
else if (info.type.find("Fair Share") != std::string::npos) priority = 2;
240+
241+
std::string infoMemberName = input.strings.get_string(info.memberNameId);
238242

239243
if (info.stintIndex >= 0) {
240244
std::string reason;
241-
if (priority == 1) reason = "Max Busy Time exceeded (" + info.memberName + ")";
245+
if (priority == 1) reason = "Max Busy Time exceeded (" + infoMemberName + ")";
242246
else if (priority == 2) {
243-
if (info.type.find("Fair Share") != std::string::npos) reason = "Fair Share Rules violated (" + info.memberName + ")";
244-
else reason = "Rest Rules violated (" + info.memberName + ")";
247+
if (info.type.find("Fair Share") != std::string::npos) reason = "Fair Share Rules violated (" + infoMemberName + ")";
248+
else reason = "Rest Rules violated (" + infoMemberName + ")";
245249
}
246-
else reason = info.type + " (" + info.memberName + ")";
250+
else reason = info.type + " (" + infoMemberName + ")";
247251

248-
groupedViolations[{priority, reason, info.memberName}].push_back(info.stintIndex);
252+
groupedViolations[{priority, reason, infoMemberName}].push_back(info.stintIndex);
249253
} else {
250254
// Try to attribute global violation to stints
251255
bool assignedAny = false;
252256
for(const auto& [vIdx, tupleInfo] : varToInfo) {
253257
auto [memName, stintIdx, role] = tupleInfo;
254-
if (memName == info.memberName && colValues[vIdx] > 0.5) {
258+
if (memName == infoMemberName && colValues[vIdx] > 0.5) {
255259
std::string reason;
256260
if (priority == 1) reason = "Max Busy Time exceeded (" + memName + ")";
257261
else if (priority == 2) {
@@ -273,7 +277,7 @@ std::vector<std::string> formatHumanDiagnostic(
273277
else
274278
reason = "Rest Rules violated";
275279
}
276-
globalViolations.push_back(reason + " (" + info.memberName + ")");
280+
globalViolations.push_back(reason + " (" + infoMemberName + ")");
277281
}
278282
}
279283
}
@@ -314,4 +318,4 @@ std::vector<std::string> formatHumanDiagnostic(
314318
return report;
315319
}
316320

317-
} // namespace jres::analysis
321+
} // namespace jres::analysis

src/analysis/solver_diagnostics.hpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ namespace jres::analysis {
2727
*/
2828
std::string explain_assignment_failure(
2929
int stintIndex,
30-
const std::string& violationDriver,
30+
jres::internal::ID violationDriverId,
3131
const jres::internal::SolverInput& input,
3232
const std::vector<jres::internal::TeamMember>& driverPool,
33-
const std::map<std::pair<std::string, int>, int>& driverWorkVars,
33+
const std::map<std::pair<jres::internal::ID, int>, int>& driverWorkVars,
3434
const std::vector<double>& colValues
3535
);
3636

@@ -40,8 +40,8 @@ std::string explain_assignment_failure(
4040
std::vector<std::string> formatHumanDiagnostic(
4141
const std::map<int, jres::internal::SlackInfo>& slackInfo,
4242
const std::set<int>& unavailableVars,
43-
const std::map<std::pair<std::string, int>, int>& driverWorkVars,
44-
const std::map<std::pair<std::string, int>, int>& spotterWorkVars,
43+
const std::map<std::pair<jres::internal::ID, int>, int>& driverWorkVars,
44+
const std::map<std::pair<jres::internal::ID, int>, int>& spotterWorkVars,
4545
const std::vector<double>& colValues,
4646
const jres::internal::SolverInput& input,
4747
const std::vector<jres::internal::TeamMember>& driverPool,

0 commit comments

Comments
 (0)