solverforge_solver/heuristic/move/
list_swap.rs1use std::fmt::Debug;
11
12use solverforge_core::domain::PlanningSolution;
13use solverforge_scoring::ScoreDirector;
14
15use super::Move;
16
17pub struct ListSwapMove<S, V> {
65 first_entity_index: usize,
67 first_position: usize,
69 second_entity_index: usize,
71 second_position: usize,
73 list_len: fn(&S, usize) -> usize,
75 list_get: fn(&S, usize, usize) -> Option<V>,
77 list_set: fn(&mut S, usize, usize, V),
79 variable_name: &'static str,
80 descriptor_index: usize,
81 indices: [usize; 2],
83}
84
85impl<S, V> Clone for ListSwapMove<S, V> {
86 fn clone(&self) -> Self {
87 *self
88 }
89}
90
91impl<S, V> Copy for ListSwapMove<S, V> {}
92
93impl<S, V: Debug> Debug for ListSwapMove<S, V> {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 f.debug_struct("ListSwapMove")
96 .field("first_entity", &self.first_entity_index)
97 .field("first_position", &self.first_position)
98 .field("second_entity", &self.second_entity_index)
99 .field("second_position", &self.second_position)
100 .field("variable_name", &self.variable_name)
101 .finish()
102 }
103}
104
105impl<S, V> ListSwapMove<S, V> {
106 #[allow(clippy::too_many_arguments)]
119 pub fn new(
120 first_entity_index: usize,
121 first_position: usize,
122 second_entity_index: usize,
123 second_position: usize,
124 list_len: fn(&S, usize) -> usize,
125 list_get: fn(&S, usize, usize) -> Option<V>,
126 list_set: fn(&mut S, usize, usize, V),
127 variable_name: &'static str,
128 descriptor_index: usize,
129 ) -> Self {
130 Self {
131 first_entity_index,
132 first_position,
133 second_entity_index,
134 second_position,
135 list_len,
136 list_get,
137 list_set,
138 variable_name,
139 descriptor_index,
140 indices: [first_entity_index, second_entity_index],
141 }
142 }
143
144 pub fn first_entity_index(&self) -> usize {
146 self.first_entity_index
147 }
148
149 pub fn first_position(&self) -> usize {
151 self.first_position
152 }
153
154 pub fn second_entity_index(&self) -> usize {
156 self.second_entity_index
157 }
158
159 pub fn second_position(&self) -> usize {
161 self.second_position
162 }
163
164 pub fn is_intra_list(&self) -> bool {
166 self.first_entity_index == self.second_entity_index
167 }
168}
169
170impl<S, V> Move<S> for ListSwapMove<S, V>
171where
172 S: PlanningSolution,
173 V: Clone + PartialEq + Send + Sync + Debug + 'static,
174{
175 fn is_doable<D: ScoreDirector<S>>(&self, score_director: &D) -> bool {
176 let solution = score_director.working_solution();
177
178 let first_len = (self.list_len)(solution, self.first_entity_index);
180 if self.first_position >= first_len {
181 return false;
182 }
183
184 let second_len = (self.list_len)(solution, self.second_entity_index);
186 if self.second_position >= second_len {
187 return false;
188 }
189
190 if self.is_intra_list() && self.first_position == self.second_position {
192 return false;
193 }
194
195 let first_val = (self.list_get)(solution, self.first_entity_index, self.first_position);
197 let second_val = (self.list_get)(solution, self.second_entity_index, self.second_position);
198
199 first_val != second_val
200 }
201
202 fn do_move<D: ScoreDirector<S>>(&self, score_director: &mut D) {
203 let first_val = (self.list_get)(
205 score_director.working_solution(),
206 self.first_entity_index,
207 self.first_position,
208 )
209 .expect("first position should be valid");
210
211 let second_val = (self.list_get)(
212 score_director.working_solution(),
213 self.second_entity_index,
214 self.second_position,
215 )
216 .expect("second position should be valid");
217
218 score_director.before_variable_changed(
220 self.descriptor_index,
221 self.first_entity_index,
222 self.variable_name,
223 );
224 if !self.is_intra_list() {
225 score_director.before_variable_changed(
226 self.descriptor_index,
227 self.second_entity_index,
228 self.variable_name,
229 );
230 }
231
232 (self.list_set)(
234 score_director.working_solution_mut(),
235 self.first_entity_index,
236 self.first_position,
237 second_val.clone(),
238 );
239 (self.list_set)(
240 score_director.working_solution_mut(),
241 self.second_entity_index,
242 self.second_position,
243 first_val.clone(),
244 );
245
246 score_director.after_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.after_variable_changed(
254 self.descriptor_index,
255 self.second_entity_index,
256 self.variable_name,
257 );
258 }
259
260 let list_set = self.list_set;
262 let first_entity = self.first_entity_index;
263 let first_pos = self.first_position;
264 let second_entity = self.second_entity_index;
265 let second_pos = self.second_position;
266
267 score_director.register_undo(Box::new(move |s: &mut S| {
268 list_set(s, first_entity, first_pos, first_val);
270 list_set(s, second_entity, second_pos, second_val);
271 }));
272 }
273
274 fn descriptor_index(&self) -> usize {
275 self.descriptor_index
276 }
277
278 fn entity_indices(&self) -> &[usize] {
279 if self.is_intra_list() {
280 &self.indices[0..1]
281 } else {
282 &self.indices
283 }
284 }
285
286 fn variable_name(&self) -> &str {
287 self.variable_name
288 }
289}