Skip to content

Commit 870ed67

Browse files
committed
feat: add map-key functionality for NamedRef and Arc-backed objects
1 parent ddb6bf7 commit 870ed67

File tree

3 files changed

+252
-18
lines changed

3 files changed

+252
-18
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Add support for parsing `.fzn` files when enabling the `fzn` feature.
1313
Users can access this functionality via the `FlatZinc::from_fzn` method.
14+
- Add helper type `ArcKey`, to use in collections that use variables or arrays as keys.
15+
`ArcKey` uses pointer identity to determine its order, equality, and hash value.
16+
Similarly, `NamedRef` can be used as a key, where the `name` attribute of variables and arrays are used to compare.
1417

1518
### Changed
1619

src/helpers.rs

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
//! Helper structures, methods, and functions to support the serialization and
2+
//! deserialization of FlatZinc data.
3+
4+
use std::{
5+
hash::{Hash, Hasher},
6+
ops::Deref,
7+
sync::Arc,
8+
};
9+
10+
#[derive(Debug, Clone)]
11+
/// A wrapper around an [`Arc`] that can be used as a key for collections, such
12+
/// as [`BTreeMap`](std::collections::BTreeMap),
13+
/// [`HashMap`](std::collections::HashMap), and
14+
/// [`HashSet`](std::collections::HashSet).
15+
///
16+
/// This struct uses pointer equality for comparison, so two [`ArcKey`]
17+
/// instances from [`Arc`] objects that share the same value will be considered
18+
/// equal. However, two `T` values with the same contents but different memory
19+
/// addresses will not be considered equal.
20+
pub struct ArcKey<T> {
21+
/// The underlying [`Arc`] value.
22+
key: Arc<T>,
23+
}
24+
25+
impl<T> From<ArcKey<T>> for Arc<T> {
26+
fn from(value: ArcKey<T>) -> Self {
27+
value.key
28+
}
29+
}
30+
31+
impl<T> ArcKey<T> {
32+
/// Creates a new [`ArcKey`] from the given [`Arc`].
33+
pub fn new(key: Arc<T>) -> Self {
34+
Self { key }
35+
}
36+
}
37+
38+
impl<T> Deref for ArcKey<T> {
39+
type Target = T;
40+
41+
fn deref(&self) -> &Self::Target {
42+
self.key.deref()
43+
}
44+
}
45+
46+
impl<T> Eq for ArcKey<T> {}
47+
48+
impl<T> Hash for ArcKey<T> {
49+
fn hash<H: Hasher>(&self, state: &mut H) {
50+
Arc::as_ptr(&self.key).hash(state);
51+
}
52+
}
53+
54+
impl<T> Ord for ArcKey<T> {
55+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
56+
Arc::as_ptr(&self.key).cmp(&Arc::as_ptr(&other.key))
57+
}
58+
}
59+
60+
impl<T> PartialEq for ArcKey<T> {
61+
fn eq(&self, other: &Self) -> bool {
62+
Arc::ptr_eq(&self.key, &other.key)
63+
}
64+
}
65+
66+
impl<T> PartialOrd for ArcKey<T> {
67+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
68+
Some(self.cmp(other))
69+
}
70+
}
71+
72+
#[cfg(test)]
73+
mod tests {
74+
use std::{
75+
collections::{BTreeMap, HashMap, HashSet},
76+
sync::Arc,
77+
};
78+
79+
use crate::helpers::ArcKey;
80+
81+
#[test]
82+
fn arc_key() {
83+
let key = Arc::new(42);
84+
let arc_key = ArcKey::new(Arc::clone(&key));
85+
assert_eq!(*arc_key, 42);
86+
assert_eq!(arc_key.key, key);
87+
}
88+
89+
#[test]
90+
fn arc_key_uses_pointer_equality() {
91+
let key = Arc::new(String::from("value"));
92+
let same_ptr = ArcKey::new(Arc::clone(&key));
93+
let also_same_ptr = ArcKey::new(Arc::clone(&key));
94+
let different_ptr = ArcKey::new(Arc::new(String::from("value")));
95+
96+
assert_eq!(same_ptr, also_same_ptr);
97+
assert_ne!(same_ptr, different_ptr);
98+
}
99+
100+
#[test]
101+
fn arc_key_works_in_btree_map() {
102+
let first = Arc::new(String::from("shared"));
103+
let second = Arc::new(String::from("shared"));
104+
let first_key = ArcKey::new(Arc::clone(&first));
105+
let second_key = ArcKey::new(Arc::clone(&second));
106+
107+
let mut map = BTreeMap::new();
108+
let _ = map.insert(first_key.clone(), "first");
109+
let _ = map.insert(ArcKey::new(first), "updated");
110+
let _ = map.insert(second_key.clone(), "second");
111+
112+
assert_eq!(map.len(), 2);
113+
assert_eq!(map.get(&first_key), Some(&"updated"));
114+
assert_eq!(map.get(&second_key), Some(&"second"));
115+
}
116+
117+
#[test]
118+
fn arc_key_works_in_hash_map() {
119+
let key = Arc::new(String::from("entry"));
120+
let same_ptr = ArcKey::new(Arc::clone(&key));
121+
let different_ptr = ArcKey::new(Arc::new(String::from("entry")));
122+
let mut map = HashMap::new();
123+
124+
let _ = map.insert(same_ptr.clone(), 1);
125+
let _ = map.insert(ArcKey::new(Arc::clone(&key)), 2);
126+
let _ = map.insert(different_ptr.clone(), 3);
127+
128+
assert_eq!(map.len(), 2);
129+
assert_eq!(map.get(&same_ptr), Some(&2));
130+
assert_eq!(map.get(&ArcKey::new(key)), Some(&2));
131+
assert_eq!(map.get(&different_ptr), Some(&3));
132+
}
133+
134+
#[test]
135+
fn arc_key_works_in_hash_set() {
136+
let key = Arc::new(7_u32);
137+
let same_ptr = ArcKey::new(Arc::clone(&key));
138+
let different_ptr = ArcKey::new(Arc::new(7_u32));
139+
140+
let mut set = HashSet::new();
141+
let _ = set.insert(same_ptr.clone());
142+
let _ = set.insert(ArcKey::new(key));
143+
let _ = set.insert(different_ptr.clone());
144+
145+
assert_eq!(set.len(), 2);
146+
assert!(set.contains(&same_ptr));
147+
assert!(set.contains(&different_ptr));
148+
}
149+
}

src/lib.rs

Lines changed: 100 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,17 @@
116116
mod error;
117117
#[cfg(feature = "fzn")]
118118
mod fzn;
119+
pub mod helpers;
119120
#[cfg(any(feature = "fzn", feature = "serde"))]
120121
mod intermediate;
121122
#[cfg(feature = "serde")]
122123
mod serde_impl;
123124

124125
use std::{
126+
cmp::Ordering,
125127
collections::HashSet,
126128
fmt::{Debug, Display},
129+
hash::{Hash, Hasher},
127130
sync::{Arc, Weak},
128131
};
129132

@@ -132,6 +135,7 @@ pub use rangelist::RangeList;
132135
use serde::{Deserializer, Serialize};
133136

134137
pub use crate::error::{FznParseError, LinkError};
138+
use crate::helpers::ArcKey;
135139

136140
/// Additional information provided in a standardized format for declarations,
137141
/// constraints, or solve objectives
@@ -381,9 +385,15 @@ pub enum Method<Identifier = String> {
381385
/// It is possible for an [`Array`] to exist without an `name` attribute, if a
382386
/// reference to such an [`Array`] is used as a [`NamedRef`], serialization can
383387
/// panic.
384-
#[derive(Clone, PartialEq, Debug)]
388+
///
389+
/// [`NamedRef`] compares, hashes, and orders by the referenced declaration
390+
/// name. As a consequence, two values that refer to different allocations but
391+
/// expose the same name are considered equal, and a variable and array with
392+
/// the same name are also treated as equal for these trait implementations.
393+
/// Note that this cannot occur in valid FlatZinc models.
394+
#[derive(Clone, Debug)]
385395
pub enum NamedRef<Identifier = String> {
386-
/// Reference to a variable
396+
/// Reference to a variable.
387397
Variable(Arc<Variable<Identifier>>),
388398
/// Reference to an array.
389399
Array(Arc<Array<Identifier>>),
@@ -441,11 +451,6 @@ pub struct Variable<Identifier = String> {
441451
pub introduced: bool,
442452
}
443453

444-
/// Return a unique key for a specific variable or array allocation.
445-
fn arc_key<T>(arc: &Arc<T>) -> usize {
446-
Arc::as_ptr(arc) as usize
447-
}
448-
449454
impl<Identifier: Display> Display for Annotation<Identifier> {
450455
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
451456
match self {
@@ -613,6 +618,27 @@ impl<Identifier> Array<Identifier> {
613618
.any(|lit| matches!(lit, Literal::Variable(_)));
614619
(ty, is_var)
615620
}
621+
622+
/// Converts an array reference into an [`ArcKey`](crate::helpers::ArcKey).
623+
///
624+
/// This is useful when storing arrays in collections such as
625+
/// [`HashMap`](std::collections::HashMap),
626+
/// [`HashSet`](std::collections::HashSet), and
627+
/// [`BTreeMap`](std::collections::BTreeMap), where the key should identify
628+
/// the specific parsed array object rather than its contents or `name`.
629+
///
630+
/// The resulting key uses the allocation / pointer identity of this
631+
/// [`Arc`]. Two arrays with the same name and equal contents will
632+
/// therefore compare as different keys if they are stored in different
633+
/// allocations.
634+
///
635+
/// During FlatZinc parsing and deserialization, this crate guarantees that
636+
/// identical top-level arrays are allocated only once. In those cases,
637+
/// `ArcKey` is a good fit for keying collections by the canonical parsed
638+
/// array object.
639+
pub fn into_key(self: Arc<Self>) -> ArcKey<Self> {
640+
ArcKey::new(self)
641+
}
616642
}
617643

618644
impl<Identifier: Display> Display for Constraint<Identifier> {
@@ -706,12 +732,13 @@ impl<Identifier> Default for FlatZinc<Identifier> {
706732

707733
impl<Identifier: Display> Display for FlatZinc<Identifier> {
708734
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
709-
let output_map: HashSet<_> = self.output.iter().map(|output| output.arc_key()).collect();
735+
let output_map: HashSet<_> = self.output.iter().collect();
710736

711737
for var in &self.variables {
712738
write!(f, "var {}", var.ty)?;
713739
write!(f, ": {}", var.name)?;
714-
if output_map.contains(&arc_key(var)) {
740+
let name_ref: NamedRef<_> = Arc::clone(var).into();
741+
if output_map.contains(&name_ref) {
715742
write!(f, " ::output_var")?;
716743
}
717744
if var.defined {
@@ -734,7 +761,8 @@ impl<Identifier: Display> Display for FlatZinc<Identifier> {
734761
if is_var { "var " } else { "" },
735762
arr.name
736763
)?;
737-
if output_map.contains(&arc_key(arr)) {
764+
let name_ref: NamedRef<_> = Arc::clone(arr).into();
765+
if output_map.contains(&name_ref) {
738766
write!(f, " ::output_array([1..{}])", arr.contents.len())?;
739767
}
740768
if arr.defined {
@@ -789,14 +817,6 @@ impl<Identifier: Display> Display for Method<Identifier> {
789817
}
790818

791819
impl<Identifier> NamedRef<Identifier> {
792-
/// Return a unique key for the referenced variable or array allocation.
793-
fn arc_key(&self) -> usize {
794-
match self {
795-
NamedRef::Variable(arc) => Arc::as_ptr(arc) as usize,
796-
NamedRef::Array(arc) => Arc::as_ptr(arc) as usize,
797-
}
798-
}
799-
800820
/// Return the identifier of the referenced output target.
801821
pub fn name(&self) -> &str {
802822
match self {
@@ -806,6 +826,44 @@ impl<Identifier> NamedRef<Identifier> {
806826
}
807827
}
808828

829+
impl<Identifier> Eq for NamedRef<Identifier> {}
830+
831+
impl<Identifier> From<Arc<Array<Identifier>>> for NamedRef<Identifier> {
832+
fn from(arc: Arc<Array<Identifier>>) -> Self {
833+
NamedRef::Array(arc)
834+
}
835+
}
836+
837+
impl<Identifier> From<Arc<Variable<Identifier>>> for NamedRef<Identifier> {
838+
fn from(arc: Arc<Variable<Identifier>>) -> Self {
839+
NamedRef::Variable(arc)
840+
}
841+
}
842+
843+
impl<Identifier> Hash for NamedRef<Identifier> {
844+
fn hash<H: Hasher>(&self, state: &mut H) {
845+
self.name().hash(state);
846+
}
847+
}
848+
849+
impl<Identifier> Ord for NamedRef<Identifier> {
850+
fn cmp(&self, other: &Self) -> Ordering {
851+
self.name().cmp(other.name())
852+
}
853+
}
854+
855+
impl<Identifier> PartialEq for NamedRef<Identifier> {
856+
fn eq(&self, other: &Self) -> bool {
857+
self.name() == other.name()
858+
}
859+
}
860+
861+
impl<Identifier> PartialOrd for NamedRef<Identifier> {
862+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
863+
Some(self.cmp(other))
864+
}
865+
}
866+
809867
impl<Identifier> Default for SolveObjective<Identifier> {
810868
fn default() -> Self {
811869
Self {
@@ -850,3 +908,27 @@ impl Display for Type {
850908
}
851909
}
852910
}
911+
912+
impl<Identifier> Variable<Identifier> {
913+
/// Converts a variable reference into an
914+
/// [`ArcKey`](crate::helpers::ArcKey).
915+
///
916+
/// This is useful when storing variables in collections such as
917+
/// [`HashMap`](std::collections::HashMap),
918+
/// [`HashSet`](std::collections::HashSet), and
919+
/// [`BTreeMap`](std::collections::BTreeMap), where the key should identify
920+
/// the specific parsed variable object rather than its fields or `name`.
921+
///
922+
/// The resulting key uses the allocation / pointer identity of this
923+
/// [`Arc`]. Two variables with the same name and equal fields will
924+
/// therefore compare as different keys if they are stored in different
925+
/// allocations.
926+
///
927+
/// During FlatZinc parsing and deserialization, this crate guarantees that
928+
/// identical top-level variables are allocated only once. In those cases,
929+
/// `ArcKey` is a good fit for keying collections by the canonical parsed
930+
/// variable object.
931+
pub fn into_key(self: Arc<Self>) -> ArcKey<Self> {
932+
ArcKey::new(self)
933+
}
934+
}

0 commit comments

Comments
 (0)