1use 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)]
71pub struct SubListSwapMove<S, V> {
72 first_entity_index: usize,
74 first_start: usize,
76 first_end: usize,
78 second_entity_index: usize,
80 second_start: usize,
82 second_end: usize,
84 list_len: fn(&S, usize) -> usize,
86 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
88 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
90 variable_name: &'static str,
91 descriptor_index: usize,
92 indices: [usize; 2],
94 _phantom: PhantomData<V>,
95}
96
97impl<S, V: Debug> Debug for SubListSwapMove<S, V> {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 f.debug_struct("SubListSwapMove")
100 .field("first_entity", &self.first_entity_index)
101 .field("first_range", &(self.first_start..self.first_end))
102 .field("second_entity", &self.second_entity_index)
103 .field("second_range", &(self.second_start..self.second_end))
104 .field("variable_name", &self.variable_name)
105 .finish()
106 }
107}
108
109impl<S, V> SubListSwapMove<S, V> {
110 #[allow(clippy::too_many_arguments)]
125 pub fn new(
126 first_entity_index: usize,
127 first_start: usize,
128 first_end: usize,
129 second_entity_index: usize,
130 second_start: usize,
131 second_end: usize,
132 list_len: fn(&S, usize) -> usize,
133 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
134 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
135 variable_name: &'static str,
136 descriptor_index: usize,
137 ) -> Self {
138 Self {
139 first_entity_index,
140 first_start,
141 first_end,
142 second_entity_index,
143 second_start,
144 second_end,
145 list_len,
146 sublist_remove,
147 sublist_insert,
148 variable_name,
149 descriptor_index,
150 indices: [first_entity_index, second_entity_index],
151 _phantom: PhantomData,
152 }
153 }
154
155 pub fn first_entity_index(&self) -> usize {
157 self.first_entity_index
158 }
159
160 pub fn first_start(&self) -> usize {
162 self.first_start
163 }
164
165 pub fn first_end(&self) -> usize {
167 self.first_end
168 }
169
170 pub fn first_len(&self) -> usize {
172 self.first_end.saturating_sub(self.first_start)
173 }
174
175 pub fn second_entity_index(&self) -> usize {
177 self.second_entity_index
178 }
179
180 pub fn second_start(&self) -> usize {
182 self.second_start
183 }
184
185 pub fn second_end(&self) -> usize {
187 self.second_end
188 }
189
190 pub fn second_len(&self) -> usize {
192 self.second_end.saturating_sub(self.second_start)
193 }
194
195 pub fn is_intra_list(&self) -> bool {
197 self.first_entity_index == self.second_entity_index
198 }
199}
200
201impl<S, V> Move<S> for SubListSwapMove<S, V>
202where
203 S: PlanningSolution,
204 V: Clone + Send + Sync + Debug + 'static,
205{
206 fn is_doable(&self, score_director: &dyn ScoreDirector<S>) -> bool {
207 let solution = score_director.working_solution();
208
209 if self.first_start >= self.first_end || self.second_start >= self.second_end {
211 return false;
212 }
213
214 let first_list_len = (self.list_len)(solution, self.first_entity_index);
216 if self.first_end > first_list_len {
217 return false;
218 }
219
220 let second_list_len = (self.list_len)(solution, self.second_entity_index);
222 if self.second_end > second_list_len {
223 return false;
224 }
225
226 if self.is_intra_list() {
228 let overlaps = self.first_start < self.second_end && self.second_start < self.first_end;
230 if overlaps {
231 return false;
232 }
233 }
234
235 true
236 }
237
238 fn do_move(&self, score_director: &mut dyn ScoreDirector<S>) {
239 score_director.before_variable_changed(
241 self.descriptor_index,
242 self.first_entity_index,
243 self.variable_name,
244 );
245 if !self.is_intra_list() {
246 score_director.before_variable_changed(
247 self.descriptor_index,
248 self.second_entity_index,
249 self.variable_name,
250 );
251 }
252
253 if self.is_intra_list() {
254 let (early_start, early_end, late_start, late_end) =
257 if self.first_start < self.second_start {
258 (
259 self.first_start,
260 self.first_end,
261 self.second_start,
262 self.second_end,
263 )
264 } else {
265 (
266 self.second_start,
267 self.second_end,
268 self.first_start,
269 self.first_end,
270 )
271 };
272
273 let late_elements = (self.sublist_remove)(
275 score_director.working_solution_mut(),
276 self.first_entity_index,
277 late_start,
278 late_end,
279 );
280
281 let early_elements = (self.sublist_remove)(
283 score_director.working_solution_mut(),
284 self.first_entity_index,
285 early_start,
286 early_end,
287 );
288
289 (self.sublist_insert)(
291 score_director.working_solution_mut(),
292 self.first_entity_index,
293 early_start,
294 late_elements.clone(),
295 );
296
297 let late_len = late_end - late_start;
301 let early_len = early_end - early_start;
302 let new_late_pos = late_start - early_len + late_len;
303 (self.sublist_insert)(
304 score_director.working_solution_mut(),
305 self.first_entity_index,
306 new_late_pos,
307 early_elements.clone(),
308 );
309
310 let sublist_remove = self.sublist_remove;
312 let sublist_insert = self.sublist_insert;
313 let entity = self.first_entity_index;
314
315 score_director.register_undo(Box::new(move |s: &mut S| {
316 let late_at_early = sublist_remove(s, entity, early_start, early_start + late_len);
318 let early_at_late = sublist_remove(
320 s,
321 entity,
322 new_late_pos - late_len,
323 new_late_pos - late_len + early_len,
324 );
325 sublist_insert(s, entity, early_start, early_at_late);
327 sublist_insert(s, entity, late_start, late_at_early);
329 }));
330 } else {
331 let first_elements = (self.sublist_remove)(
333 score_director.working_solution_mut(),
334 self.first_entity_index,
335 self.first_start,
336 self.first_end,
337 );
338
339 let second_elements = (self.sublist_remove)(
340 score_director.working_solution_mut(),
341 self.second_entity_index,
342 self.second_start,
343 self.second_end,
344 );
345
346 (self.sublist_insert)(
348 score_director.working_solution_mut(),
349 self.first_entity_index,
350 self.first_start,
351 second_elements.clone(),
352 );
353
354 (self.sublist_insert)(
355 score_director.working_solution_mut(),
356 self.second_entity_index,
357 self.second_start,
358 first_elements.clone(),
359 );
360
361 let sublist_remove = self.sublist_remove;
363 let sublist_insert = self.sublist_insert;
364 let first_entity = self.first_entity_index;
365 let first_start = self.first_start;
366 let second_entity = self.second_entity_index;
367 let second_start = self.second_start;
368 let first_len = self.first_len();
369 let second_len = self.second_len();
370
371 score_director.register_undo(Box::new(move |s: &mut S| {
372 let second_at_first =
374 sublist_remove(s, first_entity, first_start, first_start + second_len);
375 let first_at_second =
377 sublist_remove(s, second_entity, second_start, second_start + first_len);
378 sublist_insert(s, first_entity, first_start, first_at_second);
380 sublist_insert(s, second_entity, second_start, second_at_first);
381 }));
382 }
383
384 score_director.after_variable_changed(
386 self.descriptor_index,
387 self.first_entity_index,
388 self.variable_name,
389 );
390 if !self.is_intra_list() {
391 score_director.after_variable_changed(
392 self.descriptor_index,
393 self.second_entity_index,
394 self.variable_name,
395 );
396 }
397 }
398
399 fn descriptor_index(&self) -> usize {
400 self.descriptor_index
401 }
402
403 fn entity_indices(&self) -> &[usize] {
404 if self.is_intra_list() {
405 &self.indices[0..1]
406 } else {
407 &self.indices
408 }
409 }
410
411 fn variable_name(&self) -> &str {
412 self.variable_name
413 }
414}
415
416#[cfg(test)]
417mod tests {
418 use super::*;
419 use solverforge_core::domain::{EntityDescriptor, SolutionDescriptor, TypedEntityExtractor};
420 use solverforge_core::score::SimpleScore;
421 use solverforge_scoring::{RecordingScoreDirector, SimpleScoreDirector};
422 use std::any::TypeId;
423
424 #[derive(Clone, Debug)]
425 struct Vehicle {
426 visits: Vec<i32>,
427 }
428
429 #[derive(Clone, Debug)]
430 struct RoutingSolution {
431 vehicles: Vec<Vehicle>,
432 score: Option<SimpleScore>,
433 }
434
435 impl PlanningSolution for RoutingSolution {
436 type Score = SimpleScore;
437 fn score(&self) -> Option<Self::Score> {
438 self.score
439 }
440 fn set_score(&mut self, score: Option<Self::Score>) {
441 self.score = score;
442 }
443 }
444
445 fn get_vehicles(s: &RoutingSolution) -> &Vec<Vehicle> {
446 &s.vehicles
447 }
448 fn get_vehicles_mut(s: &mut RoutingSolution) -> &mut Vec<Vehicle> {
449 &mut s.vehicles
450 }
451
452 fn list_len(s: &RoutingSolution, entity_idx: usize) -> usize {
453 s.vehicles.get(entity_idx).map_or(0, |v| v.visits.len())
454 }
455 fn sublist_remove(
456 s: &mut RoutingSolution,
457 entity_idx: usize,
458 start: usize,
459 end: usize,
460 ) -> Vec<i32> {
461 s.vehicles
462 .get_mut(entity_idx)
463 .map(|v| v.visits.drain(start..end).collect())
464 .unwrap_or_default()
465 }
466 fn sublist_insert(s: &mut RoutingSolution, entity_idx: usize, pos: usize, items: Vec<i32>) {
467 if let Some(v) = s.vehicles.get_mut(entity_idx) {
468 for (i, item) in items.into_iter().enumerate() {
469 v.visits.insert(pos + i, item);
470 }
471 }
472 }
473
474 fn create_director(
475 vehicles: Vec<Vehicle>,
476 ) -> SimpleScoreDirector<RoutingSolution, impl Fn(&RoutingSolution) -> SimpleScore> {
477 let solution = RoutingSolution {
478 vehicles,
479 score: None,
480 };
481 let extractor = Box::new(TypedEntityExtractor::new(
482 "Vehicle",
483 "vehicles",
484 get_vehicles,
485 get_vehicles_mut,
486 ));
487 let entity_desc = EntityDescriptor::new("Vehicle", TypeId::of::<Vehicle>(), "vehicles")
488 .with_extractor(extractor);
489 let descriptor =
490 SolutionDescriptor::new("RoutingSolution", TypeId::of::<RoutingSolution>())
491 .with_entity(entity_desc);
492 SimpleScoreDirector::with_calculator(solution, descriptor, |_| SimpleScore::of(0))
493 }
494
495 #[test]
496 fn inter_list_swap() {
497 let vehicles = vec![
498 Vehicle {
499 visits: vec![1, 2, 3, 4],
500 },
501 Vehicle {
502 visits: vec![10, 20, 30],
503 },
504 ];
505 let mut director = create_director(vehicles);
506
507 let m = SubListSwapMove::<RoutingSolution, i32>::new(
510 0,
511 1,
512 3,
513 1,
514 0,
515 2,
516 list_len,
517 sublist_remove,
518 sublist_insert,
519 "visits",
520 0,
521 );
522
523 assert!(m.is_doable(&director));
524
525 {
526 let mut recording = RecordingScoreDirector::new(&mut director);
527 m.do_move(&mut recording);
528
529 let sol = recording.working_solution();
530 assert_eq!(sol.vehicles[0].visits, vec![1, 10, 20, 4]);
531 assert_eq!(sol.vehicles[1].visits, vec![2, 3, 30]);
532
533 recording.undo_changes();
534 }
535
536 let sol = director.working_solution();
537 assert_eq!(sol.vehicles[0].visits, vec![1, 2, 3, 4]);
538 assert_eq!(sol.vehicles[1].visits, vec![10, 20, 30]);
539 }
540
541 #[test]
542 fn intra_list_swap() {
543 let vehicles = vec![Vehicle {
544 visits: vec![1, 2, 3, 4, 5, 6, 7, 8],
545 }];
546 let mut director = create_director(vehicles);
547
548 let m = SubListSwapMove::<RoutingSolution, i32>::new(
551 0,
552 1,
553 3,
554 0,
555 5,
556 7,
557 list_len,
558 sublist_remove,
559 sublist_insert,
560 "visits",
561 0,
562 );
563
564 assert!(m.is_doable(&director));
565
566 {
567 let mut recording = RecordingScoreDirector::new(&mut director);
568 m.do_move(&mut recording);
569
570 let visits = &recording.working_solution().vehicles[0].visits;
572 assert_eq!(visits, &[1, 6, 7, 4, 5, 2, 3, 8]);
573
574 recording.undo_changes();
575 }
576
577 let visits = &director.working_solution().vehicles[0].visits;
578 assert_eq!(visits, &[1, 2, 3, 4, 5, 6, 7, 8]);
579 }
580
581 #[test]
582 fn overlapping_ranges_not_doable() {
583 let vehicles = vec![Vehicle {
584 visits: vec![1, 2, 3, 4, 5],
585 }];
586 let director = create_director(vehicles);
587
588 let m = SubListSwapMove::<RoutingSolution, i32>::new(
590 0,
591 1,
592 4,
593 0,
594 2,
595 5,
596 list_len,
597 sublist_remove,
598 sublist_insert,
599 "visits",
600 0,
601 );
602
603 assert!(!m.is_doable(&director));
604 }
605
606 #[test]
607 fn empty_range_not_doable() {
608 let vehicles = vec![Vehicle {
609 visits: vec![1, 2, 3],
610 }];
611 let director = create_director(vehicles);
612
613 let m = SubListSwapMove::<RoutingSolution, i32>::new(
614 0,
615 1,
616 1,
617 0,
618 2,
619 3,
620 list_len,
621 sublist_remove,
622 sublist_insert,
623 "visits",
624 0,
625 );
626
627 assert!(!m.is_doable(&director));
628 }
629
630 #[test]
631 fn out_of_bounds_not_doable() {
632 let vehicles = vec![Vehicle {
633 visits: vec![1, 2, 3],
634 }];
635 let director = create_director(vehicles);
636
637 let m = SubListSwapMove::<RoutingSolution, i32>::new(
638 0,
639 0,
640 2,
641 0,
642 2,
643 10,
644 list_len,
645 sublist_remove,
646 sublist_insert,
647 "visits",
648 0,
649 );
650
651 assert!(!m.is_doable(&director));
652 }
653}