solverforge_solver/heuristic/move/
list_reverse.rs1use std::fmt::Debug;
11use std::marker::PhantomData;
12
13use solverforge_core::domain::PlanningSolution;
14use solverforge_scoring::ScoreDirector;
15
16use super::Move;
17
18pub struct ListReverseMove<S, V> {
56 entity_index: usize,
58 start: usize,
60 end: usize,
62 list_len: fn(&S, usize) -> usize,
64 list_reverse: fn(&mut S, usize, usize, usize),
66 variable_name: &'static str,
67 descriptor_index: usize,
68 _phantom: PhantomData<V>,
69}
70
71impl<S, V> Clone for ListReverseMove<S, V> {
72 fn clone(&self) -> Self {
73 *self
74 }
75}
76
77impl<S, V> Copy for ListReverseMove<S, V> {}
78
79impl<S, V: Debug> Debug for ListReverseMove<S, V> {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 f.debug_struct("ListReverseMove")
82 .field("entity", &self.entity_index)
83 .field("range", &(self.start..self.end))
84 .field("variable_name", &self.variable_name)
85 .finish()
86 }
87}
88
89impl<S, V> ListReverseMove<S, V> {
90 #[allow(clippy::too_many_arguments)]
101 pub fn new(
102 entity_index: usize,
103 start: usize,
104 end: usize,
105 list_len: fn(&S, usize) -> usize,
106 list_reverse: fn(&mut S, usize, usize, usize),
107 variable_name: &'static str,
108 descriptor_index: usize,
109 ) -> Self {
110 Self {
111 entity_index,
112 start,
113 end,
114 list_len,
115 list_reverse,
116 variable_name,
117 descriptor_index,
118 _phantom: PhantomData,
119 }
120 }
121
122 pub fn entity_index(&self) -> usize {
124 self.entity_index
125 }
126
127 pub fn start(&self) -> usize {
129 self.start
130 }
131
132 pub fn end(&self) -> usize {
134 self.end
135 }
136
137 pub fn segment_len(&self) -> usize {
139 self.end.saturating_sub(self.start)
140 }
141}
142
143impl<S, V> Move<S> for ListReverseMove<S, V>
144where
145 S: PlanningSolution,
146 V: Clone + Send + Sync + Debug + 'static,
147{
148 fn is_doable<D: ScoreDirector<S>>(&self, score_director: &D) -> bool {
149 let solution = score_director.working_solution();
150
151 if self.end <= self.start + 1 {
153 return false;
154 }
155
156 let len = (self.list_len)(solution, self.entity_index);
158 if self.end > len {
159 return false;
160 }
161
162 true
163 }
164
165 fn do_move<D: ScoreDirector<S>>(&self, score_director: &mut D) {
166 score_director.before_variable_changed(
168 self.descriptor_index,
169 self.entity_index,
170 self.variable_name,
171 );
172
173 (self.list_reverse)(
175 score_director.working_solution_mut(),
176 self.entity_index,
177 self.start,
178 self.end,
179 );
180
181 score_director.after_variable_changed(
183 self.descriptor_index,
184 self.entity_index,
185 self.variable_name,
186 );
187
188 let list_reverse = self.list_reverse;
190 let entity = self.entity_index;
191 let start = self.start;
192 let end = self.end;
193
194 score_director.register_undo(Box::new(move |s: &mut S| {
195 list_reverse(s, entity, start, end);
196 }));
197 }
198
199 fn descriptor_index(&self) -> usize {
200 self.descriptor_index
201 }
202
203 fn entity_indices(&self) -> &[usize] {
204 std::slice::from_ref(&self.entity_index)
205 }
206
207 fn variable_name(&self) -> &str {
208 self.variable_name
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215 use solverforge_core::domain::{EntityDescriptor, SolutionDescriptor, TypedEntityExtractor};
216 use solverforge_core::score::SimpleScore;
217 use solverforge_scoring::{RecordingScoreDirector, SimpleScoreDirector};
218 use std::any::TypeId;
219
220 #[derive(Clone, Debug)]
221 struct Tour {
222 cities: Vec<i32>,
223 }
224
225 #[derive(Clone, Debug)]
226 struct TspSolution {
227 tours: Vec<Tour>,
228 score: Option<SimpleScore>,
229 }
230
231 impl PlanningSolution for TspSolution {
232 type Score = SimpleScore;
233 fn score(&self) -> Option<Self::Score> {
234 self.score
235 }
236 fn set_score(&mut self, score: Option<Self::Score>) {
237 self.score = score;
238 }
239 }
240
241 fn get_tours(s: &TspSolution) -> &Vec<Tour> {
242 &s.tours
243 }
244 fn get_tours_mut(s: &mut TspSolution) -> &mut Vec<Tour> {
245 &mut s.tours
246 }
247
248 fn list_len(s: &TspSolution, entity_idx: usize) -> usize {
249 s.tours.get(entity_idx).map_or(0, |t| t.cities.len())
250 }
251 fn list_reverse(s: &mut TspSolution, entity_idx: usize, start: usize, end: usize) {
252 if let Some(t) = s.tours.get_mut(entity_idx) {
253 t.cities[start..end].reverse();
254 }
255 }
256
257 fn create_director(
258 tours: Vec<Tour>,
259 ) -> SimpleScoreDirector<TspSolution, impl Fn(&TspSolution) -> SimpleScore> {
260 let solution = TspSolution { tours, score: None };
261 let extractor = Box::new(TypedEntityExtractor::new(
262 "Tour",
263 "tours",
264 get_tours,
265 get_tours_mut,
266 ));
267 let entity_desc =
268 EntityDescriptor::new("Tour", TypeId::of::<Tour>(), "tours").with_extractor(extractor);
269 let descriptor = SolutionDescriptor::new("TspSolution", TypeId::of::<TspSolution>())
270 .with_entity(entity_desc);
271 SimpleScoreDirector::with_calculator(solution, descriptor, |_| SimpleScore::of(0))
272 }
273
274 #[test]
275 fn reverse_segment() {
276 let tours = vec![Tour {
277 cities: vec![1, 2, 3, 4, 5],
278 }];
279 let mut director = create_director(tours);
280
281 let m =
283 ListReverseMove::<TspSolution, i32>::new(0, 1, 4, list_len, list_reverse, "cities", 0);
284
285 assert!(m.is_doable(&director));
286
287 {
288 let mut recording = RecordingScoreDirector::new(&mut director);
289 m.do_move(&mut recording);
290
291 let cities = &recording.working_solution().tours[0].cities;
292 assert_eq!(cities, &[1, 4, 3, 2, 5]);
293
294 recording.undo_changes();
295 }
296
297 let cities = &director.working_solution().tours[0].cities;
298 assert_eq!(cities, &[1, 2, 3, 4, 5]);
299 }
300
301 #[test]
302 fn reverse_entire_list() {
303 let tours = vec![Tour {
304 cities: vec![1, 2, 3, 4],
305 }];
306 let mut director = create_director(tours);
307
308 let m =
309 ListReverseMove::<TspSolution, i32>::new(0, 0, 4, list_len, list_reverse, "cities", 0);
310
311 assert!(m.is_doable(&director));
312
313 {
314 let mut recording = RecordingScoreDirector::new(&mut director);
315 m.do_move(&mut recording);
316
317 let cities = &recording.working_solution().tours[0].cities;
318 assert_eq!(cities, &[4, 3, 2, 1]);
319
320 recording.undo_changes();
321 }
322
323 let cities = &director.working_solution().tours[0].cities;
324 assert_eq!(cities, &[1, 2, 3, 4]);
325 }
326
327 #[test]
328 fn single_element_not_doable() {
329 let tours = vec![Tour {
330 cities: vec![1, 2, 3],
331 }];
332 let director = create_director(tours);
333
334 let m =
336 ListReverseMove::<TspSolution, i32>::new(0, 1, 2, list_len, list_reverse, "cities", 0);
337
338 assert!(!m.is_doable(&director));
339 }
340
341 #[test]
342 fn out_of_bounds_not_doable() {
343 let tours = vec![Tour {
344 cities: vec![1, 2, 3],
345 }];
346 let director = create_director(tours);
347
348 let m =
349 ListReverseMove::<TspSolution, i32>::new(0, 1, 10, list_len, list_reverse, "cities", 0);
350
351 assert!(!m.is_doable(&director));
352 }
353}