// Modified for gitoxide from the upstream imara-diff crate. // Upstream source: git cat-file -p 32d1e45d3df061e6ccba6db7fdce92db29e345d8:src/histogram/lcs.rs use crate::histogram::{Histogram, MAX_CHAIN_LEN}; use crate::intern::Token; /// Finds the longest common subsequence (LCS) using a histogram-based approach. /// /// Returns `None` if the sequences are highly repetitive and should fall back to Myers. pub(super) fn find_lcs(before: &[Token], after: &[Token], histogram: &mut Histogram) -> Option { let mut search = LcsSearch { lcs: Lcs::default(), min_occurrences: MAX_CHAIN_LEN + 1, found_cs: false, }; search.run(before, after, histogram); if search.success() { Some(search.lcs) } else { None } } /// Represents a longest common subsequence found by the histogram algorithm. #[derive(Default, Debug)] pub struct Lcs { /// Starting position in the "before" sequence. pub before_start: u32, /// Starting position in the "after" sequence. pub after_start: u32, /// Length of the common subsequence. pub len: u32, } /// State for searching for the longest common subsequence. pub struct LcsSearch { /// The best LCS found so far. lcs: Lcs, /// The minimum occurrence count of tokens in the best LCS. min_occurrences: u32, /// Whether any common subsequence was found. found_cs: bool, } impl LcsSearch { fn run(&mut self, before: &[Token], after: &[Token], histogram: &mut Histogram) { let mut pos = 0; while let Some(&token) = after.get(pos as usize) { if histogram.num_token_occurrences(token) != 0 { self.found_cs = true; if histogram.num_token_occurrences(token) <= self.min_occurrences { pos = self.update_lcs(pos, token, histogram, before, after); continue; } } pos += 1; } histogram.clear(); } fn success(&mut self) -> bool { !self.found_cs || self.min_occurrences <= MAX_CHAIN_LEN } fn update_lcs( &mut self, after_pos: u32, token: Token, histogram: &Histogram, before: &[Token], after: &[Token], ) -> u32 { let mut next_token_idx2 = after_pos + 1; let mut occurrences_iter = histogram.token_occurrences(token).iter().copied(); let mut token_idx1 = occurrences_iter.next().unwrap(); 'occurrences_iter: loop { let mut occurrences = histogram.num_token_occurrences(token); let mut start1 = token_idx1; let mut start2 = after_pos; loop { if start1 == 0 || start2 == 0 { break; } let token1 = before.get(start1 as usize - 1); let token2 = after.get(start2 as usize - 1); if matches!((token1, token2), (Some(token1), Some(token2)) if token1 == token2) { start1 -= 1; start2 -= 1; let new_occurrences = histogram.num_token_occurrences(before[start1 as usize]); occurrences = occurrences.min(new_occurrences); } else { break; } } let mut end1 = token_idx1 + 1; let mut end2 = after_pos + 1; loop { let token1 = before.get(end1 as usize); let token2 = after.get(end2 as usize); if matches!((token1, token2), (Some(token1), Some(token2)) if token1 == token2) { let new_occurrences = histogram.num_token_occurrences(before[end1 as usize]); occurrences = occurrences.min(new_occurrences); end1 += 1; end2 += 1; } else { break; } } if next_token_idx2 < end2 { next_token_idx2 = end2; } let len = end2 - start2; debug_assert_eq!(len, end1 - start1); if self.lcs.len < len || self.min_occurrences > occurrences { self.min_occurrences = occurrences; self.lcs = Lcs { before_start: start1, after_start: start2, len, }; } loop { if let Some(next_token_idx) = occurrences_iter.next() { if next_token_idx > end2 { token_idx1 = next_token_idx; break; } } else { break 'occurrences_iter; } } } next_token_idx2 } }