From a053cc079ab04ad9ad9839e5a46e3490e22cf85e Mon Sep 17 00:00:00 2001 From: nmlgc Date: Fri, 15 Jun 2018 11:54:48 +0200 Subject: [PATCH 1/9] Run `cargo fmt` --- README.md | 27 +++-- examples/example.rs | 33 +++--- src/differ.rs | 246 ++++++++++++++++++++++++----------------- src/lib.rs | 75 +++++++------ src/sequencematcher.rs | 101 ++++++++++------- src/utils.rs | 4 - tests/tests.rs | 66 ++++++----- 7 files changed, 322 insertions(+), 230 deletions(-) diff --git a/README.md b/README.md index 3b07bd3..37334a6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Difflib [![Build Status](https://travis-ci.org/DimaKudosh/difflib.svg?branch=master)](https://travis-ci.org/DimaKudosh/difflib) -Port of Python's difflib library to Rust. +Port of Python's difflib library to Rust. It's provide all necessary tools for comparing word sequences. ## Installation @@ -21,20 +21,33 @@ extern crate difflib; use difflib::differ::Differ; use difflib::sequencematcher::SequenceMatcher; - fn main() { // unified_diff let first_text = "one two three four".split(" ").collect::>(); let second_text = "zero one tree four".split(" ").collect::>(); - let diff = difflib::unified_diff(&first_text, &second_text, "Original", "Current", - "2005-01-26 23:30:50", "2010-04-02 10:20:52", 3); + let diff = difflib::unified_diff( + &first_text, + &second_text, + "Original", + "Current", + "2005-01-26 23:30:50", + "2010-04-02 10:20:52", + 3, + ); for line in &diff { println!("{:?}", line); } //context_diff - let diff = difflib::context_diff(&first_text, &second_text, "Original", "Current", - "2005-01-26 23:30:50", "2010-04-02 10:20:52", 3); + let diff = difflib::context_diff( + &first_text, + &second_text, + "Original", + "Current", + "2005-01-26 23:30:50", + "2010-04-02 10:20:52", + 3, + ); for line in &diff { println!("{:?}", line); } @@ -62,7 +75,7 @@ fn main() { let grouped_opcodes = matcher.get_grouped_opcodes(2); println!("{:?}", grouped_opcodes); let ratio = matcher.ratio(); - println!("{:?}", ratio); + println!("{:?}", ratio); matcher.set_seqs("aaaaa", "aaaab"); println!("{:?}", matcher.ratio()); } diff --git a/examples/example.rs b/examples/example.rs index 9c866f4..3f06d0a 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -3,30 +3,33 @@ extern crate difflib; use difflib::differ::Differ; use difflib::sequencematcher::SequenceMatcher; - fn main() { // unified_diff let first_text = "one two three four".split(" ").collect::>(); let second_text = "zero one tree four".split(" ").collect::>(); - let diff = difflib::unified_diff(&first_text, - &second_text, - "Original", - "Current", - "2005-01-26 23:30:50", - "2010-04-02 10:20:52", - 3); + let diff = difflib::unified_diff( + &first_text, + &second_text, + "Original", + "Current", + "2005-01-26 23:30:50", + "2010-04-02 10:20:52", + 3, + ); for line in &diff { println!("{:?}", line); } //context_diff - let diff = difflib::context_diff(&first_text, - &second_text, - "Original", - "Current", - "2005-01-26 23:30:50", - "2010-04-02 10:20:52", - 3); + let diff = difflib::context_diff( + &first_text, + &second_text, + "Original", + "Current", + "2005-01-26 23:30:50", + "2010-04-02 10:20:52", + 3, + ); for line in &diff { println!("{:?}", line); } diff --git a/src/differ.rs b/src/differ.rs index 0910887..a021e2f 100644 --- a/src/differ.rs +++ b/src/differ.rs @@ -1,7 +1,6 @@ -use sequencematcher::{SequenceMatcher, Sequence}; -use utils::{str_with_similar_chars, count_leading}; +use sequencematcher::{Sequence, SequenceMatcher}; use std::cmp; - +use utils::{count_leading, str_with_similar_chars}; pub struct Differ { pub line_junk: Option bool>, @@ -16,10 +15,11 @@ impl Differ { } } - pub fn compare(&self, - first_sequence: &T, - second_sequence: &T) - -> Vec { + pub fn compare( + &self, + first_sequence: &T, + second_sequence: &T, + ) -> Vec { let mut matcher = SequenceMatcher::new(first_sequence, second_sequence); matcher.set_is_junk(self.line_junk); let mut res = Vec::new(); @@ -27,12 +27,14 @@ impl Differ { let mut gen = Vec::new(); match opcode.tag.as_ref() { "replace" => { - gen = self.fancy_replace(first_sequence, - opcode.first_start, - opcode.first_end, - second_sequence, - opcode.second_start, - opcode.second_end) + gen = self.fancy_replace( + first_sequence, + opcode.first_start, + opcode.first_end, + second_sequence, + opcode.second_start, + opcode.second_end, + ) } "delete" => { gen = self.dump("-", first_sequence, opcode.first_start, opcode.first_end) @@ -52,12 +54,13 @@ impl Differ { res } - fn dump(&self, - tag: &str, - sequence: &T, - start: usize, - end: usize) - -> Vec { + fn dump( + &self, + tag: &str, + sequence: &T, + start: usize, + end: usize, + ) -> Vec { let mut res = Vec::new(); for i in start..end { match sequence.at_index(i) { @@ -68,14 +71,15 @@ impl Differ { res } - fn plain_replace(&self, - first_sequence: &T, - first_start: usize, - first_end: usize, - second_sequence: &T, - second_start: usize, - second_end: usize) - -> Vec { + fn plain_replace( + &self, + first_sequence: &T, + first_start: usize, + first_end: usize, + second_sequence: &T, + second_start: usize, + second_end: usize, + ) -> Vec { if !(first_start < first_end && second_start < second_end) { return Vec::new(); } @@ -94,14 +98,15 @@ impl Differ { first } - fn fancy_replace(&self, - first_sequence: &T, - first_start: usize, - first_end: usize, - second_sequence: &T, - second_start: usize, - second_end: usize) - -> Vec { + fn fancy_replace( + &self, + first_sequence: &T, + first_start: usize, + first_end: usize, + second_sequence: &T, + second_start: usize, + second_end: usize, + ) -> Vec { let mut res = Vec::new(); let (mut best_ratio, cutoff) = (0.74, 0.75); let (mut best_i, mut best_j) = (0, 0); @@ -130,14 +135,17 @@ impl Differ { } 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()); + res.extend( + self.plain_replace( + first_sequence, + first_start, + first_end, + second_sequence, + second_start, + second_end, + ).iter() + .cloned(), + ); return res; } best_i = eqi.unwrap(); @@ -145,23 +153,30 @@ impl Differ { } else { eqi = None; } - res.extend(self.fancy_helper(first_sequence, - first_start, - best_i, - second_sequence, - second_start, - best_j) - .iter() - .cloned()); - let (first_element, second_element) = (first_sequence.at_index(best_i).unwrap(), - second_sequence.at_index(best_j).unwrap()); + res.extend( + self.fancy_helper( + first_sequence, + first_start, + best_i, + second_sequence, + second_start, + best_j, + ).iter() + .cloned(), + ); + let (first_element, second_element) = ( + first_sequence.at_index(best_i).unwrap(), + second_sequence.at_index(best_j).unwrap(), + ); if eqi.is_none() { let (mut first_tag, mut second_tag) = (String::new(), String::new()); let mut cruncher = SequenceMatcher::new(first_element, second_element); cruncher.set_is_junk(self.char_junk); for opcode in &cruncher.get_opcodes() { - let (first_length, second_length) = (opcode.first_end - opcode.first_start, - opcode.second_end - opcode.second_start); + 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)); @@ -180,42 +195,50 @@ impl Differ { _ => {} } } - res.extend(self.qformat(&first_element, &second_element, &first_tag, &second_tag) - .iter() - .cloned()); + res.extend( + self.qformat(&first_element, &second_element, &first_tag, &second_tag) + .iter() + .cloned(), + ); } else { let mut s = String::from(" "); s.push_str(&first_element); res.push(s); } - res.extend(self.fancy_helper(first_sequence, - best_i + 1, - first_end, - second_sequence, - best_j + 1, - second_end) - .iter() - .cloned()); + res.extend( + 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: &T, - first_start: usize, - first_end: usize, - second_sequence: &T, - second_start: usize, - second_end: usize) - -> Vec { + fn fancy_helper( + &self, + first_sequence: &T, + first_start: usize, + first_end: usize, + second_sequence: &T, + 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); + 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); } @@ -225,17 +248,20 @@ impl Differ { res } - fn qformat(&self, - first_line: &str, - second_line: &str, - first_tags: &str, - second_tags: &str) - -> Vec { + fn qformat( + &self, + first_line: &str, + second_line: &str, + first_tags: &str, + second_tags: &str, + ) -> Vec { let mut res = Vec::new(); let mut first_tags = first_tags; let mut second_tags = second_tags; - let mut common = cmp::min(count_leading(first_line, '\t'), - count_leading(second_line, '\t')); + let mut common = cmp::min( + count_leading(first_line, '\t'), + count_leading(second_line, '\t'), + ); common = cmp::min(common, count_leading(first_tags.split_at(common).0, ' ')); common = cmp::min(common, count_leading(first_tags.split_at(common).0, ' ')); first_tags = first_tags.split_at(common).1.trim_right(); @@ -243,15 +269,21 @@ impl Differ { let mut s = String::from(format!("- {}", first_line)); res.push(s); if first_tags != "" { - s = String::from(format!("? {}{}\n", str_with_similar_chars('\t', common), first_tags)); + s = String::from(format!( + "? {}{}\n", + str_with_similar_chars('\t', common), + first_tags + )); res.push(s); } s = String::from(format!("+ {}", second_line)); res.push(s); if second_tags != "" { - s = String::from(format!("? {}{}\n", - str_with_similar_chars('\t', common), - second_tags)); + s = String::from(format!( + "? {}{}\n", + str_with_similar_chars('\t', common), + second_tags + )); res.push(s); } res @@ -283,19 +315,31 @@ impl Differ { #[test] fn test_fancy_replace() { let differ = Differ::new(); - let result = differ.fancy_replace(&vec!["abcDefghiJkl\n"], 0, 1, &vec!["abcdefGhijkl\n"], 0, 1) + let result = differ + .fancy_replace(&vec!["abcDefghiJkl\n"], 0, 1, &vec!["abcdefGhijkl\n"], 0, 1) .join(""); - assert_eq!(result, - "- abcDefghiJkl\n? ^ ^ ^\n+ abcdefGhijkl\n? ^ ^ ^\n"); + assert_eq!( + result, + "- abcDefghiJkl\n? ^ ^ ^\n+ abcdefGhijkl\n? ^ ^ ^\n" + ); } #[test] fn test_qformat() { let differ = Differ::new(); - let result = differ.qformat("\tabcDefghiJkl\n", - "\tabcdefGhijkl\n", - " ^ ^ ^ ", - " ^ ^ ^ "); - assert_eq!(result, - vec!["- \tabcDefghiJkl\n", "? \t ^ ^ ^\n", "+ \tabcdefGhijkl\n", "? \t ^ ^ ^\n"]); + let result = differ.qformat( + "\tabcDefghiJkl\n", + "\tabcdefGhijkl\n", + " ^ ^ ^ ", + " ^ ^ ^ ", + ); + assert_eq!( + result, + vec![ + "- \tabcDefghiJkl\n", + "? \t ^ ^ ^\n", + "+ \tabcdefGhijkl\n", + "? \t ^ ^ ^\n", + ] + ); } diff --git a/src/lib.rs b/src/lib.rs index 5078dc3..95cb23a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,17 @@ -pub mod sequencematcher; pub mod differ; +pub mod sequencematcher; mod utils; - +use sequencematcher::{Sequence, SequenceMatcher}; use std::collections::HashMap; -use sequencematcher::{SequenceMatcher, Sequence}; -use utils::{format_range_unified, format_range_context}; +use utils::{format_range_context, format_range_unified}; - -pub fn get_close_matches<'a>(word: &str, - possibilities: Vec<&'a str>, - n: usize, - cutoff: f32) - -> Vec<&'a str> { +pub fn get_close_matches<'a>( + word: &str, + possibilities: Vec<&'a str>, + n: usize, + cutoff: f32, +) -> Vec<&'a str> { if !(0.0 <= cutoff && cutoff <= 1.0) { panic!("Cutoff must be greater than 0.0 and lower than 1.0"); } @@ -30,14 +29,15 @@ pub fn get_close_matches<'a>(word: &str, res.iter().map(|x| x.1).collect() } -pub fn unified_diff(first_sequence: &T, - second_sequence: &T, - from_file: &str, - to_file: &str, - from_file_date: &str, - to_file_date: &str, - n: usize) - -> Vec { +pub fn unified_diff( + first_sequence: &T, + second_sequence: &T, + from_file: &str, + to_file: &str, + from_file_date: &str, + to_file_date: &str, + n: usize, +) -> Vec { let mut res = Vec::new(); let lineterm = '\n'; let mut started = false; @@ -53,7 +53,10 @@ pub fn unified_diff(first_sequence: &T, let (first, last) = (group.first().unwrap(), group.last().unwrap()); let file1_range = format_range_unified(first.first_start, last.first_end); let file2_range = format_range_unified(first.second_start, last.second_end); - res.push(format!("@@ -{} +{} @@{}", file1_range, file2_range, lineterm)); + res.push(format!( + "@@ -{} +{} @@{}", + file1_range, file2_range, lineterm + )); for code in group { if code.tag == "equal" { for i in code.first_start..code.first_end { @@ -76,15 +79,15 @@ pub fn unified_diff(first_sequence: &T, res } - -pub fn context_diff(first_sequence: &T, - second_sequence: &T, - from_file: &str, - to_file: &str, - from_file_date: &str, - to_file_date: &str, - n: usize) - -> Vec { +pub fn context_diff( + first_sequence: &T, + second_sequence: &T, + from_file: &str, + to_file: &str, + from_file_date: &str, + to_file_date: &str, + n: usize, +) -> Vec { let mut res = Vec::new(); let lineterm = '\n'; let mut prefix: HashMap = HashMap::new(); @@ -117,9 +120,11 @@ pub fn context_diff(first_sequence: &T, for opcode in group { if opcode.tag != "insert" { for i in opcode.first_start..opcode.first_end { - res.push(format!("{}{}", - prefix.get(&opcode.tag).unwrap(), - first_sequence.at_index(i).unwrap())); + res.push(format!( + "{}{}", + prefix.get(&opcode.tag).unwrap(), + first_sequence.at_index(i).unwrap() + )); } } } @@ -137,9 +142,11 @@ pub fn context_diff(first_sequence: &T, for opcode in group { if opcode.tag != "delete" { for i in opcode.second_start..opcode.second_end { - res.push(format!("{}{}", - prefix.get(&opcode.tag).unwrap(), - second_sequence.at_index(i).unwrap())); + res.push(format!( + "{}{}", + prefix.get(&opcode.tag).unwrap(), + second_sequence.at_index(i).unwrap() + )); } } } diff --git a/src/sequencematcher.rs b/src/sequencematcher.rs index 7973ba8..4444baf 100644 --- a/src/sequencematcher.rs +++ b/src/sequencematcher.rs @@ -1,7 +1,7 @@ -use std::collections::{HashMap, HashSet}; use std::cmp::{max, min}; -use utils::calculate_ratio; +use std::collections::{HashMap, HashSet}; use std::fmt::Debug; +use utils::calculate_ratio; #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] pub struct Match { @@ -30,12 +30,13 @@ pub struct Opcode { } impl Opcode { - fn new(tag: String, - first_start: usize, - first_end: usize, - second_start: usize, - second_end: usize) - -> 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, @@ -113,7 +114,6 @@ pub struct SequenceMatcher<'a, T: 'a + ?Sized + Sequence> { second_sequence_popular: HashSet<&'a str>, } - impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { pub fn new(first_sequence: &'a T, second_sequence: &'a T) -> SequenceMatcher<'a, T> { let mut matcher = SequenceMatcher { @@ -156,7 +156,8 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { let second_sequence = self.second_sequence; let mut second_sequence_elements = HashMap::new(); for i in 0..second_sequence.len() { - let mut counter = second_sequence_elements.entry(second_sequence.at_index(i).unwrap()) + let mut counter = second_sequence_elements + .entry(second_sequence.at_index(i).unwrap()) .or_insert(Vec::new()); counter.push(i); } @@ -188,12 +189,13 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { self.second_sequence_popular = popular; } - pub fn find_longest_match(&self, - first_start: usize, - first_end: usize, - second_start: usize, - second_end: usize) - -> Match { + pub fn find_longest_match( + &self, + first_start: usize, + first_end: usize, + second_start: usize, + second_end: usize, + ) -> Match { let first_sequence = &self.first_sequence; let second_sequence = &self.second_sequence; let second_sequence_elements = &self.second_sequence_elements; @@ -234,15 +236,18 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { j2len = new_j2len; } for _ in 0..2 { - while best_i > first_start && best_j > second_start && - first_sequence.at_index(best_i - 1) == second_sequence.at_index(best_j - 1) { + while best_i > first_start + && best_j > second_start + && first_sequence.at_index(best_i - 1) == second_sequence.at_index(best_j - 1) + { best_i = best_i - 1; best_j = best_j - 1; best_size = best_size + 1; } - while best_i + best_size < first_end && best_j + best_size < second_end && - first_sequence.at_index(best_i + best_size) == - second_sequence.at_index(best_j + best_size) { + while best_i + best_size < first_end && best_j + best_size < second_end + && first_sequence.at_index(best_i + best_size) + == second_sequence.at_index(best_j + best_size) + { best_size += 1; } } @@ -266,10 +271,12 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { queue.push((first_start, m.first_start, second_start, m.second_start)); } if m.first_start + m.size < first_end && m.second_start + m.size < second_end { - queue.push((m.first_start + m.size, - first_end, - m.second_start + m.size, - second_end)); + queue.push(( + m.first_start + m.size, + first_end, + m.second_start + m.size, + second_end, + )); } matches.push(m); } @@ -319,9 +326,13 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { i = m.first_start + m.size; j = m.second_start + m.size; if m.size != 0 { - opcodes.push( -Opcode::new(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); @@ -350,21 +361,25 @@ Opcode::new(String::from("equal"), m.first_start, i, m.second_start, j) for code in &codes { let (mut first_start, mut second_start) = (code.first_start, code.second_start); if code.tag == "equal" && code.first_end - code.first_start > nn { - group.push(Opcode::new(code.tag.clone(), - code.first_start, - min(code.first_end, code.first_start + n), - code.second_start, - min(code.second_end, code.second_start + n))); + group.push(Opcode::new( + code.tag.clone(), + code.first_start, + min(code.first_end, code.first_start + n), + code.second_start, + min(code.second_end, code.second_start + n), + )); res.push(group.clone()); group.clear(); first_start = max(first_start, code.first_end.saturating_sub(n)); second_start = max(second_start, code.second_end.saturating_sub(n)); } - group.push(Opcode::new(code.tag.clone(), - first_start, - code.first_end, - second_start, - code.second_end)); + group.push(Opcode::new( + code.tag.clone(), + first_start, + code.first_end, + second_start, + code.second_end, + )); } if !group.is_empty() && !(group.len() == 1 && group.first().unwrap().tag == "equal") { res.push(group.clone()); @@ -373,8 +388,12 @@ Opcode::new(String::from("equal"), m.first_start, i, m.second_start, j) } pub fn ratio(&mut self) -> f32 { - 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()) + 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(), + ) } } diff --git a/src/utils.rs b/src/utils.rs index 71c7743..c22a9d2 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -5,7 +5,6 @@ pub fn calculate_ratio(matches: usize, length: usize) -> f32 { return 1.0; } - pub fn str_with_similar_chars(c: char, length: usize) -> String { let mut s = String::new(); for _ in 0..length { @@ -14,7 +13,6 @@ pub fn str_with_similar_chars(c: char, length: usize) -> String { s } - pub fn count_leading(line: &str, c: char) -> usize { let (mut i, n) = (0, line.len()); let line: Vec = line.chars().collect(); @@ -24,7 +22,6 @@ pub fn count_leading(line: &str, c: char) -> usize { return i; } - pub fn format_range_unified(start: usize, end: usize) -> String { let mut beginning = start + 1; let length = end - start; @@ -37,7 +34,6 @@ pub fn format_range_unified(start: usize, end: usize) -> String { format!("{},{}", beginning, length) } - pub fn format_range_context(start: usize, end: usize) -> String { let mut beginning = start + 1; let length = end - start; diff --git a/tests/tests.rs b/tests/tests.rs index 7a61a0f..e58132f 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,7 +1,7 @@ extern crate difflib; -use difflib::sequencematcher::{SequenceMatcher, Match, Opcode}; use difflib::differ::Differ; +use difflib::sequencematcher::{Match, Opcode, SequenceMatcher}; #[test] fn test_longest_match() { @@ -97,8 +97,10 @@ fn test_differ_compare() { let second_text = vec!["ore\n", "tree\n", "emu\n"]; let differ = Differ::new(); let result = differ.compare(&first_text, &second_text).join(""); - assert_eq!(result, - "- one\n? ^\n+ ore\n? ^\n- two\n- three\n? -\n+ tree\n+ emu\n"); + assert_eq!( + result, + "- one\n? ^\n+ ore\n? ^\n- two\n- three\n? -\n+ tree\n+ emu\n" + ); } fn is_junk_char(ch: &str) -> bool { @@ -115,8 +117,10 @@ fn test_differ_compare_with_func() { let mut differ = Differ::new(); differ.char_junk = Some(is_junk_char); let result = differ.compare(&first_text, &second_text).join(""); - assert_eq!(result, - "- one\n? ^\n+ ore\n? ^\n- two\n- three\n? -\n+ tree\n+ emu\n"); + assert_eq!( + result, + "- one\n? ^\n+ ore\n? ^\n- two\n- three\n? -\n+ tree\n+ emu\n" + ); } #[test] @@ -133,33 +137,39 @@ fn test_differ_restore() { fn test_unified_diff() { let first_text = "one two three four".split(" ").collect::>(); let second_text = "zero one tree four".split(" ").collect::>(); - let result = difflib::unified_diff(&first_text, - &second_text, - "Original", - "Current", - "2005-01-26 23:30:50", - "2010-04-02 10:20:52", - 3) - .join(""); - assert_eq!(result, - "--- Original\t2005-01-26 23:30:50\n+++ Current\t2010-04-02 10:20:52\n@@ -1,4 \ - +1,4 @@\n+zero one-two-three+tree four"); + let result = difflib::unified_diff( + &first_text, + &second_text, + "Original", + "Current", + "2005-01-26 23:30:50", + "2010-04-02 10:20:52", + 3, + ).join(""); + assert_eq!( + result, + "--- Original\t2005-01-26 23:30:50\n+++ Current\t2010-04-02 10:20:52\n@@ -1,4 \ + +1,4 @@\n+zero one-two-three+tree four" + ); } #[test] fn test_context_diff() { let first_text = "one two three four".split(" ").collect::>(); let second_text = "zero one tree four".split(" ").collect::>(); - let result = difflib::context_diff(&first_text, - &second_text, - "Original", - "Current", - "2005-01-26 23:30:50", - "2010-04-02 10:20:52", - 3) - .join(""); - assert_eq!(result, - "*** Original\t2005-01-26 23:30:50\n--- Current\t2010-04-02 \ - 10:20:52\n***************\n*** 1,4 ****\n one! two! three four--- 1,4 ----\n+ \ - zero one! tree four"); + let result = difflib::context_diff( + &first_text, + &second_text, + "Original", + "Current", + "2005-01-26 23:30:50", + "2010-04-02 10:20:52", + 3, + ).join(""); + assert_eq!( + result, + "*** Original\t2005-01-26 23:30:50\n--- Current\t2010-04-02 \ + 10:20:52\n***************\n*** 1,4 ****\n one! two! three four--- 1,4 ----\n+ \ + zero one! tree four" + ); } From e36a1d4645db54baf199a98e75ac66841fdf393d Mon Sep 17 00:00:00 2001 From: nmlgc Date: Thu, 28 Jun 2018 13:19:50 +0200 Subject: [PATCH 2/9] Fix mutability warnings --- src/sequencematcher.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sequencematcher.rs b/src/sequencematcher.rs index 4444baf..1800ac7 100644 --- a/src/sequencematcher.rs +++ b/src/sequencematcher.rs @@ -347,12 +347,12 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { } if codes.first().unwrap().tag == "equal" { - let mut opcode = codes.first_mut().unwrap(); + let opcode = codes.first_mut().unwrap(); opcode.first_start = max(opcode.first_start, opcode.first_end.saturating_sub(n)); opcode.second_start = max(opcode.second_start, opcode.second_end.saturating_sub(n)); } if codes.last().unwrap().tag == "equal" { - let mut opcode = codes.last_mut().unwrap(); + let opcode = codes.last_mut().unwrap(); opcode.first_end = min(opcode.first_start + n, opcode.first_end); opcode.second_end = min(opcode.second_start + n, opcode.second_end); } From 63e214364aecaa7143c0ee4eeaa4e332e67cecff Mon Sep 17 00:00:00 2001 From: nmlgc Date: Fri, 29 Jun 2018 11:58:31 +0200 Subject: [PATCH 3/9] Remove second_sequence_popular from the SequenceMatcher struct. It's never read from. --- src/sequencematcher.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/sequencematcher.rs b/src/sequencematcher.rs index 1800ac7..5555565 100644 --- a/src/sequencematcher.rs +++ b/src/sequencematcher.rs @@ -111,7 +111,6 @@ pub struct SequenceMatcher<'a, T: 'a + ?Sized + Sequence> { opcodes: Option>, is_junk: Option bool>, second_sequence_elements: HashMap<&'a str, Vec>, - second_sequence_popular: HashSet<&'a str>, } impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { @@ -123,7 +122,6 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { opcodes: None, is_junk: None, second_sequence_elements: HashMap::new(), - second_sequence_popular: HashSet::new(), }; matcher.set_seqs(first_sequence, second_sequence); matcher @@ -186,7 +184,6 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { } } self.second_sequence_elements = second_sequence_elements; - self.second_sequence_popular = popular; } pub fn find_longest_match( From e2bbf92e4cf849042f290d2ecfef82da002e8f37 Mon Sep 17 00:00:00 2001 From: nmlgc Date: Sat, 30 Jun 2018 12:40:50 +0200 Subject: [PATCH 4/9] chain_second_seq(): Filter out junk and popular elements in a more idiomatic way --- src/sequencematcher.rs | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/src/sequencematcher.rs b/src/sequencematcher.rs index 5555565..0dc645b 100644 --- a/src/sequencematcher.rs +++ b/src/sequencematcher.rs @@ -1,5 +1,5 @@ use std::cmp::{max, min}; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::fmt::Debug; use utils::calculate_ratio; @@ -159,29 +159,20 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { .or_insert(Vec::new()); counter.push(i); } - if self.is_junk.is_some() { - let mut junk = Vec::new(); - for element in second_sequence_elements.keys() { - if (self.is_junk.unwrap())(element) { - junk.push(element.clone()); - } - } - for element in &junk { - second_sequence_elements.remove(element); - } + if let Some(junk_func) = self.is_junk { + second_sequence_elements = second_sequence_elements + .into_iter() + .filter(|&(element, _)| !junk_func(element)) + .collect(); } - let mut popular = HashSet::new(); + // Filter out popular elements let len = second_sequence.len(); if len >= 200 { let test_len = (len as f32 / 100.0).floor() as usize + 1; - for (element, indexes) in second_sequence_elements.iter() { - if indexes.len() > test_len { - popular.insert(element.clone()); - } - } - for element in &popular { - second_sequence_elements.remove(element); - } + second_sequence_elements = second_sequence_elements + .into_iter() + .filter(|&(_, ref indexes)| indexes.len() > test_len) + .collect(); } self.second_sequence_elements = second_sequence_elements; } From 282c139cc03efbee6ff766b6b3af97ac6c988c76 Mon Sep 17 00:00:00 2001 From: nmlgc Date: Sun, 1 Jul 2018 18:41:22 +0200 Subject: [PATCH 5/9] Remove generic types from Differ The entire implementation pretty much only makes sense for string slices, and that was all the original code supported anyway. --- src/differ.rs | 50 ++++++++++++++++-------------------------- src/sequencematcher.rs | 13 +++++++++++ 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/src/differ.rs b/src/differ.rs index a021e2f..6eb5eba 100644 --- a/src/differ.rs +++ b/src/differ.rs @@ -1,4 +1,4 @@ -use sequencematcher::{Sequence, SequenceMatcher}; +use sequencematcher::SequenceMatcher; use std::cmp; use utils::{count_leading, str_with_similar_chars}; @@ -15,11 +15,7 @@ impl Differ { } } - pub fn compare( - &self, - first_sequence: &T, - second_sequence: &T, - ) -> Vec { + pub fn compare(&self, first_sequence: &[&str], second_sequence: &[&str]) -> Vec { let mut matcher = SequenceMatcher::new(first_sequence, second_sequence); matcher.set_is_junk(self.line_junk); let mut res = Vec::new(); @@ -54,16 +50,10 @@ impl Differ { res } - fn dump( - &self, - tag: &str, - sequence: &T, - start: usize, - end: usize, - ) -> Vec { + fn dump(&self, tag: &str, sequence: &[&str], start: usize, end: usize) -> Vec { let mut res = Vec::new(); for i in start..end { - match sequence.at_index(i) { + match sequence.get(i) { Some(s) => res.push(format!("{} {}", tag, s)), None => {} } @@ -71,12 +61,12 @@ impl Differ { res } - fn plain_replace( + fn plain_replace( &self, - first_sequence: &T, + first_sequence: &[&str], first_start: usize, first_end: usize, - second_sequence: &T, + second_sequence: &[&str], second_start: usize, second_end: usize, ) -> Vec { @@ -98,12 +88,12 @@ impl Differ { first } - fn fancy_replace( + fn fancy_replace( &self, - first_sequence: &T, + first_sequence: &[&str], first_start: usize, first_end: usize, - second_sequence: &T, + second_sequence: &[&str], second_start: usize, second_end: usize, ) -> Vec { @@ -114,9 +104,9 @@ impl Differ { let mut eqi: Option = None; let mut eqj: Option = None; for j in second_start..second_end { - second_sequence_str = second_sequence.at_index(j).unwrap(); + second_sequence_str = &second_sequence[j]; for i in first_start..first_end { - first_sequence_str = first_sequence.at_index(i).unwrap(); + first_sequence_str = &first_sequence[i]; if first_sequence_str == second_sequence_str { if eqi.is_none() { eqi = Some(i); @@ -124,7 +114,7 @@ impl Differ { } continue; } - let mut cruncher = SequenceMatcher::new(first_sequence_str, second_sequence_str); + let mut cruncher = SequenceMatcher::new(*first_sequence_str, *second_sequence_str); cruncher.set_is_junk(self.char_junk); if cruncher.ratio() > best_ratio { best_ratio = cruncher.ratio(); @@ -164,13 +154,11 @@ impl Differ { ).iter() .cloned(), ); - let (first_element, second_element) = ( - first_sequence.at_index(best_i).unwrap(), - second_sequence.at_index(best_j).unwrap(), - ); + let first_element = &first_sequence[best_i]; + let second_element = &second_sequence[best_j]; if eqi.is_none() { let (mut first_tag, mut second_tag) = (String::new(), String::new()); - let mut cruncher = SequenceMatcher::new(first_element, second_element); + let mut cruncher = SequenceMatcher::new(*first_element, second_element); cruncher.set_is_junk(self.char_junk); for opcode in &cruncher.get_opcodes() { let (first_length, second_length) = ( @@ -219,12 +207,12 @@ impl Differ { res } - fn fancy_helper( + fn fancy_helper( &self, - first_sequence: &T, + first_sequence: &[&str], first_start: usize, first_end: usize, - second_sequence: &T, + second_sequence: &[&str], second_start: usize, second_end: usize, ) -> Vec { diff --git a/src/sequencematcher.rs b/src/sequencematcher.rs index 0dc645b..4256507 100644 --- a/src/sequencematcher.rs +++ b/src/sequencematcher.rs @@ -104,6 +104,19 @@ impl<'a> Sequence for Vec { } } +impl<'a> Sequence for [&'a str] { + fn len(&self) -> usize { + self.len() + } + + fn at_index(&self, index: usize) -> Option<&str> { + if index < self.len() { + return Some(self[index]); + } + None + } +} + pub struct SequenceMatcher<'a, T: 'a + ?Sized + Sequence> { first_sequence: &'a T, second_sequence: &'a T, From de82e46021d1a770ec6ac8de7ada63bb14eb9028 Mon Sep 17 00:00:00 2001 From: nmlgc Date: Mon, 2 Jul 2018 20:09:51 +0200 Subject: [PATCH 6/9] Apply all Clippy lints that don't break the API --- src/differ.rs | 64 +++++++++++++++++----------------- src/lib.rs | 4 +-- src/sequencematcher.rs | 79 ++++++++++++++++++++---------------------- src/utils.rs | 4 +-- 4 files changed, 73 insertions(+), 78 deletions(-) diff --git a/src/differ.rs b/src/differ.rs index 6eb5eba..6b87e01 100644 --- a/src/differ.rs +++ b/src/differ.rs @@ -2,6 +2,7 @@ use sequencematcher::SequenceMatcher; use std::cmp; use utils::{count_leading, str_with_similar_chars}; +#[derive(Default)] pub struct Differ { pub line_junk: Option bool>, pub char_junk: Option bool>, @@ -53,9 +54,8 @@ impl Differ { fn dump(&self, tag: &str, sequence: &[&str], start: usize, end: usize) -> Vec { let mut res = Vec::new(); for i in start..end { - match sequence.get(i) { - Some(s) => res.push(format!("{} {}", tag, s)), - None => {} + if let Some(s) = sequence.get(i) { + res.push(format!("{} {}", tag, s)) } } res @@ -73,15 +73,17 @@ impl Differ { if !(first_start < first_end && second_start < second_end) { return Vec::new(); } - let mut first; - let second; - 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); + let (mut first, second) = if second_end - second_start < first_end - first_start { + ( + self.dump("+", second_sequence, second_start, second_end), + 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); - } + ( + self.dump("-", first_sequence, first_start, first_end), + self.dump("+", second_sequence, second_start, second_end), + ) + }; for s in second { first.push(s); } @@ -100,13 +102,20 @@ impl Differ { let mut res = Vec::new(); let (mut best_ratio, cutoff) = (0.74, 0.75); let (mut best_i, mut best_j) = (0, 0); - let (mut second_sequence_str, mut first_sequence_str); let mut eqi: Option = None; let mut eqj: Option = None; - for j in second_start..second_end { - second_sequence_str = &second_sequence[j]; - for i in first_start..first_end { - first_sequence_str = &first_sequence[i]; + for (j, second_sequence_str) in second_sequence + .iter() + .enumerate() + .take(second_end) + .skip(second_start) + { + for (i, first_sequence_str) in first_sequence + .iter() + .enumerate() + .take(second_end) + .skip(second_start) + { if first_sequence_str == second_sequence_str { if eqi.is_none() { eqi = Some(i); @@ -254,40 +263,31 @@ impl Differ { common = cmp::min(common, count_leading(first_tags.split_at(common).0, ' ')); first_tags = first_tags.split_at(common).1.trim_right(); second_tags = second_tags.split_at(common).1.trim_right(); - let mut s = String::from(format!("- {}", first_line)); + let mut s = format!("- {}", first_line); res.push(s); if first_tags != "" { - s = String::from(format!( - "? {}{}\n", - str_with_similar_chars('\t', common), - first_tags - )); + s = format!("? {}{}\n", str_with_similar_chars('\t', common), first_tags); res.push(s); } - s = String::from(format!("+ {}", second_line)); + s = format!("+ {}", second_line); res.push(s); if second_tags != "" { - s = String::from(format!( + s = format!( "? {}{}\n", str_with_similar_chars('\t', common), second_tags - )); + ); res.push(s); } res } - pub fn restore(delta: &Vec, which: usize) -> Vec { + pub fn restore(delta: &[String], which: usize) -> Vec { if !(which == 1 || which == 2) { panic!("Second parameter must be 1 or 2"); } let mut res = Vec::new(); - let tag; - if which == 1 { - tag = "- ".to_string(); - } else { - tag = "+ ".to_string(); - } + let tag = if which == 1 { "- " } else { "+ " }.to_string(); let prefixes = vec![tag, " ".to_string()]; for line in delta { for prefix in &prefixes { diff --git a/src/lib.rs b/src/lib.rs index 95cb23a..40801fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -122,7 +122,7 @@ pub fn context_diff( for i in opcode.first_start..opcode.first_end { res.push(format!( "{}{}", - prefix.get(&opcode.tag).unwrap(), + &prefix[&opcode.tag], first_sequence.at_index(i).unwrap() )); } @@ -144,7 +144,7 @@ pub fn context_diff( for i in opcode.second_start..opcode.second_end { res.push(format!( "{}{}", - prefix.get(&opcode.tag).unwrap(), + &prefix[&opcode.tag], second_sequence.at_index(i).unwrap() )); } diff --git a/src/sequencematcher.rs b/src/sequencematcher.rs index 4256507..a0416df 100644 --- a/src/sequencematcher.rs +++ b/src/sequencematcher.rs @@ -13,9 +13,9 @@ pub struct Match { impl Match { fn new(first_start: usize, second_start: usize, size: usize) -> Match { Match { - first_start: first_start, - second_start: second_start, - size: size, + first_start, + second_start, + size, } } } @@ -38,11 +38,11 @@ impl Opcode { second_end: usize, ) -> Opcode { Opcode { - tag: tag, - first_start: first_start, - first_end: first_end, - second_start: second_start, - second_end: second_end, + tag, + first_start, + first_end, + second_start, + second_end, } } } @@ -129,8 +129,8 @@ pub struct SequenceMatcher<'a, T: 'a + ?Sized + Sequence> { impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { pub fn new(first_sequence: &'a T, second_sequence: &'a T) -> SequenceMatcher<'a, T> { let mut matcher = SequenceMatcher { - first_sequence: first_sequence, - second_sequence: second_sequence, + first_sequence, + second_sequence, matching_blocks: None, opcodes: None, is_junk: None, @@ -169,7 +169,7 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { for i in 0..second_sequence.len() { let mut counter = second_sequence_elements .entry(second_sequence.at_index(i).unwrap()) - .or_insert(Vec::new()); + .or_insert_with(Vec::new); counter.push(i); } if let Some(junk_func) = self.is_junk { @@ -204,35 +204,30 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { let mut j2len: HashMap = HashMap::new(); for i in first_start..first_end { let mut new_j2len: HashMap = HashMap::new(); - match second_sequence_elements.get(first_sequence.at_index(i).unwrap()) { - Some(indexes) => { - for j in indexes { - let j = j.clone(); - if j < second_start { - continue; - }; - if j >= second_end { - break; - }; - let mut size = 0; - if j > 0 { - match j2len.get(&(j - 1)) { - Some(k) => { - size = k.clone(); - } - None => {} - } - } - size += 1; - new_j2len.insert(j, size); - if size > best_size { - best_i = i + 1 - size; - best_j = j + 1 - size; - best_size = size; + if let Some(indexes) = second_sequence_elements.get(first_sequence.at_index(i).unwrap()) + { + for j in indexes { + let j = *j; + if j < second_start { + continue; + }; + if j >= second_end { + break; + }; + let mut size = 0; + if j > 0 { + if let Some(k) = j2len.get(&(j - 1)) { + size = *k; } } + size += 1; + new_j2len.insert(j, size); + if size > best_size { + best_i = i + 1 - size; + best_j = j + 1 - size; + best_size = size; + } } - None => {} } j2len = new_j2len; } @@ -241,9 +236,9 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { && best_j > second_start && first_sequence.at_index(best_i - 1) == second_sequence.at_index(best_j - 1) { - best_i = best_i - 1; - best_j = best_j - 1; - best_size = best_size + 1; + best_i -= 1; + best_j -= 1; + best_size += 1; } while best_i + best_size < first_end && best_j + best_size < second_end && first_sequence.at_index(best_i + best_size) @@ -337,7 +332,7 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { } } self.opcodes = Some(opcodes); - return self.opcodes.as_ref().unwrap().clone(); + self.opcodes.as_ref().unwrap().clone() } pub fn get_grouped_opcodes(&mut self, n: usize) -> Vec> { @@ -382,7 +377,7 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { code.second_end, )); } - if !group.is_empty() && !(group.len() == 1 && group.first().unwrap().tag == "equal") { + if !(group.len() == 1 && group.first().unwrap().tag == "equal") || group.is_empty() { res.push(group.clone()); } res diff --git a/src/utils.rs b/src/utils.rs index c22a9d2..b711bf8 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,7 +2,7 @@ pub fn calculate_ratio(matches: usize, length: usize) -> f32 { if length != 0 { return 2.0 * matches as f32 / length as f32; } - return 1.0; + 1.0 } pub fn str_with_similar_chars(c: char, length: usize) -> String { @@ -19,7 +19,7 @@ pub fn count_leading(line: &str, c: char) -> usize { while (i < n) && line[i] == c { i += 1; } - return i; + i } pub fn format_range_unified(start: usize, end: usize) -> String { From 6fd59b0c089e3baece7ac200b8220ce25d025e02 Mon Sep 17 00:00:00 2001 From: nmlgc Date: Tue, 3 Jul 2018 20:28:11 +0200 Subject: [PATCH 7/9] Make SequenceMatcher generic over all slice types --- Cargo.toml | 2 +- README.md | 6 +-- examples/example.rs | 4 +- src/differ.rs | 15 ++++-- src/lib.rs | 65 ++++++++++++++--------- src/sequencematcher.rs | 117 ++++++++++------------------------------- tests/tests.rs | 12 ++--- 7 files changed, 89 insertions(+), 132 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 13fce5e..2edf6df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "difflib" -version = "0.3.1" +version = "0.4.0" authors = ["Dima Kudosh "] description = "Port of Python's difflib library to Rust." documentation = "https://github.com/DimaKudosh/difflib/wiki" diff --git a/README.md b/README.md index 37334a6..45f8be7 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Simply add difflib to your dependencies block in Cargo.toml ```rust [dependencies] -difflib = "0.3.0" +difflib = "0.4.0" ``` ## Documentation @@ -65,7 +65,7 @@ fn main() { } //SequenceMatcher examples - let mut matcher = SequenceMatcher::new("one two three four", "zero one tree four"); + let mut matcher = SequenceMatcher::new(b"one two three four", b"zero one tree four"); let m = matcher.find_longest_match(0, 18, 0, 18); println!("{:?}", m); let all_matches = matcher.get_matching_blocks(); @@ -76,7 +76,7 @@ fn main() { println!("{:?}", grouped_opcodes); let ratio = matcher.ratio(); println!("{:?}", ratio); - matcher.set_seqs("aaaaa", "aaaab"); + matcher.set_seqs(b"aaaaa", b"aaaab"); println!("{:?}", matcher.ratio()); } ``` diff --git a/examples/example.rs b/examples/example.rs index 3f06d0a..d643be1 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -47,7 +47,7 @@ fn main() { } //SequenceMatcher examples - let mut matcher = SequenceMatcher::new("one two three four", "zero one tree four"); + let mut matcher = SequenceMatcher::new(b"one two three four", b"zero one tree four"); let m = matcher.find_longest_match(0, 18, 0, 18); println!("{:?}", m); let all_matches = matcher.get_matching_blocks(); @@ -58,6 +58,6 @@ fn main() { println!("{:?}", grouped_opcodes); let ratio = matcher.ratio(); println!("{:?}", ratio); - matcher.set_seqs("aaaaa", "aaaab"); + matcher.set_seqs(b"aaaaa", b"aaaab"); println!("{:?}", matcher.ratio()); } diff --git a/src/differ.rs b/src/differ.rs index 6b87e01..5caef9a 100644 --- a/src/differ.rs +++ b/src/differ.rs @@ -4,8 +4,8 @@ use utils::{count_leading, str_with_similar_chars}; #[derive(Default)] pub struct Differ { - pub line_junk: Option bool>, - pub char_junk: Option bool>, + pub line_junk: Option bool>, + pub char_junk: Option bool>, } impl Differ { @@ -123,7 +123,12 @@ impl Differ { } continue; } - let mut cruncher = SequenceMatcher::new(*first_sequence_str, *second_sequence_str); + let (first_sequence_chars, second_sequence_chars) = ( + first_sequence_str.chars().collect::>(), + second_sequence_str.chars().collect::>(), + ); + let mut cruncher = + SequenceMatcher::new(&first_sequence_chars, &second_sequence_chars); cruncher.set_is_junk(self.char_junk); if cruncher.ratio() > best_ratio { best_ratio = cruncher.ratio(); @@ -167,7 +172,9 @@ impl Differ { let second_element = &second_sequence[best_j]; if eqi.is_none() { let (mut first_tag, mut second_tag) = (String::new(), String::new()); - let mut cruncher = SequenceMatcher::new(*first_element, second_element); + let first_element_chars: Vec = first_element.chars().collect(); + let second_element_chars: Vec = second_element.chars().collect(); + let mut cruncher = SequenceMatcher::new(&first_element_chars, &second_element_chars); cruncher.set_is_junk(self.char_junk); for opcode in &cruncher.get_opcodes() { let (first_length, second_length) = ( diff --git a/src/lib.rs b/src/lib.rs index 40801fe..2c25f15 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ mod utils; use sequencematcher::{Sequence, SequenceMatcher}; use std::collections::HashMap; +use std::fmt::Display; use utils::{format_range_context, format_range_unified}; pub fn get_close_matches<'a>( @@ -16,9 +17,9 @@ pub fn get_close_matches<'a>( panic!("Cutoff must be greater than 0.0 and lower than 1.0"); } let mut res: Vec<(f32, &str)> = Vec::new(); - let mut matcher = SequenceMatcher::new("", word); + let mut matcher = SequenceMatcher::new(b"", word.as_bytes()); for i in &possibilities { - matcher.set_first_seq(i); + matcher.set_first_seq(i.as_bytes()); let ratio = matcher.ratio(); if ratio >= cutoff { res.push((ratio, i)); @@ -29,9 +30,9 @@ pub fn get_close_matches<'a>( res.iter().map(|x| x.1).collect() } -pub fn unified_diff( - first_sequence: &T, - second_sequence: &T, +pub fn unified_diff( + first_sequence: &[T], + second_sequence: &[T], from_file: &str, to_file: &str, from_file_date: &str, @@ -59,19 +60,31 @@ pub fn unified_diff( )); for code in group { if code.tag == "equal" { - for i in code.first_start..code.first_end { - res.push(format!(" {}", first_sequence.at_index(i).unwrap())); + for item in first_sequence + .iter() + .take(code.first_end) + .skip(code.first_start) + { + res.push(format!(" {}", item)); } continue; } if code.tag == "replace" || code.tag == "delete" { - for i in code.first_start..code.first_end { - res.push(format!("-{}", first_sequence.at_index(i).unwrap())); + for item in first_sequence + .iter() + .take(code.first_end) + .skip(code.first_start) + { + res.push(format!("-{}", item)); } } if code.tag == "replace" || code.tag == "insert" { - for i in code.second_start..code.second_end { - res.push(format!("+{}", second_sequence.at_index(i).unwrap())); + for item in second_sequence + .iter() + .take(code.second_end) + .skip(code.second_start) + { + res.push(format!("+{}", item)); } } } @@ -79,9 +92,9 @@ pub fn unified_diff( res } -pub fn context_diff( - first_sequence: &T, - second_sequence: &T, +pub fn context_diff( + first_sequence: &[T], + second_sequence: &[T], from_file: &str, to_file: &str, from_file_date: &str, @@ -119,12 +132,12 @@ pub fn context_diff( if any { for opcode in group { if opcode.tag != "insert" { - for i in opcode.first_start..opcode.first_end { - res.push(format!( - "{}{}", - &prefix[&opcode.tag], - first_sequence.at_index(i).unwrap() - )); + for item in first_sequence + .iter() + .take(opcode.first_end) + .skip(opcode.first_start) + { + res.push(format!("{}{}", &prefix[&opcode.tag], item)); } } } @@ -141,12 +154,12 @@ pub fn context_diff( if any { for opcode in group { if opcode.tag != "delete" { - for i in opcode.second_start..opcode.second_end { - res.push(format!( - "{}{}", - &prefix[&opcode.tag], - second_sequence.at_index(i).unwrap() - )); + for item in second_sequence + .iter() + .take(opcode.second_end) + .skip(opcode.second_start) + { + res.push(format!("{}{}", prefix[&opcode.tag], item)); } } } diff --git a/src/sequencematcher.rs b/src/sequencematcher.rs index a0416df..4cfc8b8 100644 --- a/src/sequencematcher.rs +++ b/src/sequencematcher.rs @@ -1,6 +1,6 @@ use std::cmp::{max, min}; use std::collections::HashMap; -use std::fmt::Debug; +use std::hash::Hash; use utils::calculate_ratio; #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] @@ -47,87 +47,20 @@ impl Opcode { } } -pub trait Sequence: Debug { - fn len(&self) -> usize; - fn at_index(&self, index: usize) -> Option<&str>; -} +pub trait Sequence: Eq + Hash {} +impl Sequence for T {} -impl Sequence for str { - fn len(&self) -> usize { - self.len() - } - - fn at_index(&self, index: usize) -> Option<&str> { - if index > self.len() { - return None; - } - unsafe { Some(self.slice_unchecked(index, index + 1)) } - } -} - -impl<'a> Sequence for Vec<&'a str> { - fn len(&self) -> usize { - self.len() - } - - fn at_index(&self, index: usize) -> Option<&str> { - if index < self.len() { - return Some(self[index]); - } - None - } -} - -impl Sequence for String { - fn len(&self) -> usize { - self.len() - } - - fn at_index(&self, index: usize) -> Option<&str> { - if index > self.len() { - return None; - } - unsafe { Some(self.slice_unchecked(index, index + 1)) } - } -} - -impl<'a> Sequence for Vec { - fn len(&self) -> usize { - self.len() - } - - fn at_index(&self, index: usize) -> Option<&str> { - if index < self.len() { - return Some(&self[index]); - } - None - } -} - -impl<'a> Sequence for [&'a str] { - fn len(&self) -> usize { - self.len() - } - - fn at_index(&self, index: usize) -> Option<&str> { - if index < self.len() { - return Some(self[index]); - } - None - } -} - -pub struct SequenceMatcher<'a, T: 'a + ?Sized + Sequence> { - first_sequence: &'a T, - second_sequence: &'a T, +pub struct SequenceMatcher<'a, T: 'a + Sequence> { + first_sequence: &'a [T], + second_sequence: &'a [T], matching_blocks: Option>, opcodes: Option>, - is_junk: Option bool>, - second_sequence_elements: HashMap<&'a str, Vec>, + is_junk: Option bool>, + second_sequence_elements: HashMap<&'a T, Vec>, } -impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { - pub fn new(first_sequence: &'a T, second_sequence: &'a T) -> SequenceMatcher<'a, T> { +impl<'a, T: Sequence> SequenceMatcher<'a, T> { + pub fn new(first_sequence: &'a [T], second_sequence: &'a [T]) -> SequenceMatcher<'a, T> { let mut matcher = SequenceMatcher { first_sequence, second_sequence, @@ -140,23 +73,23 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { matcher } - pub fn set_is_junk(&mut self, is_junk: Option bool>) { + pub fn set_is_junk(&mut self, is_junk: Option bool>) { self.is_junk = is_junk; self.set_second_seq(self.second_sequence); } - pub fn set_seqs(&mut self, first_sequence: &'a T, second_sequence: &'a T) { + pub fn set_seqs(&mut self, first_sequence: &'a [T], second_sequence: &'a [T]) { self.set_first_seq(first_sequence); self.set_second_seq(second_sequence); } - pub fn set_first_seq(&mut self, sequence: &'a T) { + pub fn set_first_seq(&mut self, sequence: &'a [T]) { self.first_sequence = sequence; self.matching_blocks = None; self.opcodes = None; } - pub fn set_second_seq(&mut self, sequence: &'a T) { + pub fn set_second_seq(&mut self, sequence: &'a [T]) { self.second_sequence = sequence; self.matching_blocks = None; self.opcodes = None; @@ -166,9 +99,9 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { fn chain_second_seq(&mut self) { let second_sequence = self.second_sequence; let mut second_sequence_elements = HashMap::new(); - for i in 0..second_sequence.len() { + for (i, item) in second_sequence.iter().enumerate() { let mut counter = second_sequence_elements - .entry(second_sequence.at_index(i).unwrap()) + .entry(item) .or_insert_with(Vec::new); counter.push(i); } @@ -202,10 +135,14 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { let second_sequence_elements = &self.second_sequence_elements; let (mut best_i, mut best_j, mut best_size) = (first_start, second_start, 0); let mut j2len: HashMap = HashMap::new(); - for i in first_start..first_end { + for (i, item) in first_sequence + .iter() + .enumerate() + .take(first_end) + .skip(first_start) + { let mut new_j2len: HashMap = HashMap::new(); - if let Some(indexes) = second_sequence_elements.get(first_sequence.at_index(i).unwrap()) - { + if let Some(indexes) = second_sequence_elements.get(item) { for j in indexes { let j = *j; if j < second_start { @@ -234,15 +171,15 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { for _ in 0..2 { while best_i > first_start && best_j > second_start - && first_sequence.at_index(best_i - 1) == second_sequence.at_index(best_j - 1) + && first_sequence.get(best_i - 1) == second_sequence.get(best_j - 1) { best_i -= 1; best_j -= 1; best_size += 1; } - while best_i + best_size < first_end && best_j + best_size < second_end - && first_sequence.at_index(best_i + best_size) - == second_sequence.at_index(best_j + best_size) + while best_i + best_size < first_end + && best_j + best_size < second_end + && first_sequence.get(best_i + best_size) == second_sequence.get(best_j + best_size) { best_size += 1; } diff --git a/tests/tests.rs b/tests/tests.rs index e58132f..3de23df 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -5,7 +5,7 @@ use difflib::sequencematcher::{Match, Opcode, SequenceMatcher}; #[test] fn test_longest_match() { - let matcher = SequenceMatcher::new(" abcd", "abcd abcd"); + let matcher = SequenceMatcher::new(b" abcd", b"abcd abcd"); let m = matcher.find_longest_match(0, 5, 0, 9); assert_eq!(m.first_start, 0); assert_eq!(m.second_start, 4); @@ -14,7 +14,7 @@ fn test_longest_match() { #[test] fn test_all_matches() { - let mut matcher = SequenceMatcher::new("abxcd", "abcd"); + let mut matcher = SequenceMatcher::new(b"abxcd", b"abcd"); let result = matcher.get_matching_blocks(); let mut expected_result = Vec::new(); expected_result.push(Match { @@ -37,7 +37,7 @@ fn test_all_matches() { #[test] fn test_get_opcodes() { - let mut matcher = SequenceMatcher::new("qabxcd", "abycdf"); + let mut matcher = SequenceMatcher::new(b"qabxcd", b"abycdf"); let result = matcher.get_opcodes(); let mut expected_result = Vec::new(); expected_result.push(Opcode { @@ -80,7 +80,7 @@ fn test_get_opcodes() { #[test] fn test_ratio() { - let mut matcher = SequenceMatcher::new("abcd", "bcde"); + let mut matcher = SequenceMatcher::new(b"abcd", b"bcde"); assert_eq!(matcher.ratio(), 0.75); } @@ -103,8 +103,8 @@ fn test_differ_compare() { ); } -fn is_junk_char(ch: &str) -> bool { - if ch == " " || ch == "\t" { +fn is_junk_char(ch: &char) -> bool { + if *ch == ' ' || *ch == '\t' { return true; } false From 06afc1879b97b5487079e8124d09eec9e35e84c7 Mon Sep 17 00:00:00 2001 From: nmlgc Date: Thu, 5 Jul 2018 21:46:43 +0200 Subject: [PATCH 8/9] Implicitly convert &str to [u8] for compatibility with existing usage code Thanks to stephaneyfx from the #rust IRC channel for showing me that this needs separate type parameters on the functions in order to compile. The only API change that can't be helped are the `line_junk` and `char_junk` function pointers in the Differ struct, which now take `&&str` and `&char` parameters, respectively. This is probably for the better, since `char_junk` now operates on Unicode scalar values rather than the single UTF-8 bytes disguised as `&str` that were previously retrieved via the unsafe `slice_unchecked()` function. --- README.md | 4 ++-- examples/example.rs | 4 ++-- src/lib.rs | 4 ++-- src/sequencematcher.rs | 32 +++++++++++++++++++++++--------- tests/tests.rs | 8 ++++---- 5 files changed, 33 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 45f8be7..b881fd2 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ fn main() { } //SequenceMatcher examples - let mut matcher = SequenceMatcher::new(b"one two three four", b"zero one tree four"); + let mut matcher = SequenceMatcher::new("one two three four", "zero one tree four"); let m = matcher.find_longest_match(0, 18, 0, 18); println!("{:?}", m); let all_matches = matcher.get_matching_blocks(); @@ -76,7 +76,7 @@ fn main() { println!("{:?}", grouped_opcodes); let ratio = matcher.ratio(); println!("{:?}", ratio); - matcher.set_seqs(b"aaaaa", b"aaaab"); + matcher.set_seqs("aaaaa", "aaaab"); println!("{:?}", matcher.ratio()); } ``` diff --git a/examples/example.rs b/examples/example.rs index d643be1..3f06d0a 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -47,7 +47,7 @@ fn main() { } //SequenceMatcher examples - let mut matcher = SequenceMatcher::new(b"one two three four", b"zero one tree four"); + let mut matcher = SequenceMatcher::new("one two three four", "zero one tree four"); let m = matcher.find_longest_match(0, 18, 0, 18); println!("{:?}", m); let all_matches = matcher.get_matching_blocks(); @@ -58,6 +58,6 @@ fn main() { println!("{:?}", grouped_opcodes); let ratio = matcher.ratio(); println!("{:?}", ratio); - matcher.set_seqs(b"aaaaa", b"aaaab"); + matcher.set_seqs("aaaaa", "aaaab"); println!("{:?}", matcher.ratio()); } diff --git a/src/lib.rs b/src/lib.rs index 2c25f15..ca6b1cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,9 +17,9 @@ pub fn get_close_matches<'a>( panic!("Cutoff must be greater than 0.0 and lower than 1.0"); } let mut res: Vec<(f32, &str)> = Vec::new(); - let mut matcher = SequenceMatcher::new(b"", word.as_bytes()); + let mut matcher = SequenceMatcher::new("", word); for i in &possibilities { - matcher.set_first_seq(i.as_bytes()); + matcher.set_first_seq(i); let ratio = matcher.ratio(); if ratio >= cutoff { res.push((ratio, i)); diff --git a/src/sequencematcher.rs b/src/sequencematcher.rs index 4cfc8b8..4157808 100644 --- a/src/sequencematcher.rs +++ b/src/sequencematcher.rs @@ -60,10 +60,13 @@ pub struct SequenceMatcher<'a, T: 'a + Sequence> { } impl<'a, T: Sequence> SequenceMatcher<'a, T> { - pub fn new(first_sequence: &'a [T], second_sequence: &'a [T]) -> SequenceMatcher<'a, T> { + pub fn new(first_sequence: &'a S, second_sequence: &'a S) -> SequenceMatcher<'a, T> + where + S: AsRef<[T]> + ?Sized, + { let mut matcher = SequenceMatcher { - first_sequence, - second_sequence, + first_sequence: first_sequence.as_ref(), + second_sequence: second_sequence.as_ref(), matching_blocks: None, opcodes: None, is_junk: None, @@ -75,22 +78,33 @@ impl<'a, T: Sequence> SequenceMatcher<'a, T> { pub fn set_is_junk(&mut self, is_junk: Option bool>) { self.is_junk = is_junk; - self.set_second_seq(self.second_sequence); + self.matching_blocks = None; + self.opcodes = None; + self.chain_second_seq(); } - pub fn set_seqs(&mut self, first_sequence: &'a [T], second_sequence: &'a [T]) { + pub fn set_seqs(&mut self, first_sequence: &'a S, second_sequence: &'a S) + where + S: AsRef<[T]> + ?Sized, + { self.set_first_seq(first_sequence); self.set_second_seq(second_sequence); } - pub fn set_first_seq(&mut self, sequence: &'a [T]) { - self.first_sequence = sequence; + pub fn set_first_seq(&mut self, sequence: &'a S) + where + S: AsRef<[T]> + ?Sized, + { + self.first_sequence = sequence.as_ref(); self.matching_blocks = None; self.opcodes = None; } - pub fn set_second_seq(&mut self, sequence: &'a [T]) { - self.second_sequence = sequence; + pub fn set_second_seq(&mut self, sequence: &'a S) + where + S: AsRef<[T]> + ?Sized, + { + self.second_sequence = sequence.as_ref(); self.matching_blocks = None; self.opcodes = None; self.chain_second_seq(); diff --git a/tests/tests.rs b/tests/tests.rs index 3de23df..f01a315 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -5,7 +5,7 @@ use difflib::sequencematcher::{Match, Opcode, SequenceMatcher}; #[test] fn test_longest_match() { - let matcher = SequenceMatcher::new(b" abcd", b"abcd abcd"); + let matcher = SequenceMatcher::new(" abcd", "abcd abcd"); let m = matcher.find_longest_match(0, 5, 0, 9); assert_eq!(m.first_start, 0); assert_eq!(m.second_start, 4); @@ -14,7 +14,7 @@ fn test_longest_match() { #[test] fn test_all_matches() { - let mut matcher = SequenceMatcher::new(b"abxcd", b"abcd"); + let mut matcher = SequenceMatcher::new("abxcd", "abcd"); let result = matcher.get_matching_blocks(); let mut expected_result = Vec::new(); expected_result.push(Match { @@ -37,7 +37,7 @@ fn test_all_matches() { #[test] fn test_get_opcodes() { - let mut matcher = SequenceMatcher::new(b"qabxcd", b"abycdf"); + let mut matcher = SequenceMatcher::new("qabxcd", "abycdf"); let result = matcher.get_opcodes(); let mut expected_result = Vec::new(); expected_result.push(Opcode { @@ -80,7 +80,7 @@ fn test_get_opcodes() { #[test] fn test_ratio() { - let mut matcher = SequenceMatcher::new(b"abcd", b"bcde"); + let mut matcher = SequenceMatcher::new("abcd", "bcde"); assert_eq!(matcher.ratio(), 0.75); } From 31ea2a244aa4ccd5954af544998426be13392341 Mon Sep 17 00:00:00 2001 From: nmlgc Date: Fri, 6 Jul 2018 15:07:32 +0200 Subject: [PATCH 9/9] Test SequenceMatcher with integer slices --- tests/tests.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/tests.rs b/tests/tests.rs index f01a315..8f49de9 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -173,3 +173,22 @@ fn test_context_diff() { zero one! tree four" ); } + +#[test] +fn test_integer_slice() { + let s1 = vec![1, 2, 3, 4, 5]; + let s2 = vec![5, 4, 3, 2, 1]; + let result = SequenceMatcher::new(&s1, &s2).get_matching_blocks(); + let mut expected_result = Vec::new(); + expected_result.push(Match { + first_start: 0, + second_start: 4, + size: 1, + }); + expected_result.push(Match { + first_start: 5, + second_start: 5, + size: 0, + }); + assert_eq!(result, expected_result); +}