Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ equivalent = { version = "1.0", default-features = false }
hashbrown = { version = "0.16.1", default-features = false }

arbitrary = { version = "1.0", optional = true, default-features = false }
approxim = { version = "0.6.6", optional = true }
quickcheck = { version = "1.0", optional = true, default-features = false }
serde_core = { version = "1.0.220", optional = true, default-features = false }
rayon = { version = "1.9", optional = true }
Expand All @@ -42,6 +43,7 @@ serde = { version = "1.0", default-features = false, features = ["derive"] }
default = ["std"]
std = []
serde = ["dep:serde_core", "dep:serde"]
approxim = ["dep:approxim", "std"]

# for testing only, of course
test_debug = []
Expand All @@ -59,7 +61,7 @@ features = ["arbitrary", "quickcheck", "serde", "borsh", "rayon", "sval"]
rustdoc-args = ["--cfg", "docsrs"]

[workspace]
members = ["test-nostd", "test-serde", "test-sval"]
members = ["test-approxim", "test-nostd", "test-serde", "test-sval"]

[lints.rust]
private-bounds = "deny"
Expand Down
87 changes: 87 additions & 0 deletions src/approxim.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#![cfg_attr(docsrs, doc(cfg(feature = "approxim")))]
//! Trait implementations from [`approxim`](https://docs.rs/approxim/latest/approxim/) crate
//! for [`IndexMap`].
//!
//! Keys are compared using `PartialEq` and values are compared using the approximate comparison
//! traits. Insertion order is not taken into account for the comparison, just as with
//! [`PartialEq`].

use core::hash::BuildHasher;
use std::hash::Hash;

use approxim::{AbsDiffEq, RelativeEq, UlpsEq};

use crate::IndexMap;

impl<K, V1, V2, S1, S2> AbsDiffEq<IndexMap<K, V2, S2>> for IndexMap<K, V1, S1>
where
K: Eq + Hash,
V1: AbsDiffEq<V2>,
V1::Epsilon: Copy,
S1: BuildHasher,
S2: BuildHasher,
{
type Epsilon = V1::Epsilon;

fn default_epsilon() -> Self::Epsilon {
V1::default_epsilon()
}

fn abs_diff_eq(&self, other: &IndexMap<K, V2, S2>, epsilon: Self::Epsilon) -> bool {
self.len() == other.len()
&& self.iter().all(|(key, value)| {
other
.get(key)
.map_or(false, |v| value.abs_diff_eq(v, epsilon))
})
}
}

impl<K, V1, V2, S1, S2> RelativeEq<IndexMap<K, V2, S2>> for IndexMap<K, V1, S1>
where
K: Eq + Hash,
V1: RelativeEq<V2>,
V1::Epsilon: Copy,
S1: BuildHasher,
S2: BuildHasher,
{
fn default_max_relative() -> Self::Epsilon {
V1::default_max_relative()
}

fn relative_eq(
&self,
other: &IndexMap<K, V2, S2>,
epsilon: Self::Epsilon,
max_relative: Self::Epsilon,
) -> bool {
self.len() == other.len()
&& self.iter().all(|(key, value)| {
other
.get(key)
.map_or(false, |v| value.relative_eq(v, epsilon, max_relative))
})
}
}

impl<K, V1, V2, S1, S2> UlpsEq<IndexMap<K, V2, S2>> for IndexMap<K, V1, S1>
where
K: Eq + Hash,
V1: UlpsEq<V2>,
V1::Epsilon: Copy,
S1: BuildHasher,
S2: BuildHasher,
{
fn default_max_ulps() -> u32 {
V1::default_max_ulps()
}

fn ulps_eq(&self, other: &IndexMap<K, V2, S2>, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
self.len() == other.len()
&& self.iter().all(|(key, value)| {
other
.get(key)
.map_or(false, |v| value.ulps_eq(v, epsilon, max_ulps))
})
}
}
8 changes: 8 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
//! dependency that arose between [`borsh`] and `indexmap`, `borsh v1.5.6`
//! added an `indexmap` feature that should be used instead of enabling the
//! feature here.
//! * `approxim`: Adds implementations for [`AbsDiffEq`], [`RelativeEq`] and [`UlpsEq`]
//! to [`IndexMap`].
//!
//! _Note: only the `std` feature is enabled by default._
//!
Expand All @@ -53,6 +55,9 @@
//! [`borsh`]: `::borsh`
//! [`arbitrary::Arbitrary`]: `::arbitrary::Arbitrary`
//! [`quickcheck::Arbitrary`]: `::quickcheck::Arbitrary`
//! [`AbsDiffEq`]: `::approxim::AbsDiffEq`
//! [`RelativeEq`]: `::approxim::RelativeEq`
//! [`UlpsEq`]: `::approxim::UlpsEq`
//!
//! ### Alternate Hashers
//!
Expand Down Expand Up @@ -105,6 +110,9 @@ extern crate alloc;
#[macro_use]
extern crate std;

#[cfg(feature = "approxim")]
#[cfg_attr(docsrs, doc(cfg(feature = "approxim")))]
mod approxim;
mod arbitrary;
#[macro_use]
mod macros;
Expand Down
11 changes: 11 additions & 0 deletions test-approxim/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "test-approxim"
version = "0.1.0"
publish = false
edition = "2021"

[dependencies]

[dev-dependencies]
approxim = { version = "0.6.6" }
indexmap = { path = "..", features = ["approxim"] }
77 changes: 77 additions & 0 deletions test-approxim/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#![cfg(test)]

use approxim::{assert_abs_diff_eq, assert_relative_eq, assert_ulps_eq};
use indexmap::indexmap;

#[test]
fn test_abs_diff_eq() {
// check with non-zero epsilon
let left = indexmap! { 1 => 1000, 2 => 2000 };
let right = indexmap! { 1 => 1001, 2 => 1999 };
assert_abs_diff_eq!(left, right, epsilon = 1);

// check different insertion order
let left = indexmap! { 1 => 2, 3 => 4 };
let right = indexmap! { 3 => 4, 1 => 2, };
assert_abs_diff_eq!(left, right);
}

#[test]
#[should_panic]
fn test_abs_diff_eq_len() {
let left = indexmap! { 1 => 1000, 2 => 2000 };
let right = indexmap! { 1 => 1000, 2 => 2000, 3 => 3000 };
assert_abs_diff_eq!(left, right);
}

#[test]
fn test_relative_eq() {
// check with non-default epsilon
let left = indexmap! { 1 => 1.71828182, 2 => 1.0 };
let right = indexmap! { 1 => 1.718281815, 2 => 1.00000001 };
assert_relative_eq!(left, right, epsilon = 1e-8);

// check with non-default max relative value
let left = indexmap! { 1 => 1., 2 => 1. };
let right = indexmap! { 1 => 1.2, 2 => 1.5 };
assert_relative_eq!(left, right, max_relative = 0.34);

// check different insertion order
let left = indexmap! { 1 => 2., 3 => 8. };
let right = indexmap! { 3 => 8., 1 => 2., };
assert_relative_eq!(left, right);
}

#[test]
#[should_panic]
fn test_relative_eq_len() {
let left = indexmap! { 1 => 0., 2 => 1. };
let right = indexmap! { 1 => 0., 2 => 1., 3 => 2. };
assert_relative_eq!(left, right);
}

#[test]
fn test_ulps_eq() {
// check with non-default epsilon
let left = indexmap! { 1 => 0., 2 => 0. };
let right = indexmap! { 1 => 1e-41, 2 => 1e-40 };
assert_ulps_eq!(left, right, epsilon = 1e-40);

// check with non-default max ulps value
let left = indexmap! { 1 => 1., 2 => 1. };
let right = indexmap! { 1 => 1. + 1e-16, 2 => 1. + 1e-15 };
assert_ulps_eq!(left, right, max_ulps = 5);

// check different insertion order
let left = indexmap! { 1 => 2., 3 => 8. };
let right = indexmap! { 3 => 8., 1 => 2., };
assert_ulps_eq!(left, right);
}

#[test]
#[should_panic]
fn test_ulps_eq_len() {
let left = indexmap! { 1 => 0., 2 => 1. };
let right = indexmap! { 1 => 0., 2 => 1., 3 => 2. };
assert_ulps_eq!(left, right);
}