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