solverforge_solver/heuristic/move/
sublist_change.rs1use std::fmt::Debug;
12use std::marker::PhantomData;
13
14use solverforge_core::domain::PlanningSolution;
15use solverforge_scoring::Director;
16
17use super::Move;
18
19pub struct SubListChangeMove<S, V> {
72 source_entity_index: usize,
74 source_start: usize,
76 source_end: usize,
78 dest_entity_index: usize,
80 dest_position: usize,
82 list_len: fn(&S, usize) -> usize,
83 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
85 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
87 variable_name: &'static str,
88 descriptor_index: usize,
89 indices: [usize; 2],
91 _phantom: PhantomData<fn() -> V>,
92}
93
94impl<S, V> Clone for SubListChangeMove<S, V> {
95 fn clone(&self) -> Self {
96 *self
97 }
98}
99
100impl<S, V> Copy for SubListChangeMove<S, V> {}
101
102impl<S, V: Debug> Debug for SubListChangeMove<S, V> {
103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104 f.debug_struct("SubListChangeMove")
105 .field("source_entity", &self.source_entity_index)
106 .field("source_range", &(self.source_start..self.source_end))
107 .field("dest_entity", &self.dest_entity_index)
108 .field("dest_position", &self.dest_position)
109 .field("variable_name", &self.variable_name)
110 .finish()
111 }
112}
113
114impl<S, V> SubListChangeMove<S, V> {
115 #[allow(clippy::too_many_arguments)]
130 pub fn new(
131 source_entity_index: usize,
132 source_start: usize,
133 source_end: usize,
134 dest_entity_index: usize,
135 dest_position: usize,
136 list_len: fn(&S, usize) -> usize,
137 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
138 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
139 variable_name: &'static str,
140 descriptor_index: usize,
141 ) -> Self {
142 Self {
143 source_entity_index,
144 source_start,
145 source_end,
146 dest_entity_index,
147 dest_position,
148 list_len,
149 sublist_remove,
150 sublist_insert,
151 variable_name,
152 descriptor_index,
153 indices: [source_entity_index, dest_entity_index],
154 _phantom: PhantomData,
155 }
156 }
157
158 pub fn source_entity_index(&self) -> usize {
159 self.source_entity_index
160 }
161
162 pub fn source_start(&self) -> usize {
163 self.source_start
164 }
165
166 pub fn source_end(&self) -> usize {
167 self.source_end
168 }
169
170 pub fn sublist_len(&self) -> usize {
171 self.source_end.saturating_sub(self.source_start)
172 }
173
174 pub fn dest_entity_index(&self) -> usize {
175 self.dest_entity_index
176 }
177
178 pub fn dest_position(&self) -> usize {
179 self.dest_position
180 }
181
182 pub fn is_intra_list(&self) -> bool {
183 self.source_entity_index == self.dest_entity_index
184 }
185}
186
187impl<S, V> Move<S> for SubListChangeMove<S, V>
188where
189 S: PlanningSolution,
190 V: Clone + Send + Sync + Debug + 'static,
191{
192 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
193 let solution = score_director.working_solution();
194
195 if self.source_start >= self.source_end {
197 return false;
198 }
199
200 let source_len = (self.list_len)(solution, self.source_entity_index);
202 if self.source_end > source_len {
203 return false;
204 }
205
206 let dest_len = (self.list_len)(solution, self.dest_entity_index);
208 let sublist_len = self.sublist_len();
209
210 let max_dest = if self.is_intra_list() {
211 source_len - sublist_len
213 } else {
214 dest_len
215 };
216
217 if self.dest_position > max_dest {
218 return false;
219 }
220
221 if self.is_intra_list() {
223 if self.dest_position >= self.source_start && self.dest_position <= self.source_end {
225 return false;
226 }
227 }
228
229 true
230 }
231
232 fn do_move<D: Director<S>>(&self, score_director: &mut D) {
233 score_director.before_variable_changed(self.descriptor_index, self.source_entity_index);
235 if !self.is_intra_list() {
236 score_director.before_variable_changed(self.descriptor_index, self.dest_entity_index);
237 }
238
239 let elements = (self.sublist_remove)(
241 score_director.working_solution_mut(),
242 self.source_entity_index,
243 self.source_start,
244 self.source_end,
245 );
246
247 let dest_pos = self.dest_position;
249
250 (self.sublist_insert)(
252 score_director.working_solution_mut(),
253 self.dest_entity_index,
254 dest_pos,
255 elements,
256 );
257
258 score_director.after_variable_changed(self.descriptor_index, self.source_entity_index);
260 if !self.is_intra_list() {
261 score_director.after_variable_changed(self.descriptor_index, self.dest_entity_index);
262 }
263
264 let sublist_remove = self.sublist_remove;
266 let sublist_insert = self.sublist_insert;
267 let src_entity = self.source_entity_index;
268 let src_start = self.source_start;
269 let dest_entity = self.dest_entity_index;
270 let sublist_len = self.sublist_len();
271
272 score_director.register_undo(Box::new(move |s: &mut S| {
273 let removed = sublist_remove(s, dest_entity, dest_pos, dest_pos + sublist_len);
275 sublist_insert(s, src_entity, src_start, removed);
277 }));
278 }
279
280 fn descriptor_index(&self) -> usize {
281 self.descriptor_index
282 }
283
284 fn entity_indices(&self) -> &[usize] {
285 if self.is_intra_list() {
286 &self.indices[0..1]
287 } else {
288 &self.indices
289 }
290 }
291
292 fn variable_name(&self) -> &str {
293 self.variable_name
294 }
295}
296
297#[cfg(test)]
298#[path = "sublist_change_tests.rs"]
299mod tests;