Compare commits

..

10 commits

Author SHA1 Message Date
Barrett Ruth
11f9b4d80b
Merge pull request #6 from barrettruth/fix/naming
Some checks failed
Clippy / build (push) Has been cancelled
Rustfmt / build (push) Has been cancelled
Tests / Test on Latest (push) Has been cancelled
Tests / Build on 1.66.0 (push) Has been cancelled
Tests / Test on WASI (push) Has been cancelled
one more fix
2026-01-10 11:26:46 -06:00
59aa979656 one more fix 2026-01-10 12:25:31 -05:00
Barrett Ruth
60ed33012d
Merge pull request #5 from barrettruth/feat/fp
feat: github name rename
2026-01-10 11:18:05 -06:00
10951a01a1 remove claude 2026-01-10 12:17:45 -05:00
5c3280edfd feat: github name rename 2026-01-10 12:16:04 -05:00
Barrett Ruth
3b29cb72bb
Merge pull request #4 from barrett-ruth/feat/fp
floating point epsilon to comparison
2025-09-11 22:33:43 +02:00
66a392e610 update docstrings 2025-09-11 15:31:36 -05:00
e78319df2b updates 2025-09-11 15:28:48 -05:00
773ccde361 update 2025-09-11 15:24:09 -05:00
332ca5582b cargo format 2025-09-11 15:20:00 -05:00
8 changed files with 97 additions and 37 deletions

View file

@ -1,6 +1,6 @@
[package]
name = "likewise"
version = "3.0.0"
version = "3.1.0"
authors = [
"Barrett Ruth <br.barrettruth@gmail.com>",
"Armin Ronacher <armin.ronacher@active-4.com>",
@ -11,7 +11,7 @@ edition = "2018"
rust-version = "1.66"
license = "Apache-2.0"
description = "A diff library for Rust (fork of similar)"
repository = "https://github.com/barrett-ruth/likewise"
repository = "https://github.com/barrettruth/likewise"
keywords = ["diff", "difference", "patience", "compare", "changes"]
readme = "README.md"
exclude = ["assets/*"]

View file

@ -3,7 +3,7 @@
> This crate is a fork of [similar](https://github.com/mitsuhiko/similar) library, which, as of 11/9/25, is rather inactive.
[![Crates.io](https://img.shields.io/crates/d/likewise.svg)](https://crates.io/crates/likewise)
[![License](https://img.shields.io/github/license/frozen/likewise)](https://github.com/frozen/likewise/blob/main/LICENSE)
[![License](https://img.shields.io/github/license/barrettruth/likewise)](https://github.com/barrettruth/likewise/blob/main/LICENSE)
[![rustc 1.66.0](https://img.shields.io/badge/rust-1.66%2B-orange.svg)](https://img.shields.io/badge/rust-1.65%2B-orange.svg)
[![Documentation](https://docs.rs/likewise/badge.svg)](https://docs.rs/likewise)
@ -44,19 +44,20 @@ fn main() {
* Patience diff
* HuntMcIlroy / HuntSzymanski LCS diff
* Diffing on arbitrary comparable sequences
* **Floating point comparison with epsilon tolerance**
* Line, word, character and grapheme level diffing
* Text and Byte diffing
* Unified diff generation
## Related Projects
* [similar](https://github.com/mitsuhiko/similar)
* [similar](https://github.com/mitsuhiko/similar) the original library
* [insta](https://insta.rs) snapshot testing library
* [similar-asserts](https://github.com/mitsuhiko/similar-asserts) assertion library
## License and Links
* [Documentation](https://docs.rs/likewise/)
* [Issue Tracker](https://github.com/barrett-ruth/likewise/issues)
* [Examples](https://github.com/barrett-ruth/likewise/tree/main/examples)
* License: [Apache-2.0](https://github.com/barrett-ruth/likewise/blob/main/LICENSE)
* [Issue Tracker](https://github.com/barrettruth/likewise/issues)
* [Examples](https://github.com/barrettruth/likewise/tree/main/examples)
* License: [Apache-2.0](https://github.com/barrettruth/likewise/blob/main/LICENSE)

View file

@ -9,10 +9,10 @@ fn main() {
// Strict comparison would show many differences due to measurement noise
// FP comparison with 0.05 tolerance treats small variations as equal
let ops = capture_diff_slices_fp(
Algorithm::Myers,
&baseline_readings,
&current_readings,
0.05
Algorithm::Myers,
&baseline_readings,
&current_readings,
0.05,
);
println!("Baseline: {:?}", baseline_readings);
@ -20,7 +20,9 @@ fn main() {
println!("With epsilon=0.05: {} diff operations", ops.len());
for op in &ops {
let changes: Vec<_> = op.iter_changes(&baseline_readings, &current_readings).collect();
let changes: Vec<_> = op
.iter_changes(&baseline_readings, &current_readings)
.collect();
for change in changes {
match change.tag() {
ChangeTag::Equal => println!(" ✓ Equal: {}", change.value()),
@ -41,8 +43,16 @@ fn main() {
for &epsilon in &[0.0001, 0.001, 0.01] {
let ops = capture_diff_slices_fp(Algorithm::Myers, &old, &new, epsilon);
let all_equal = ops.len() == 1 && matches!(ops[0], likewise::DiffOp::Equal { len: 3, .. });
println!(" epsilon={}: {} ({})", epsilon, ops.len(),
if all_equal { "all equal" } else { "differences found" });
println!(
" epsilon={}: {} ({})",
epsilon,
ops.len(),
if all_equal {
"all equal"
} else {
"differences found"
}
);
}
// Example 3: Edge cases with NaN and infinity
@ -61,5 +71,8 @@ fn main() {
let new_f64 = vec![1.0000000000002, 2.0, 3.141592653589794];
let ops_f64 = capture_diff_slices_fp_f64(Algorithm::Myers, &old_f64, &new_f64, 1e-12);
println!("f64 comparison with epsilon=1e-12: {} operations", ops_f64.len());
}
println!(
"f64 comparison with epsilon=1e-12: {} operations",
ops_f64.len()
);
}

View file

@ -293,6 +293,10 @@ fn test_bad_range_regression() {
);
}
/// LCS diff algorithm with f32 epsilon comparison.
///
/// Diff `old`, between indices `old_range` and `new` between indices `new_range`.
/// Values are considered equal if their absolute difference is within `epsilon`.
pub fn diff_fp_deadline<D>(
d: &mut D,
old: &[f32],
@ -308,6 +312,10 @@ where
crate::algorithms::myers::diff_fp_deadline(d, old, old_range, new, new_range, epsilon, deadline)
}
/// LCS diff algorithm with f64 epsilon comparison.
///
/// Diff `old`, between indices `old_range` and `new` between indices `new_range`.
/// Values are considered equal if their absolute difference is within `epsilon`.
pub fn diff_fp_f64_deadline<D>(
d: &mut D,
old: &[f64],
@ -320,5 +328,7 @@ pub fn diff_fp_f64_deadline<D>(
where
D: DiffHook,
{
crate::algorithms::myers::diff_fp_f64_deadline(d, old, old_range, new, new_range, epsilon, deadline)
crate::algorithms::myers::diff_fp_f64_deadline(
d, old, old_range, new, new_range, epsilon, deadline,
)
}

View file

@ -132,4 +132,3 @@ where
{
diff_deadline(alg, d, old, 0..old.len(), new, 0..new.len(), deadline)
}

View file

@ -21,7 +21,10 @@
use std::ops::{Index, IndexMut, Range};
use crate::algorithms::utils::{common_prefix_len, common_suffix_len, common_prefix_len_fp, common_suffix_len_fp, common_prefix_len_fp_f64, common_suffix_len_fp_f64, is_empty_range};
use crate::algorithms::utils::{
common_prefix_len, common_prefix_len_fp, common_prefix_len_fp_f64, common_suffix_len,
common_suffix_len_fp, common_suffix_len_fp_f64, is_empty_range,
};
use crate::algorithms::DiffHook;
use crate::deadline_support::{deadline_exceeded, Instant};
@ -442,6 +445,9 @@ fn test_finish_called() {
}
/// Myers' diff algorithm with f32 epsilon comparison.
///
/// Diff `old`, between indices `old_range` and `new` between indices `new_range`.
/// Values are considered equal if their absolute difference is within `epsilon`.
pub fn diff_fp_deadline<D>(
d: &mut D,
old: &[f32],
@ -457,11 +463,16 @@ where
let max_d = max_d(old_range.len(), new_range.len());
let mut vb = V::new(max_d);
let mut vf = V::new(max_d);
conquer_fp(d, old, old_range, new, new_range, epsilon, &mut vf, &mut vb, deadline)?;
conquer_fp(
d, old, old_range, new, new_range, epsilon, &mut vf, &mut vb, deadline,
)?;
d.finish()
}
/// Myers' diff algorithm with f64 epsilon comparison.
///
/// Diff `old`, between indices `old_range` and `new` between indices `new_range`.
/// Values are considered equal if their absolute difference is within `epsilon`.
pub fn diff_fp_f64_deadline<D>(
d: &mut D,
old: &[f64],
@ -477,7 +488,9 @@ where
let max_d = max_d(old_range.len(), new_range.len());
let mut vb = V::new(max_d);
let mut vf = V::new(max_d);
conquer_fp_f64(d, old, old_range, new, new_range, epsilon, &mut vf, &mut vb, deadline)?;
conquer_fp_f64(
d, old, old_range, new, new_range, epsilon, &mut vf, &mut vb, deadline,
)?;
d.finish()
}
@ -681,7 +694,8 @@ where
D: DiffHook,
{
// Check for common prefix
let common_prefix_len = common_prefix_len_fp(old, old_range.clone(), new, new_range.clone(), epsilon);
let common_prefix_len =
common_prefix_len_fp(old, old_range.clone(), new, new_range.clone(), epsilon);
if common_prefix_len > 0 {
d.equal(old_range.start, new_range.start, common_prefix_len)?;
}
@ -689,7 +703,8 @@ where
new_range.start += common_prefix_len;
// Check for common suffix
let common_suffix_len = common_suffix_len_fp(old, old_range.clone(), new, new_range.clone(), epsilon);
let common_suffix_len =
common_suffix_len_fp(old, old_range.clone(), new, new_range.clone(), epsilon);
let common_suffix = (
old_range.end - common_suffix_len,
new_range.end - common_suffix_len,
@ -753,7 +768,8 @@ where
D: DiffHook,
{
// Check for common prefix
let common_prefix_len = common_prefix_len_fp_f64(old, old_range.clone(), new, new_range.clone(), epsilon);
let common_prefix_len =
common_prefix_len_fp_f64(old, old_range.clone(), new, new_range.clone(), epsilon);
if common_prefix_len > 0 {
d.equal(old_range.start, new_range.start, common_prefix_len)?;
}
@ -761,7 +777,8 @@ where
new_range.start += common_prefix_len;
// Check for common suffix
let common_suffix_len = common_suffix_len_fp_f64(old, old_range.clone(), new, new_range.clone(), epsilon);
let common_suffix_len =
common_suffix_len_fp_f64(old, old_range.clone(), new, new_range.clone(), epsilon);
let common_suffix = (
old_range.end - common_suffix_len,
new_range.end - common_suffix_len,

View file

@ -197,6 +197,10 @@ fn test_finish_called() {
assert!(d.0);
}
/// Patience diff algorithm with f32 epsilon comparison.
///
/// Diff `old`, between indices `old_range` and `new` between indices `new_range`.
/// Values are considered equal if their absolute difference is within `epsilon`.
pub fn diff_fp_deadline<D>(
d: &mut D,
old: &[f32],
@ -212,6 +216,10 @@ where
crate::algorithms::myers::diff_fp_deadline(d, old, old_range, new, new_range, epsilon, deadline)
}
/// Patience diff algorithm with f64 epsilon comparison.
///
/// Diff `old`, between indices `old_range` and `new` between indices `new_range`.
/// Values are considered equal if their absolute difference is within `epsilon`.
pub fn diff_fp_f64_deadline<D>(
d: &mut D,
old: &[f64],
@ -224,5 +232,7 @@ pub fn diff_fp_f64_deadline<D>(
where
D: DiffHook,
{
crate::algorithms::myers::diff_fp_f64_deadline(d, old, old_range, new, new_range, epsilon, deadline)
crate::algorithms::myers::diff_fp_f64_deadline(
d, old, old_range, new, new_range, epsilon, deadline,
)
}

View file

@ -124,9 +124,15 @@ fn capture_diff_fp_deadline(
) -> Vec<DiffOp> {
let mut d = Compact::new(Replace::new(Capture::new()), old, new);
let result = match alg {
Algorithm::Myers => crate::algorithms::myers::diff_fp_deadline(&mut d, old, old_range, new, new_range, epsilon, deadline),
Algorithm::Patience => crate::algorithms::patience::diff_fp_deadline(&mut d, old, old_range, new, new_range, epsilon, deadline),
Algorithm::Lcs => crate::algorithms::lcs::diff_fp_deadline(&mut d, old, old_range, new, new_range, epsilon, deadline),
Algorithm::Myers => crate::algorithms::myers::diff_fp_deadline(
&mut d, old, old_range, new, new_range, epsilon, deadline,
),
Algorithm::Patience => crate::algorithms::patience::diff_fp_deadline(
&mut d, old, old_range, new, new_range, epsilon, deadline,
),
Algorithm::Lcs => crate::algorithms::lcs::diff_fp_deadline(
&mut d, old, old_range, new, new_range, epsilon, deadline,
),
};
result.unwrap();
d.into_inner().into_inner().into_ops()
@ -143,9 +149,15 @@ fn capture_diff_fp_f64_deadline(
) -> Vec<DiffOp> {
let mut d = Compact::new(Replace::new(Capture::new()), old, new);
let result = match alg {
Algorithm::Myers => crate::algorithms::myers::diff_fp_f64_deadline(&mut d, old, old_range, new, new_range, epsilon, deadline),
Algorithm::Patience => crate::algorithms::patience::diff_fp_f64_deadline(&mut d, old, old_range, new, new_range, epsilon, deadline),
Algorithm::Lcs => crate::algorithms::lcs::diff_fp_f64_deadline(&mut d, old, old_range, new, new_range, epsilon, deadline),
Algorithm::Myers => crate::algorithms::myers::diff_fp_f64_deadline(
&mut d, old, old_range, new, new_range, epsilon, deadline,
),
Algorithm::Patience => crate::algorithms::patience::diff_fp_f64_deadline(
&mut d, old, old_range, new, new_range, epsilon, deadline,
),
Algorithm::Lcs => crate::algorithms::lcs::diff_fp_f64_deadline(
&mut d, old, old_range, new, new_range, epsilon, deadline,
),
};
result.unwrap();
d.into_inner().into_inner().into_ops()
@ -268,10 +280,10 @@ fn test_non_string_iter_change() {
fn test_fp_epsilon() {
let old = vec![1.0, 2.0, 3.0];
let new = vec![1.001, 2.0, 2.999];
let ops_tight = capture_diff_slices_fp(Algorithm::Myers, &old, &new, 0.0001);
assert!(ops_tight.len() > 1);
let ops_loose = capture_diff_slices_fp(Algorithm::Myers, &old, &new, 0.01);
assert_eq!(ops_loose.len(), 1);
}
@ -280,9 +292,7 @@ fn test_fp_epsilon() {
fn test_fp_nan() {
let old = vec![f32::NAN];
let new = vec![f32::NAN];
let ops = capture_diff_slices_fp(Algorithm::Myers, &old, &new, 0.001);
assert_eq!(ops.len(), 1);
}