use alloc::vec::Vec;
use core::any::type_name;
use core::hash::Hash;
use core::ops::{Index, Range};
use crate::algorithms::DiffHook;
use crate::algorithms::utils::stable_hash;
use crate::deadline_support::{Instant, deadline_exceeded};
use crate::types::MapType;
const DISJOINT_FAST_PATH_MIN_LEN: usize = 512;
const DISJOINT_FAST_PATH_MIN_WORK: usize = 128 * 1024;
const DISJOINT_FAST_PATH_DEADLINE_CHECK_INTERVAL: usize = 1024;
pub(crate) fn maybe_emit_disjoint_fast_path<Old, New, D>(
d: &mut D,
old: &Old,
old_range: Range<usize>,
new: &New,
new_range: Range<usize>,
deadline: Option<Instant>,
) -> Result<bool, D::Error>
where
Old: Index<usize> + ?Sized,
New: Index<usize> + ?Sized,
D: DiffHook,
Old::Output: Hash + Eq,
New::Output: PartialEq<Old::Output> + Hash + Eq,
{
if deadline_exceeded(deadline) {
return Ok(false);
}
let old_len = old_range.len();
let new_len = new_range.len();
if old_len < DISJOINT_FAST_PATH_MIN_LEN
|| new_len < DISJOINT_FAST_PATH_MIN_LEN
|| old_len.saturating_mul(new_len) < DISJOINT_FAST_PATH_MIN_WORK
{
return Ok(false);
}
if type_name::<Old::Output>() != type_name::<New::Output>() {
return Ok(false);
}
if new[new_range.start] == old[old_range.start]
|| new[new_range.end - 1] == old[old_range.end - 1]
{
return Ok(false);
}
let has_common_item =
match has_common_item(old, old_range.clone(), new, new_range.clone(), deadline) {
Some(value) => value,
None => return Ok(false),
};
if has_common_item {
return Ok(false);
}
d.delete(old_range.start, old_len, new_range.start)?;
d.insert(old_range.start, new_range.start, new_len)?;
d.finish()?;
Ok(true)
}
fn has_common_item<Old, New>(
old: &Old,
old_range: Range<usize>,
new: &New,
new_range: Range<usize>,
deadline: Option<Instant>,
) -> Option<bool>
where
Old: Index<usize> + ?Sized,
New: Index<usize> + ?Sized,
Old::Output: Hash,
New::Output: PartialEq<Old::Output> + Hash,
{
let mut by_hash = MapType::<u64, Vec<usize>>::new();
for (idx, old_idx) in old_range.enumerate() {
if (idx & (DISJOINT_FAST_PATH_DEADLINE_CHECK_INTERVAL - 1) == 0)
&& deadline_exceeded(deadline)
{
return None;
}
by_hash
.entry(stable_hash(&old[old_idx]))
.or_default()
.push(old_idx);
}
for (idx, new_idx) in new_range.enumerate() {
if (idx & (DISJOINT_FAST_PATH_DEADLINE_CHECK_INTERVAL - 1) == 0)
&& deadline_exceeded(deadline)
{
return None;
}
if let Some(candidates) = by_hash.get(&stable_hash(&new[new_idx])) {
let new_item = &new[new_idx];
if candidates.iter().any(|&old_idx| new_item == &old[old_idx]) {
return Some(true);
}
}
}
Some(false)
}
#[test]
fn test_has_common_item() {
assert_eq!(
has_common_item(&[1, 2, 3], 0..3, &[9, 3, 10], 0..3, None),
Some(true)
);
assert_eq!(
has_common_item(&[1, 2, 3], 0..3, &[9, 8, 10], 0..3, None),
Some(false)
);
}
#[test]
fn test_has_common_item_hash_collisions() {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Collide(u32);
impl Hash for Collide {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
0u8.hash(state);
}
}
assert_eq!(
has_common_item(
&[Collide(1), Collide(2)],
0..2,
&[Collide(3), Collide(4)],
0..2,
None
),
Some(false)
);
assert_eq!(
has_common_item(
&[Collide(1), Collide(2)],
0..2,
&[Collide(3), Collide(2)],
0..2,
None
),
Some(true)
);
}