diff --git a/src/differ.rs b/src/differ.rs index 05439c6..33326e3 100644 --- a/src/differ.rs +++ b/src/differ.rs @@ -1,4 +1,5 @@ use sequencematcher::SequenceMatcher; +use utils::{slice_str, str_with_similar_chars}; pub struct Differ { @@ -13,4 +14,144 @@ impl Differ{ charjunk: None } } + + pub fn compare(&self, first_sequence: &str, second_sequence: &str) -> Vec> { + let mut matcher = SequenceMatcher::new(first_sequence, second_sequence); + let mut res = Vec::new(); + for opcode in matcher.get_opcodes(){ + match opcode.tag.as_ref() { + "replace" => {}, + "delete" => { res.push( self.dump("-", first_sequence, opcode.first_start, opcode.first_end) ) }, + "insert" => { res.push( self.dump("+", second_sequence, opcode.second_start, opcode.second_end) ) }, + "equal" => { res.push( self.dump(" ", first_sequence, opcode.first_start, opcode.first_end) ) }, + _ => {} + } + } + res + } + + fn dump(&self, tag: &str, string: &str, start: usize, end: usize) -> Vec { + let mut res = Vec::new(); + match slice_str(string, start, end) { + Some(s) => { + for c in s.chars(){ + res.push(format!("{} {}", tag, c)); + } + }, + None => {}, + } + res + } + + fn plain_replace(&self, first_sequence: &str, first_start: usize, first_end: usize, + second_sequence: &str, second_start: usize, second_end: usize) -> Vec> { + if !(first_start < first_end && second_start < second_end){ + return Vec::new(); + } + let mut first = Vec::new(); + let mut second = Vec::new(); + if second_end - second_start < first_end - first_start{ + first = self.dump("+", second_sequence, second_start, second_end); + second = self.dump("-", first_sequence, first_start, first_end); + } else { + first = self.dump("-", first_sequence, first_start, first_end); + second = self.dump("+", second_sequence, second_start, second_end); + } + vec![first, second] + } + + fn fancy_replace(&self, first_sequence: &str, first_start: usize, first_end: usize, + second_sequence: &str, second_start: usize, second_end: usize) -> Vec> { + let mut res = Vec::new(); + let first_sequence_vec: Vec = first_sequence.chars().collect(); + let second_sequence_vec: Vec = second_sequence.chars().collect(); + let (mut best_ratio, cutoff) = (0.74, 0.75); + let (mut best_i, mut best_j) = (0, 0); + let (mut second_sequence_char, mut first_sequence_char) = (String::new(), String::new()); + let mut cruncher = SequenceMatcher::new("", ""); + //cruncher.charjunk = self.charjunk; + // eqi, eqj = None, None # 1st indices of equal lines (if any) + let mut eqi: Option = None; + let mut eqj: Option = None; + for j in second_start..second_end{ + cruncher.set_first_seq(""); + cruncher.set_second_seq(""); + second_sequence_char = second_sequence_vec[j].to_string(); + //cruncher.set_second_seq(&second_sequence_char); + for i in first_start..first_end{ + first_sequence_char = first_sequence_vec[i].to_string(); + if first_sequence_char == second_sequence_char{ + if eqi.is_none(){ + eqi = Some(i); + eqj = Some(j); + } + continue; + } + //cruncher.set_first_seq(&first_sequence_char); + if cruncher.ratio() > best_ratio{ + best_ratio = cruncher.ratio(); + best_i = i; + best_j = j; + } + } + } + if best_ratio < cutoff{ + if eqi.is_none(){ + res.extend(self.plain_replace(first_sequence, first_start, first_end, second_sequence, second_start, second_end).iter().cloned()); + } + best_i = eqi.unwrap(); + best_j = eqj.unwrap(); + best_ratio = 1.0; + } else { + eqi = None; + } + res.extend(vec![self.fancy_helper(first_sequence, first_start, best_i, second_sequence, second_start, best_j)].iter().cloned()); + let (first_elt, second_elt) = (first_sequence_vec[best_i].to_string(), second_sequence_vec[best_j].to_string()); + if eqi.is_none(){ + let (mut first_tag, mut second_tag) = (String::new(), String::new()); + //cruncher.set_seqs(&first_elt, &second_elt); + for opcode in &cruncher.get_opcodes(){ + let (first_length, second_length) = (opcode.first_end - opcode.first_start, opcode.second_end - opcode.second_start); + match opcode.tag.as_ref() { + "replace" => { + first_tag.push_str(&str_with_similar_chars('^', first_length)); + second_tag.push_str(&str_with_similar_chars('^', second_length)); + }, + "delete" => { + first_tag.push_str(&str_with_similar_chars('^', first_length)); + }, + "insert" => { + second_tag.push_str(&str_with_similar_chars('^', second_length)); + }, + "equal" => { + first_tag.push_str(&str_with_similar_chars(' ', first_length)); + second_tag.push_str(&str_with_similar_chars(' ', second_length)); + }, + _ => {} + } + //res.extend(self.qformat) + } + } else { + let mut s = String::from(" "); + s.push_str(&first_elt); + res.extend(vec![vec![s]].iter().cloned()); + } + res.extend(vec![self.fancy_helper(first_sequence, best_i + 1, first_end, second_sequence, best_j + 1, second_end)].iter().cloned()); + res + } + + fn fancy_helper(&self, first_sequence: &str, first_start: usize, first_end: usize, + second_sequence: &str, second_start: usize, second_end: usize) -> Vec { + let mut res = Vec::new(); + if first_start < first_end{ + if second_start < second_end{ + res = self.fancy_replace(first_sequence, first_start, first_end, second_sequence, second_start, second_end); + } else { + res = self.dump("-", first_sequence, first_start, first_end); + } + } else if second_start < second_end { + res = self.dump("+", second_sequence, second_start, second_end); + } + res + } } \ No newline at end of file diff --git a/src/sequencematcher.rs b/src/sequencematcher.rs index 9f51d80..127455b 100644 --- a/src/sequencematcher.rs +++ b/src/sequencematcher.rs @@ -38,24 +38,68 @@ impl Match{ } } +#[derive(Debug, Clone)] +pub struct Opcode{ + pub tag: String, + pub first_start: usize, + pub first_end: usize, + pub second_start: usize, + pub second_end: usize +} + +impl Opcode{ + fn new(tag: String, first_start: usize, first_end: usize, second_start: usize, second_end: usize) -> Opcode { + Opcode{ + tag: tag, + first_start: first_start, + first_end: first_end, + second_start: second_start, + second_end: second_end + } + } +} + pub struct SequenceMatcher<'a>{ first_sequence: &'a str, second_sequence: &'a str, matching_blocks: Option>, - opcodes: Option> + opcodes: Option>, + autojunk: bool } + impl<'a> SequenceMatcher<'a>{ pub fn new(first_sequence: &'a str, second_sequence: &'a str) -> SequenceMatcher<'a> { SequenceMatcher{ first_sequence: first_sequence, second_sequence: second_sequence, matching_blocks: None, - opcodes: None + opcodes: None, + autojunk: true, + } } + pub fn set_seqs(&mut self, first_sequence: &'a str, second_sequence: &'a str) { + self.set_first_seq(first_sequence); + self.set_second_seq(second_sequence); + } + + pub fn set_first_seq(&mut self, sequence: &'a str) { + self.first_sequence = sequence; + self.matching_blocks = None; + self.opcodes = None; + } + + pub fn set_second_seq(&mut self, sequence: &'a str) { + self.second_sequence = sequence; + self.matching_blocks = None; + self.opcodes = None; + //self.fullbcount = None + //self.__chain_b() + } + pub fn find_longest_match(&self, first_start: usize, first_end: usize, second_start: usize, second_end: usize) -> Option { let first_sequence: Vec = slice_str(self.first_sequence, first_start, first_end).unwrap_or("").chars().collect(); let second_sequence: Vec = slice_str(self.second_sequence, second_start, second_end).unwrap_or("").chars().collect(); @@ -133,7 +177,7 @@ impl<'a> SequenceMatcher<'a>{ self.matching_blocks.as_ref().unwrap().clone() } - pub fn get_opcodes(&mut self) -> Vec<(String, usize, usize, usize, usize)>{ + pub fn get_opcodes(&mut self) -> Vec{ if self.opcodes.as_ref().is_some(){ return self.opcodes.as_ref().unwrap().clone() } @@ -151,12 +195,12 @@ impl<'a> SequenceMatcher<'a>{ tag = String::from("insert"); } if !tag.is_empty(){ - opcodes.push( (tag, i, m.first_start, j, m.second_start) ); + opcodes.push( Opcode::new(tag, i, m.first_start, j, m.second_start) ); } i = m.first_start + m.size; j = m.second_start + m.size; if m.size != 0{ - opcodes.push( (String::from("equal"), m.first_start, i, m.second_start, j) ); + opcodes.push( Opcode::new(String::from("equal"), m.first_start, i, m.second_start, j) ); } } self.opcodes = Some(opcodes); @@ -167,4 +211,4 @@ impl<'a> SequenceMatcher<'a>{ let matches = self.get_matching_blocks().iter().fold(0, |res, &m| res + m.size); calculate_ratio(matches, self.first_sequence.len() + self.second_sequence.len()) } -} \ No newline at end of file +} diff --git a/src/utils.rs b/src/utils.rs index 8060adf..62501df 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -17,4 +17,13 @@ pub fn calculate_ratio(matches: usize, length: usize) -> f32{ return 2.0 * matches as f32 / length as f32 } return 1.0 -} \ No newline at end of file +} + + +pub fn str_with_similar_chars(c: char, length: usize) -> String { + let mut s = String::new(); + for _ in 0..length{ + s.push_str(&c.to_string()); + } + s +}