Partly implement differ struce, add Opcode struct

This commit is contained in:
Dima Kudosh
2016-02-17 21:03:12 +03:00
parent ca75fcc862
commit 38cc286ba2
3 changed files with 201 additions and 7 deletions
+141
View File
@@ -1,4 +1,5 @@
use sequencematcher::SequenceMatcher; use sequencematcher::SequenceMatcher;
use utils::{slice_str, str_with_similar_chars};
pub struct Differ { pub struct Differ {
@@ -13,4 +14,144 @@ impl Differ{
charjunk: None charjunk: None
} }
} }
pub fn compare(&self, first_sequence: &str, second_sequence: &str) -> Vec<Vec<String>> {
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<String> {
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<Vec<String>> {
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<Vec<String>> {
let mut res = Vec::new();
let first_sequence_vec: Vec<char> = first_sequence.chars().collect();
let second_sequence_vec: Vec<char> = 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<usize> = None;
let mut eqj: Option<usize> = 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<String> {
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
}
} }
+50 -6
View File
@@ -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>{ pub struct SequenceMatcher<'a>{
first_sequence: &'a str, first_sequence: &'a str,
second_sequence: &'a str, second_sequence: &'a str,
matching_blocks: Option<Vec<Match>>, matching_blocks: Option<Vec<Match>>,
opcodes: Option<Vec<(String, usize, usize, usize, usize)>> opcodes: Option<Vec<Opcode>>,
autojunk: bool
} }
impl<'a> SequenceMatcher<'a>{ impl<'a> SequenceMatcher<'a>{
pub fn new(first_sequence: &'a str, second_sequence: &'a str) -> SequenceMatcher<'a> { pub fn new(first_sequence: &'a str, second_sequence: &'a str) -> SequenceMatcher<'a> {
SequenceMatcher{ SequenceMatcher{
first_sequence: first_sequence, first_sequence: first_sequence,
second_sequence: second_sequence, second_sequence: second_sequence,
matching_blocks: None, 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<Match> { pub fn find_longest_match(&self, first_start: usize, first_end: usize, second_start: usize, second_end: usize) -> Option<Match> {
let first_sequence: Vec<char> = slice_str(self.first_sequence, first_start, first_end).unwrap_or("").chars().collect(); let first_sequence: Vec<char> = slice_str(self.first_sequence, first_start, first_end).unwrap_or("").chars().collect();
let second_sequence: Vec<char> = slice_str(self.second_sequence, second_start, second_end).unwrap_or("").chars().collect(); let second_sequence: Vec<char> = 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() 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<Opcode>{
if self.opcodes.as_ref().is_some(){ if self.opcodes.as_ref().is_some(){
return self.opcodes.as_ref().unwrap().clone() return self.opcodes.as_ref().unwrap().clone()
} }
@@ -151,12 +195,12 @@ impl<'a> SequenceMatcher<'a>{
tag = String::from("insert"); tag = String::from("insert");
} }
if !tag.is_empty(){ 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; i = m.first_start + m.size;
j = m.second_start + m.size; j = m.second_start + m.size;
if m.size != 0{ 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); 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); 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()) calculate_ratio(matches, self.first_sequence.len() + self.second_sequence.len())
} }
} }
+10 -1
View File
@@ -17,4 +17,13 @@ pub fn calculate_ratio(matches: usize, length: usize) -> f32{
return 2.0 * matches as f32 / length as f32 return 2.0 * matches as f32 / length as f32
} }
return 1.0 return 1.0
} }
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
}