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