comparison rust/hg-core/src/utils.rs @ 46586:435d9fc72646

copies-rust: extract generic map merge logic from merge_copies_dict This deduplicates the copy-tracing-specific logic Differential Revision: https://phab.mercurial-scm.org/D9682
author Simon Sapin <simon.sapin@octobus.net>
date Wed, 23 Dec 2020 11:48:16 +0100
parents a25033eb43b5
children 755c31a1caf9
comparison
equal deleted inserted replaced
46585:60b2b7ecf9cb 46586:435d9fc72646
7 7
8 //! Contains useful functions, traits, structs, etc. for use in core. 8 //! Contains useful functions, traits, structs, etc. for use in core.
9 9
10 use crate::errors::{HgError, IoErrorContext}; 10 use crate::errors::{HgError, IoErrorContext};
11 use crate::utils::hg_path::HgPath; 11 use crate::utils::hg_path::HgPath;
12 use im_rc::ordmap::DiffItem;
13 use im_rc::ordmap::OrdMap;
12 use std::{io::Write, ops::Deref}; 14 use std::{io::Write, ops::Deref};
13 15
14 pub mod files; 16 pub mod files;
15 pub mod hg_path; 17 pub mod hg_path;
16 pub mod path_auditor; 18 pub mod path_auditor;
197 std::env::current_exe().map_err(|error| HgError::IoError { 199 std::env::current_exe().map_err(|error| HgError::IoError {
198 error, 200 error,
199 context: IoErrorContext::CurrentExe, 201 context: IoErrorContext::CurrentExe,
200 }) 202 })
201 } 203 }
204
205 pub(crate) enum MergeResult<V> {
206 UseLeftValue,
207 UseRightValue,
208 UseNewValue(V),
209 }
210
211 /// Return the union of the two given maps,
212 /// calling `merge(key, left_value, right_value)` to resolve keys that exist in
213 /// both.
214 ///
215 /// CC https://github.com/bodil/im-rs/issues/166
216 pub(crate) fn ordmap_union_with_merge<K, V>(
217 left: OrdMap<K, V>,
218 right: OrdMap<K, V>,
219 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
220 ) -> OrdMap<K, V>
221 where
222 K: Clone + Ord,
223 V: Clone + PartialEq,
224 {
225 if left.ptr_eq(&right) {
226 // One of the two maps is an unmodified clone of the other
227 left
228 } else if left.len() / 2 > right.len() {
229 // When two maps have different sizes,
230 // their size difference is a lower bound on
231 // how many keys of the larger map are not also in the smaller map.
232 // This in turn is a lower bound on the number of differences in
233 // `OrdMap::diff` and the "amount of work" that would be done
234 // by `ordmap_union_with_merge_by_diff`.
235 //
236 // Here `left` is more than twice the size of `right`,
237 // so the number of differences is more than the total size of
238 // `right`. Therefore an algorithm based on iterating `right`
239 // is more efficient.
240 //
241 // This helps a lot when a tiny (or empty) map is merged
242 // with a large one.
243 ordmap_union_with_merge_by_iter(left, right, merge)
244 } else if left.len() < right.len() / 2 {
245 // Same as above but with `left` and `right` swapped
246 ordmap_union_with_merge_by_iter(right, left, |key, a, b| {
247 // Also swapped in `merge` arguments:
248 match merge(key, b, a) {
249 MergeResult::UseNewValue(v) => MergeResult::UseNewValue(v),
250 // … and swap back in `merge` result:
251 MergeResult::UseLeftValue => MergeResult::UseRightValue,
252 MergeResult::UseRightValue => MergeResult::UseLeftValue,
253 }
254 })
255 } else {
256 // For maps of similar size, use the algorithm based on `OrdMap::diff`
257 ordmap_union_with_merge_by_diff(left, right, merge)
258 }
259 }
260
261 /// Efficient if `right` is much smaller than `left`
262 fn ordmap_union_with_merge_by_iter<K, V>(
263 mut left: OrdMap<K, V>,
264 right: OrdMap<K, V>,
265 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
266 ) -> OrdMap<K, V>
267 where
268 K: Clone + Ord,
269 V: Clone,
270 {
271 for (key, right_value) in right {
272 match left.get(&key) {
273 None => {
274 left.insert(key, right_value);
275 }
276 Some(left_value) => match merge(&key, left_value, &right_value) {
277 MergeResult::UseLeftValue => {}
278 MergeResult::UseRightValue => {
279 left.insert(key, right_value);
280 }
281 MergeResult::UseNewValue(new_value) => {
282 left.insert(key, new_value);
283 }
284 },
285 }
286 }
287 left
288 }
289
290 /// Fallback when both maps are of similar size
291 fn ordmap_union_with_merge_by_diff<K, V>(
292 mut left: OrdMap<K, V>,
293 mut right: OrdMap<K, V>,
294 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
295 ) -> OrdMap<K, V>
296 where
297 K: Clone + Ord,
298 V: Clone + PartialEq,
299 {
300 // (key, value) pairs that would need to be inserted in either map
301 // in order to turn it into the union.
302 //
303 // TODO: if/when https://github.com/bodil/im-rs/pull/168 is accepted,
304 // change these from `Vec<(K, V)>` to `Vec<(&K, Cow<V>)>`
305 // with `left_updates` only borrowing from `right` and `right_updates` from
306 // `left`, and with `Cow::Owned` used for `MergeResult::UseNewValue`.
307 //
308 // This would allow moving all `.clone()` calls to after we’ve decided
309 // which of `right_updates` or `left_updates` to use
310 // (value ones becoming `Cow::into_owned`),
311 // and avoid making clones we don’t end up using.
312 let mut left_updates = Vec::new();
313 let mut right_updates = Vec::new();
314
315 for difference in left.diff(&right) {
316 match difference {
317 DiffItem::Add(key, value) => {
318 left_updates.push((key.clone(), value.clone()))
319 }
320 DiffItem::Remove(key, value) => {
321 right_updates.push((key.clone(), value.clone()))
322 }
323 DiffItem::Update {
324 old: (key, left_value),
325 new: (_, right_value),
326 } => match merge(key, left_value, right_value) {
327 MergeResult::UseLeftValue => {
328 right_updates.push((key.clone(), left_value.clone()))
329 }
330 MergeResult::UseRightValue => {
331 left_updates.push((key.clone(), right_value.clone()))
332 }
333 MergeResult::UseNewValue(new_value) => {
334 left_updates.push((key.clone(), new_value.clone()));
335 right_updates.push((key.clone(), new_value))
336 }
337 },
338 }
339 }
340 if left_updates.len() < right_updates.len() {
341 for (key, value) in left_updates {
342 left.insert(key, value);
343 }
344 left
345 } else {
346 for (key, value) in right_updates {
347 right.insert(key, value);
348 }
349 right
350 }
351 }