Merge pull request #5 from nmlgc/master

Make SequenceMatcher generic over all slice types
This commit is contained in:
Dima Kudosh
2018-07-22 15:36:05 +03:00
committed by GitHub
8 changed files with 477 additions and 411 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "difflib" name = "difflib"
version = "0.3.1" version = "0.4.0"
authors = ["Dima Kudosh <dimakudosh@gmail.com>"] authors = ["Dima Kudosh <dimakudosh@gmail.com>"]
description = "Port of Python's difflib library to Rust." description = "Port of Python's difflib library to Rust."
documentation = "https://github.com/DimaKudosh/difflib/wiki" documentation = "https://github.com/DimaKudosh/difflib/wiki"
+19 -6
View File
@@ -8,7 +8,7 @@ Simply add difflib to your dependencies block in Cargo.toml
```rust ```rust
[dependencies] [dependencies]
difflib = "0.3.0" difflib = "0.4.0"
``` ```
## Documentation ## Documentation
@@ -21,20 +21,33 @@ extern crate difflib;
use difflib::differ::Differ; use difflib::differ::Differ;
use difflib::sequencematcher::SequenceMatcher; use difflib::sequencematcher::SequenceMatcher;
fn main() { fn main() {
// unified_diff // unified_diff
let first_text = "one two three four".split(" ").collect::<Vec<&str>>(); let first_text = "one two three four".split(" ").collect::<Vec<&str>>();
let second_text = "zero one tree four".split(" ").collect::<Vec<&str>>(); let second_text = "zero one tree four".split(" ").collect::<Vec<&str>>();
let diff = difflib::unified_diff(&first_text, &second_text, "Original", "Current", let diff = difflib::unified_diff(
"2005-01-26 23:30:50", "2010-04-02 10:20:52", 3); &first_text,
&second_text,
"Original",
"Current",
"2005-01-26 23:30:50",
"2010-04-02 10:20:52",
3,
);
for line in &diff { for line in &diff {
println!("{:?}", line); println!("{:?}", line);
} }
//context_diff //context_diff
let diff = difflib::context_diff(&first_text, &second_text, "Original", "Current", let diff = difflib::context_diff(
"2005-01-26 23:30:50", "2010-04-02 10:20:52", 3); &first_text,
&second_text,
"Original",
"Current",
"2005-01-26 23:30:50",
"2010-04-02 10:20:52",
3,
);
for line in &diff { for line in &diff {
println!("{:?}", line); println!("{:?}", line);
} }
+18 -15
View File
@@ -3,30 +3,33 @@ extern crate difflib;
use difflib::differ::Differ; use difflib::differ::Differ;
use difflib::sequencematcher::SequenceMatcher; use difflib::sequencematcher::SequenceMatcher;
fn main() { fn main() {
// unified_diff // unified_diff
let first_text = "one two three four".split(" ").collect::<Vec<&str>>(); let first_text = "one two three four".split(" ").collect::<Vec<&str>>();
let second_text = "zero one tree four".split(" ").collect::<Vec<&str>>(); let second_text = "zero one tree four".split(" ").collect::<Vec<&str>>();
let diff = difflib::unified_diff(&first_text, let diff = difflib::unified_diff(
&second_text, &first_text,
"Original", &second_text,
"Current", "Original",
"2005-01-26 23:30:50", "Current",
"2010-04-02 10:20:52", "2005-01-26 23:30:50",
3); "2010-04-02 10:20:52",
3,
);
for line in &diff { for line in &diff {
println!("{:?}", line); println!("{:?}", line);
} }
//context_diff //context_diff
let diff = difflib::context_diff(&first_text, let diff = difflib::context_diff(
&second_text, &first_text,
"Original", &second_text,
"Current", "Original",
"2005-01-26 23:30:50", "Current",
"2010-04-02 10:20:52", "2005-01-26 23:30:50",
3); "2010-04-02 10:20:52",
3,
);
for line in &diff { for line in &diff {
println!("{:?}", line); println!("{:?}", line);
} }
+169 -130
View File
@@ -1,11 +1,11 @@
use sequencematcher::{SequenceMatcher, Sequence}; use sequencematcher::SequenceMatcher;
use utils::{str_with_similar_chars, count_leading};
use std::cmp; use std::cmp;
use utils::{count_leading, str_with_similar_chars};
#[derive(Default)]
pub struct Differ { pub struct Differ {
pub line_junk: Option<fn(&str) -> bool>, pub line_junk: Option<fn(&&str) -> bool>,
pub char_junk: Option<fn(&str) -> bool>, pub char_junk: Option<fn(&char) -> bool>,
} }
impl Differ { impl Differ {
@@ -16,10 +16,7 @@ impl Differ {
} }
} }
pub fn compare<T: ?Sized + Sequence>(&self, pub fn compare(&self, first_sequence: &[&str], second_sequence: &[&str]) -> Vec<String> {
first_sequence: &T,
second_sequence: &T)
-> Vec<String> {
let mut matcher = SequenceMatcher::new(first_sequence, second_sequence); let mut matcher = SequenceMatcher::new(first_sequence, second_sequence);
matcher.set_is_junk(self.line_junk); matcher.set_is_junk(self.line_junk);
let mut res = Vec::new(); let mut res = Vec::new();
@@ -27,12 +24,14 @@ impl Differ {
let mut gen = Vec::new(); let mut gen = Vec::new();
match opcode.tag.as_ref() { match opcode.tag.as_ref() {
"replace" => { "replace" => {
gen = self.fancy_replace(first_sequence, gen = self.fancy_replace(
opcode.first_start, first_sequence,
opcode.first_end, opcode.first_start,
second_sequence, opcode.first_end,
opcode.second_start, second_sequence,
opcode.second_end) opcode.second_start,
opcode.second_end,
)
} }
"delete" => { "delete" => {
gen = self.dump("-", first_sequence, opcode.first_start, opcode.first_end) gen = self.dump("-", first_sequence, opcode.first_start, opcode.first_end)
@@ -52,66 +51,71 @@ impl Differ {
res res
} }
fn dump<T: ?Sized + Sequence>(&self, fn dump(&self, tag: &str, sequence: &[&str], start: usize, end: usize) -> Vec<String> {
tag: &str,
sequence: &T,
start: usize,
end: usize)
-> Vec<String> {
let mut res = Vec::new(); let mut res = Vec::new();
for i in start..end { for i in start..end {
match sequence.at_index(i) { if let Some(s) = sequence.get(i) {
Some(s) => res.push(format!("{} {}", tag, s)), res.push(format!("{} {}", tag, s))
None => {}
} }
} }
res res
} }
fn plain_replace<T: ?Sized + Sequence>(&self, fn plain_replace(
first_sequence: &T, &self,
first_start: usize, first_sequence: &[&str],
first_end: usize, first_start: usize,
second_sequence: &T, first_end: usize,
second_start: usize, second_sequence: &[&str],
second_end: usize) second_start: usize,
-> Vec<String> { second_end: usize,
) -> Vec<String> {
if !(first_start < first_end && second_start < second_end) { if !(first_start < first_end && second_start < second_end) {
return Vec::new(); return Vec::new();
} }
let mut first; let (mut first, second) = if second_end - second_start < first_end - first_start {
let second; (
if second_end - second_start < first_end - first_start { self.dump("+", second_sequence, second_start, second_end),
first = self.dump("+", second_sequence, second_start, second_end); self.dump("-", first_sequence, first_start, first_end),
second = self.dump("-", first_sequence, first_start, first_end); )
} else { } 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 { for s in second {
first.push(s); first.push(s);
} }
first first
} }
fn fancy_replace<T: ?Sized + Sequence>(&self, fn fancy_replace(
first_sequence: &T, &self,
first_start: usize, first_sequence: &[&str],
first_end: usize, first_start: usize,
second_sequence: &T, first_end: usize,
second_start: usize, second_sequence: &[&str],
second_end: usize) second_start: usize,
-> Vec<String> { second_end: usize,
) -> Vec<String> {
let mut res = Vec::new(); let mut res = Vec::new();
let (mut best_ratio, cutoff) = (0.74, 0.75); let (mut best_ratio, cutoff) = (0.74, 0.75);
let (mut best_i, mut best_j) = (0, 0); let (mut best_i, mut best_j) = (0, 0);
let (mut second_sequence_str, mut first_sequence_str);
let mut eqi: Option<usize> = None; let mut eqi: Option<usize> = None;
let mut eqj: Option<usize> = None; let mut eqj: Option<usize> = None;
for j in second_start..second_end { for (j, second_sequence_str) in second_sequence
second_sequence_str = second_sequence.at_index(j).unwrap(); .iter()
for i in first_start..first_end { .enumerate()
first_sequence_str = first_sequence.at_index(i).unwrap(); .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 first_sequence_str == second_sequence_str {
if eqi.is_none() { if eqi.is_none() {
eqi = Some(i); eqi = Some(i);
@@ -119,7 +123,12 @@ impl Differ {
} }
continue; continue;
} }
let mut cruncher = SequenceMatcher::new(first_sequence_str, second_sequence_str); let (first_sequence_chars, second_sequence_chars) = (
first_sequence_str.chars().collect::<Vec<char>>(),
second_sequence_str.chars().collect::<Vec<char>>(),
);
let mut cruncher =
SequenceMatcher::new(&first_sequence_chars, &second_sequence_chars);
cruncher.set_is_junk(self.char_junk); cruncher.set_is_junk(self.char_junk);
if cruncher.ratio() > best_ratio { if cruncher.ratio() > best_ratio {
best_ratio = cruncher.ratio(); best_ratio = cruncher.ratio();
@@ -130,14 +139,17 @@ impl Differ {
} }
if best_ratio < cutoff { if best_ratio < cutoff {
if eqi.is_none() { if eqi.is_none() {
res.extend(self.plain_replace(first_sequence, res.extend(
first_start, self.plain_replace(
first_end, first_sequence,
second_sequence, first_start,
second_start, first_end,
second_end) second_sequence,
.iter() second_start,
.cloned()); second_end,
).iter()
.cloned(),
);
return res; return res;
} }
best_i = eqi.unwrap(); best_i = eqi.unwrap();
@@ -145,23 +157,30 @@ impl Differ {
} else { } else {
eqi = None; eqi = None;
} }
res.extend(self.fancy_helper(first_sequence, res.extend(
first_start, self.fancy_helper(
best_i, first_sequence,
second_sequence, first_start,
second_start, best_i,
best_j) second_sequence,
.iter() second_start,
.cloned()); best_j,
let (first_element, second_element) = (first_sequence.at_index(best_i).unwrap(), ).iter()
second_sequence.at_index(best_j).unwrap()); .cloned(),
);
let first_element = &first_sequence[best_i];
let second_element = &second_sequence[best_j];
if eqi.is_none() { if eqi.is_none() {
let (mut first_tag, mut second_tag) = (String::new(), String::new()); 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<char> = first_element.chars().collect();
let second_element_chars: Vec<char> = second_element.chars().collect();
let mut cruncher = SequenceMatcher::new(&first_element_chars, &second_element_chars);
cruncher.set_is_junk(self.char_junk); cruncher.set_is_junk(self.char_junk);
for opcode in &cruncher.get_opcodes() { for opcode in &cruncher.get_opcodes() {
let (first_length, second_length) = (opcode.first_end - opcode.first_start, let (first_length, second_length) = (
opcode.second_end - opcode.second_start); opcode.first_end - opcode.first_start,
opcode.second_end - opcode.second_start,
);
match opcode.tag.as_ref() { match opcode.tag.as_ref() {
"replace" => { "replace" => {
first_tag.push_str(&str_with_similar_chars('^', first_length)); first_tag.push_str(&str_with_similar_chars('^', first_length));
@@ -180,42 +199,50 @@ impl Differ {
_ => {} _ => {}
} }
} }
res.extend(self.qformat(&first_element, &second_element, &first_tag, &second_tag) res.extend(
.iter() self.qformat(&first_element, &second_element, &first_tag, &second_tag)
.cloned()); .iter()
.cloned(),
);
} else { } else {
let mut s = String::from(" "); let mut s = String::from(" ");
s.push_str(&first_element); s.push_str(&first_element);
res.push(s); res.push(s);
} }
res.extend(self.fancy_helper(first_sequence, res.extend(
best_i + 1, self.fancy_helper(
first_end, first_sequence,
second_sequence, best_i + 1,
best_j + 1, first_end,
second_end) second_sequence,
.iter() best_j + 1,
.cloned()); second_end,
).iter()
.cloned(),
);
res res
} }
fn fancy_helper<T: ?Sized + Sequence>(&self, fn fancy_helper(
first_sequence: &T, &self,
first_start: usize, first_sequence: &[&str],
first_end: usize, first_start: usize,
second_sequence: &T, first_end: usize,
second_start: usize, second_sequence: &[&str],
second_end: usize) second_start: usize,
-> Vec<String> { second_end: usize,
) -> Vec<String> {
let mut res = Vec::new(); let mut res = Vec::new();
if first_start < first_end { if first_start < first_end {
if second_start < second_end { if second_start < second_end {
res = self.fancy_replace(first_sequence, res = self.fancy_replace(
first_start, first_sequence,
first_end, first_start,
second_sequence, first_end,
second_start, second_sequence,
second_end); second_start,
second_end,
);
} else { } else {
res = self.dump("-", first_sequence, first_start, first_end); res = self.dump("-", first_sequence, first_start, first_end);
} }
@@ -225,49 +252,49 @@ impl Differ {
res res
} }
fn qformat(&self, fn qformat(
first_line: &str, &self,
second_line: &str, first_line: &str,
first_tags: &str, second_line: &str,
second_tags: &str) first_tags: &str,
-> Vec<String> { second_tags: &str,
) -> Vec<String> {
let mut res = Vec::new(); let mut res = Vec::new();
let mut first_tags = first_tags; let mut first_tags = first_tags;
let mut second_tags = second_tags; let mut second_tags = second_tags;
let mut common = cmp::min(count_leading(first_line, '\t'), let mut common = cmp::min(
count_leading(second_line, '\t')); 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, ' '));
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(); first_tags = first_tags.split_at(common).1.trim_right();
second_tags = second_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); res.push(s);
if first_tags != "" { 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); res.push(s);
} }
s = String::from(format!("+ {}", second_line)); s = format!("+ {}", second_line);
res.push(s); res.push(s);
if second_tags != "" { if second_tags != "" {
s = String::from(format!("? {}{}\n", s = format!(
str_with_similar_chars('\t', common), "? {}{}\n",
second_tags)); str_with_similar_chars('\t', common),
second_tags
);
res.push(s); res.push(s);
} }
res res
} }
pub fn restore(delta: &Vec<String>, which: usize) -> Vec<String> { pub fn restore(delta: &[String], which: usize) -> Vec<String> {
if !(which == 1 || which == 2) { if !(which == 1 || which == 2) {
panic!("Second parameter must be 1 or 2"); panic!("Second parameter must be 1 or 2");
} }
let mut res = Vec::new(); let mut res = Vec::new();
let tag; let tag = if which == 1 { "- " } else { "+ " }.to_string();
if which == 1 {
tag = "- ".to_string();
} else {
tag = "+ ".to_string();
}
let prefixes = vec![tag, " ".to_string()]; let prefixes = vec![tag, " ".to_string()];
for line in delta { for line in delta {
for prefix in &prefixes { for prefix in &prefixes {
@@ -283,19 +310,31 @@ impl Differ {
#[test] #[test]
fn test_fancy_replace() { fn test_fancy_replace() {
let differ = Differ::new(); 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(""); .join("");
assert_eq!(result, assert_eq!(
"- abcDefghiJkl\n? ^ ^ ^\n+ abcdefGhijkl\n? ^ ^ ^\n"); result,
"- abcDefghiJkl\n? ^ ^ ^\n+ abcdefGhijkl\n? ^ ^ ^\n"
);
} }
#[test] #[test]
fn test_qformat() { fn test_qformat() {
let differ = Differ::new(); let differ = Differ::new();
let result = differ.qformat("\tabcDefghiJkl\n", let result = differ.qformat(
"\tabcdefGhijkl\n", "\tabcDefghiJkl\n",
" ^ ^ ^ ", "\tabcdefGhijkl\n",
" ^ ^ ^ "); " ^ ^ ^ ",
assert_eq!(result, " ^ ^ ^ ",
vec!["- \tabcDefghiJkl\n", "? \t ^ ^ ^\n", "+ \tabcdefGhijkl\n", "? \t ^ ^ ^\n"]); );
assert_eq!(
result,
vec![
"- \tabcDefghiJkl\n",
"? \t ^ ^ ^\n",
"+ \tabcdefGhijkl\n",
"? \t ^ ^ ^\n",
]
);
} }
+62 -42
View File
@@ -1,18 +1,18 @@
pub mod sequencematcher;
pub mod differ; pub mod differ;
pub mod sequencematcher;
mod utils; mod utils;
use sequencematcher::{Sequence, SequenceMatcher};
use std::collections::HashMap; use std::collections::HashMap;
use sequencematcher::{SequenceMatcher, Sequence}; use std::fmt::Display;
use utils::{format_range_unified, format_range_context}; use utils::{format_range_context, format_range_unified};
pub fn get_close_matches<'a>(
pub fn get_close_matches<'a>(word: &str, word: &str,
possibilities: Vec<&'a str>, possibilities: Vec<&'a str>,
n: usize, n: usize,
cutoff: f32) cutoff: f32,
-> Vec<&'a str> { ) -> Vec<&'a str> {
if !(0.0 <= cutoff && cutoff <= 1.0) { if !(0.0 <= cutoff && cutoff <= 1.0) {
panic!("Cutoff must be greater than 0.0 and lower than 1.0"); panic!("Cutoff must be greater than 0.0 and lower than 1.0");
} }
@@ -30,14 +30,15 @@ pub fn get_close_matches<'a>(word: &str,
res.iter().map(|x| x.1).collect() res.iter().map(|x| x.1).collect()
} }
pub fn unified_diff<T: Sequence>(first_sequence: &T, pub fn unified_diff<T: Sequence + Display>(
second_sequence: &T, first_sequence: &[T],
from_file: &str, second_sequence: &[T],
to_file: &str, from_file: &str,
from_file_date: &str, to_file: &str,
to_file_date: &str, from_file_date: &str,
n: usize) to_file_date: &str,
-> Vec<String> { n: usize,
) -> Vec<String> {
let mut res = Vec::new(); let mut res = Vec::new();
let lineterm = '\n'; let lineterm = '\n';
let mut started = false; let mut started = false;
@@ -53,22 +54,37 @@ pub fn unified_diff<T: Sequence>(first_sequence: &T,
let (first, last) = (group.first().unwrap(), group.last().unwrap()); let (first, last) = (group.first().unwrap(), group.last().unwrap());
let file1_range = format_range_unified(first.first_start, last.first_end); let file1_range = format_range_unified(first.first_start, last.first_end);
let file2_range = format_range_unified(first.second_start, last.second_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 { for code in group {
if code.tag == "equal" { if code.tag == "equal" {
for i in code.first_start..code.first_end { for item in first_sequence
res.push(format!(" {}", first_sequence.at_index(i).unwrap())); .iter()
.take(code.first_end)
.skip(code.first_start)
{
res.push(format!(" {}", item));
} }
continue; continue;
} }
if code.tag == "replace" || code.tag == "delete" { if code.tag == "replace" || code.tag == "delete" {
for i in code.first_start..code.first_end { for item in first_sequence
res.push(format!("-{}", first_sequence.at_index(i).unwrap())); .iter()
.take(code.first_end)
.skip(code.first_start)
{
res.push(format!("-{}", item));
} }
} }
if code.tag == "replace" || code.tag == "insert" { if code.tag == "replace" || code.tag == "insert" {
for i in code.second_start..code.second_end { for item in second_sequence
res.push(format!("+{}", second_sequence.at_index(i).unwrap())); .iter()
.take(code.second_end)
.skip(code.second_start)
{
res.push(format!("+{}", item));
} }
} }
} }
@@ -76,15 +92,15 @@ pub fn unified_diff<T: Sequence>(first_sequence: &T,
res res
} }
pub fn context_diff<T: Sequence + Display>(
pub fn context_diff<T: Sequence>(first_sequence: &T, first_sequence: &[T],
second_sequence: &T, second_sequence: &[T],
from_file: &str, from_file: &str,
to_file: &str, to_file: &str,
from_file_date: &str, from_file_date: &str,
to_file_date: &str, to_file_date: &str,
n: usize) n: usize,
-> Vec<String> { ) -> Vec<String> {
let mut res = Vec::new(); let mut res = Vec::new();
let lineterm = '\n'; let lineterm = '\n';
let mut prefix: HashMap<String, String> = HashMap::new(); let mut prefix: HashMap<String, String> = HashMap::new();
@@ -116,10 +132,12 @@ pub fn context_diff<T: Sequence>(first_sequence: &T,
if any { if any {
for opcode in group { for opcode in group {
if opcode.tag != "insert" { if opcode.tag != "insert" {
for i in opcode.first_start..opcode.first_end { for item in first_sequence
res.push(format!("{}{}", .iter()
prefix.get(&opcode.tag).unwrap(), .take(opcode.first_end)
first_sequence.at_index(i).unwrap())); .skip(opcode.first_start)
{
res.push(format!("{}{}", &prefix[&opcode.tag], item));
} }
} }
} }
@@ -136,10 +154,12 @@ pub fn context_diff<T: Sequence>(first_sequence: &T,
if any { if any {
for opcode in group { for opcode in group {
if opcode.tag != "delete" { if opcode.tag != "delete" {
for i in opcode.second_start..opcode.second_end { for item in second_sequence
res.push(format!("{}{}", .iter()
prefix.get(&opcode.tag).unwrap(), .take(opcode.second_end)
second_sequence.at_index(i).unwrap())); .skip(opcode.second_start)
{
res.push(format!("{}{}", prefix[&opcode.tag], item));
} }
} }
} }
+145 -179
View File
@@ -1,7 +1,7 @@
use std::collections::{HashMap, HashSet};
use std::cmp::{max, min}; use std::cmp::{max, min};
use std::collections::HashMap;
use std::hash::Hash;
use utils::calculate_ratio; use utils::calculate_ratio;
use std::fmt::Debug;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
pub struct Match { pub struct Match {
@@ -13,9 +13,9 @@ pub struct Match {
impl Match { impl Match {
fn new(first_start: usize, second_start: usize, size: usize) -> Match { fn new(first_start: usize, second_start: usize, size: usize) -> Match {
Match { Match {
first_start: first_start, first_start,
second_start: second_start, second_start,
size: size, size,
} }
} }
} }
@@ -30,123 +30,81 @@ pub struct Opcode {
} }
impl Opcode { impl Opcode {
fn new(tag: String, fn new(
first_start: usize, tag: String,
first_end: usize, first_start: usize,
second_start: usize, first_end: usize,
second_end: usize) second_start: usize,
-> Opcode { second_end: usize,
) -> Opcode {
Opcode { Opcode {
tag: tag, tag,
first_start: first_start, first_start,
first_end: first_end, first_end,
second_start: second_start, second_start,
second_end: second_end, second_end,
} }
} }
} }
pub trait Sequence: Debug { pub trait Sequence: Eq + Hash {}
fn len(&self) -> usize; impl<T: Eq + Hash> Sequence for T {}
fn at_index(&self, index: usize) -> Option<&str>;
}
impl Sequence for str { pub struct SequenceMatcher<'a, T: 'a + Sequence> {
fn len(&self) -> usize { first_sequence: &'a [T],
self.len() second_sequence: &'a [T],
}
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<String> {
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,
matching_blocks: Option<Vec<Match>>, matching_blocks: Option<Vec<Match>>,
opcodes: Option<Vec<Opcode>>, opcodes: Option<Vec<Opcode>>,
is_junk: Option<fn(&str) -> bool>, is_junk: Option<fn(&T) -> bool>,
second_sequence_elements: HashMap<&'a str, Vec<usize>>, second_sequence_elements: HashMap<&'a T, Vec<usize>>,
second_sequence_popular: HashSet<&'a str>,
} }
impl<'a, T: Sequence> SequenceMatcher<'a, T> {
impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { pub fn new<S>(first_sequence: &'a S, second_sequence: &'a S) -> SequenceMatcher<'a, T>
pub fn new(first_sequence: &'a T, second_sequence: &'a T) -> SequenceMatcher<'a, T> { where
S: AsRef<[T]> + ?Sized,
{
let mut matcher = SequenceMatcher { let mut matcher = SequenceMatcher {
first_sequence: first_sequence, first_sequence: first_sequence.as_ref(),
second_sequence: second_sequence, second_sequence: second_sequence.as_ref(),
matching_blocks: None, matching_blocks: None,
opcodes: None, opcodes: None,
is_junk: None, is_junk: None,
second_sequence_elements: HashMap::new(), second_sequence_elements: HashMap::new(),
second_sequence_popular: HashSet::new(),
}; };
matcher.set_seqs(first_sequence, second_sequence); matcher.set_seqs(first_sequence, second_sequence);
matcher matcher
} }
pub fn set_is_junk(&mut self, is_junk: Option<fn(&str) -> bool>) { pub fn set_is_junk(&mut self, is_junk: Option<fn(&T) -> bool>) {
self.is_junk = is_junk; 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<S>(&mut self, first_sequence: &'a S, second_sequence: &'a S)
where
S: AsRef<[T]> + ?Sized,
{
self.set_first_seq(first_sequence); self.set_first_seq(first_sequence);
self.set_second_seq(second_sequence); self.set_second_seq(second_sequence);
} }
pub fn set_first_seq(&mut self, sequence: &'a T) { pub fn set_first_seq<S>(&mut self, sequence: &'a S)
self.first_sequence = sequence; where
S: AsRef<[T]> + ?Sized,
{
self.first_sequence = sequence.as_ref();
self.matching_blocks = None; self.matching_blocks = None;
self.opcodes = None; self.opcodes = None;
} }
pub fn set_second_seq(&mut self, sequence: &'a T) { pub fn set_second_seq<S>(&mut self, sequence: &'a S)
self.second_sequence = sequence; where
S: AsRef<[T]> + ?Sized,
{
self.second_sequence = sequence.as_ref();
self.matching_blocks = None; self.matching_blocks = None;
self.opcodes = None; self.opcodes = None;
self.chain_second_seq(); self.chain_second_seq();
@@ -155,94 +113,88 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> {
fn chain_second_seq(&mut self) { fn chain_second_seq(&mut self) {
let second_sequence = self.second_sequence; let second_sequence = self.second_sequence;
let mut second_sequence_elements = HashMap::new(); 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()) let mut counter = second_sequence_elements
.or_insert(Vec::new()); .entry(item)
.or_insert_with(Vec::new);
counter.push(i); counter.push(i);
} }
if self.is_junk.is_some() { if let Some(junk_func) = self.is_junk {
let mut junk = Vec::new(); second_sequence_elements = second_sequence_elements
for element in second_sequence_elements.keys() { .into_iter()
if (self.is_junk.unwrap())(element) { .filter(|&(element, _)| !junk_func(element))
junk.push(element.clone()); .collect();
}
}
for element in &junk {
second_sequence_elements.remove(element);
}
} }
let mut popular = HashSet::new(); // Filter out popular elements
let len = second_sequence.len(); let len = second_sequence.len();
if len >= 200 { if len >= 200 {
let test_len = (len as f32 / 100.0).floor() as usize + 1; let test_len = (len as f32 / 100.0).floor() as usize + 1;
for (element, indexes) in second_sequence_elements.iter() { second_sequence_elements = second_sequence_elements
if indexes.len() > test_len { .into_iter()
popular.insert(element.clone()); .filter(|&(_, ref indexes)| indexes.len() > test_len)
} .collect();
}
for element in &popular {
second_sequence_elements.remove(element);
}
} }
self.second_sequence_elements = second_sequence_elements; self.second_sequence_elements = second_sequence_elements;
self.second_sequence_popular = popular;
} }
pub fn find_longest_match(&self, pub fn find_longest_match(
first_start: usize, &self,
first_end: usize, first_start: usize,
second_start: usize, first_end: usize,
second_end: usize) second_start: usize,
-> Match { second_end: usize,
) -> Match {
let first_sequence = &self.first_sequence; let first_sequence = &self.first_sequence;
let second_sequence = &self.second_sequence; let second_sequence = &self.second_sequence;
let second_sequence_elements = &self.second_sequence_elements; 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 best_i, mut best_j, mut best_size) = (first_start, second_start, 0);
let mut j2len: HashMap<usize, usize> = HashMap::new(); let mut j2len: HashMap<usize, usize> = 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<usize, usize> = HashMap::new(); let mut new_j2len: HashMap<usize, usize> = HashMap::new();
match second_sequence_elements.get(first_sequence.at_index(i).unwrap()) { if let Some(indexes) = second_sequence_elements.get(item) {
Some(indexes) => { for j in indexes {
for j in indexes { let j = *j;
let j = j.clone(); if j < second_start {
if j < second_start { continue;
continue; };
}; if j >= second_end {
if j >= second_end { break;
break; };
}; let mut size = 0;
let mut size = 0; if j > 0 {
if j > 0 { if let Some(k) = j2len.get(&(j - 1)) {
match j2len.get(&(j - 1)) { size = *k;
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;
} }
} }
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; j2len = new_j2len;
} }
for _ in 0..2 { for _ in 0..2 {
while best_i > first_start && best_j > second_start && while best_i > first_start
first_sequence.at_index(best_i - 1) == second_sequence.at_index(best_j - 1) { && best_j > second_start
best_i = best_i - 1; && first_sequence.get(best_i - 1) == second_sequence.get(best_j - 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 && while best_i + best_size < first_end
first_sequence.at_index(best_i + best_size) == && best_j + best_size < second_end
second_sequence.at_index(best_j + best_size) { && first_sequence.get(best_i + best_size) == second_sequence.get(best_j + best_size)
{
best_size += 1; best_size += 1;
} }
} }
@@ -266,10 +218,12 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> {
queue.push((first_start, m.first_start, second_start, m.second_start)); 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 { if m.first_start + m.size < first_end && m.second_start + m.size < second_end {
queue.push((m.first_start + m.size, queue.push((
first_end, m.first_start + m.size,
m.second_start + m.size, first_end,
second_end)); m.second_start + m.size,
second_end,
));
} }
matches.push(m); matches.push(m);
} }
@@ -319,13 +273,17 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> {
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( opcodes.push(Opcode::new(
Opcode::new(String::from("equal"), m.first_start, i, m.second_start, j) String::from("equal"),
); m.first_start,
i,
m.second_start,
j,
));
} }
} }
self.opcodes = Some(opcodes); 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<Vec<Opcode>> { pub fn get_grouped_opcodes(&mut self, n: usize) -> Vec<Vec<Opcode>> {
@@ -336,12 +294,12 @@ Opcode::new(String::from("equal"), m.first_start, i, m.second_start, j)
} }
if codes.first().unwrap().tag == "equal" { 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.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)); opcode.second_start = max(opcode.second_start, opcode.second_end.saturating_sub(n));
} }
if codes.last().unwrap().tag == "equal" { 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.first_end = min(opcode.first_start + n, opcode.first_end);
opcode.second_end = min(opcode.second_start + n, opcode.second_end); opcode.second_end = min(opcode.second_start + n, opcode.second_end);
} }
@@ -350,31 +308,39 @@ Opcode::new(String::from("equal"), m.first_start, i, m.second_start, j)
for code in &codes { for code in &codes {
let (mut first_start, mut second_start) = (code.first_start, code.second_start); let (mut first_start, mut second_start) = (code.first_start, code.second_start);
if code.tag == "equal" && code.first_end - code.first_start > nn { if code.tag == "equal" && code.first_end - code.first_start > nn {
group.push(Opcode::new(code.tag.clone(), group.push(Opcode::new(
code.first_start, code.tag.clone(),
min(code.first_end, code.first_start + n), code.first_start,
code.second_start, min(code.first_end, code.first_start + n),
min(code.second_end, code.second_start + n))); code.second_start,
min(code.second_end, code.second_start + n),
));
res.push(group.clone()); res.push(group.clone());
group.clear(); group.clear();
first_start = max(first_start, code.first_end.saturating_sub(n)); first_start = max(first_start, code.first_end.saturating_sub(n));
second_start = max(second_start, code.second_end.saturating_sub(n)); second_start = max(second_start, code.second_end.saturating_sub(n));
} }
group.push(Opcode::new(code.tag.clone(), group.push(Opcode::new(
first_start, code.tag.clone(),
code.first_end, first_start,
second_start, code.first_end,
code.second_end)); second_start,
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.push(group.clone());
} }
res res
} }
pub fn ratio(&mut self) -> f32 { pub fn ratio(&mut self) -> f32 {
let matches = self.get_matching_blocks().iter().fold(0, |res, &m| res + m.size); let matches = self.get_matching_blocks()
calculate_ratio(matches, .iter()
self.first_sequence.len() + self.second_sequence.len()) .fold(0, |res, &m| res + m.size);
calculate_ratio(
matches,
self.first_sequence.len() + self.second_sequence.len(),
)
} }
} }
+2 -6
View File
@@ -2,10 +2,9 @@ pub fn calculate_ratio(matches: usize, length: usize) -> f32 {
if length != 0 { if length != 0 {
return 2.0 * matches as f32 / length as f32; 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 { pub fn str_with_similar_chars(c: char, length: usize) -> String {
let mut s = String::new(); let mut s = String::new();
for _ in 0..length { for _ in 0..length {
@@ -14,17 +13,15 @@ pub fn str_with_similar_chars(c: char, length: usize) -> String {
s s
} }
pub fn count_leading(line: &str, c: char) -> usize { pub fn count_leading(line: &str, c: char) -> usize {
let (mut i, n) = (0, line.len()); let (mut i, n) = (0, line.len());
let line: Vec<char> = line.chars().collect(); let line: Vec<char> = line.chars().collect();
while (i < n) && line[i] == c { while (i < n) && line[i] == c {
i += 1; i += 1;
} }
return i; i
} }
pub fn format_range_unified(start: usize, end: usize) -> String { pub fn format_range_unified(start: usize, end: usize) -> String {
let mut beginning = start + 1; let mut beginning = start + 1;
let length = end - start; let length = end - start;
@@ -37,7 +34,6 @@ pub fn format_range_unified(start: usize, end: usize) -> String {
format!("{},{}", beginning, length) format!("{},{}", beginning, length)
} }
pub fn format_range_context(start: usize, end: usize) -> String { pub fn format_range_context(start: usize, end: usize) -> String {
let mut beginning = start + 1; let mut beginning = start + 1;
let length = end - start; let length = end - start;
+59 -30
View File
@@ -1,7 +1,7 @@
extern crate difflib; extern crate difflib;
use difflib::sequencematcher::{SequenceMatcher, Match, Opcode};
use difflib::differ::Differ; use difflib::differ::Differ;
use difflib::sequencematcher::{Match, Opcode, SequenceMatcher};
#[test] #[test]
fn test_longest_match() { fn test_longest_match() {
@@ -97,12 +97,14 @@ fn test_differ_compare() {
let second_text = vec!["ore\n", "tree\n", "emu\n"]; let second_text = vec!["ore\n", "tree\n", "emu\n"];
let differ = Differ::new(); let differ = Differ::new();
let result = differ.compare(&first_text, &second_text).join(""); let result = differ.compare(&first_text, &second_text).join("");
assert_eq!(result, assert_eq!(
"- one\n? ^\n+ ore\n? ^\n- two\n- three\n? -\n+ tree\n+ emu\n"); result,
"- one\n? ^\n+ ore\n? ^\n- two\n- three\n? -\n+ tree\n+ emu\n"
);
} }
fn is_junk_char(ch: &str) -> bool { fn is_junk_char(ch: &char) -> bool {
if ch == " " || ch == "\t" { if *ch == ' ' || *ch == '\t' {
return true; return true;
} }
false false
@@ -115,8 +117,10 @@ fn test_differ_compare_with_func() {
let mut differ = Differ::new(); let mut differ = Differ::new();
differ.char_junk = Some(is_junk_char); differ.char_junk = Some(is_junk_char);
let result = differ.compare(&first_text, &second_text).join(""); let result = differ.compare(&first_text, &second_text).join("");
assert_eq!(result, assert_eq!(
"- one\n? ^\n+ ore\n? ^\n- two\n- three\n? -\n+ tree\n+ emu\n"); result,
"- one\n? ^\n+ ore\n? ^\n- two\n- three\n? -\n+ tree\n+ emu\n"
);
} }
#[test] #[test]
@@ -133,33 +137,58 @@ fn test_differ_restore() {
fn test_unified_diff() { fn test_unified_diff() {
let first_text = "one two three four".split(" ").collect::<Vec<&str>>(); let first_text = "one two three four".split(" ").collect::<Vec<&str>>();
let second_text = "zero one tree four".split(" ").collect::<Vec<&str>>(); let second_text = "zero one tree four".split(" ").collect::<Vec<&str>>();
let result = difflib::unified_diff(&first_text, let result = difflib::unified_diff(
&second_text, &first_text,
"Original", &second_text,
"Current", "Original",
"2005-01-26 23:30:50", "Current",
"2010-04-02 10:20:52", "2005-01-26 23:30:50",
3) "2010-04-02 10:20:52",
.join(""); 3,
assert_eq!(result, ).join("");
"--- Original\t2005-01-26 23:30:50\n+++ Current\t2010-04-02 10:20:52\n@@ -1,4 \ assert_eq!(
+1,4 @@\n+zero one-two-three+tree four"); 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] #[test]
fn test_context_diff() { fn test_context_diff() {
let first_text = "one two three four".split(" ").collect::<Vec<&str>>(); let first_text = "one two three four".split(" ").collect::<Vec<&str>>();
let second_text = "zero one tree four".split(" ").collect::<Vec<&str>>(); let second_text = "zero one tree four".split(" ").collect::<Vec<&str>>();
let result = difflib::context_diff(&first_text, let result = difflib::context_diff(
&second_text, &first_text,
"Original", &second_text,
"Current", "Original",
"2005-01-26 23:30:50", "Current",
"2010-04-02 10:20:52", "2005-01-26 23:30:50",
3) "2010-04-02 10:20:52",
.join(""); 3,
assert_eq!(result, ).join("");
"*** Original\t2005-01-26 23:30:50\n--- Current\t2010-04-02 \ assert_eq!(
10:20:52\n***************\n*** 1,4 ****\n one! two! three four--- 1,4 ----\n+ \ result,
zero one! tree four"); "*** 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"
);
}
#[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);
} }