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"
+21 -8
View File
@@ -1,6 +1,6 @@
# Difflib [![Build Status](https://travis-ci.org/DimaKudosh/difflib.svg?branch=master)](https://travis-ci.org/DimaKudosh/difflib) # 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. It's provide all necessary tools for comparing word sequences.
## Installation ## Installation
@@ -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);
} }
@@ -62,7 +75,7 @@ fn main() {
let grouped_opcodes = matcher.get_grouped_opcodes(2); let grouped_opcodes = matcher.get_grouped_opcodes(2);
println!("{:?}", grouped_opcodes); println!("{:?}", grouped_opcodes);
let ratio = matcher.ratio(); let ratio = matcher.ratio();
println!("{:?}", ratio); println!("{:?}", ratio);
matcher.set_seqs("aaaaa", "aaaab"); matcher.set_seqs("aaaaa", "aaaab");
println!("{:?}", matcher.ratio()); println!("{:?}", matcher.ratio());
} }
+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);
} }