Skip to main content

owl_structural_model/
operand_set.rs

1//! Unordered structural identity without imposing payload traits at construction.
2
3use crate::{OperandList, StructuralModelError};
4use serde::{Deserialize, Serialize};
5use std::collections::hash_map::DefaultHasher;
6use std::hash::{Hash, Hasher};
7use std::ops::Deref;
8
9/// An unordered OWL structural association.
10/// Equality and hashing ignore order and repeated equal members. Iteration and
11/// Serde preserve source occurrences; they do not define a canonical encoding.
12/// `MIN` checks functional-syntax occurrence arity, which permits repetitions.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14#[serde(transparent)]
15pub struct OperandSet<T, const MIN: usize = 0>(OperandList<T, MIN>);
16impl<T, const MIN: usize> OperandSet<T, MIN> {
17    /// Validate structural occurrence arity without evaluating members.
18    /// # Errors
19    /// Returns a structural error when fewer than `MIN` occurrences are supplied.
20    pub fn try_new(items: Vec<T>) -> Result<Self, StructuralModelError> {
21        Ok(Self(OperandList::try_new(items)?))
22    }
23    /// Return source occurrences, without claiming a canonical order.
24    #[must_use]
25    pub fn as_slice(&self) -> &[T] {
26        self.0.as_slice()
27    }
28}
29impl<T> OperandSet<T, 0> {
30    /// Create a possibly empty unordered association.
31    #[must_use]
32    pub fn new(items: Vec<T>) -> Self {
33        Self(OperandList::new(items))
34    }
35}
36impl<T, const MIN: usize> Deref for OperandSet<T, MIN> {
37    type Target = [T];
38    fn deref(&self) -> &Self::Target {
39        self.as_slice()
40    }
41}
42impl<'a, T, const MIN: usize> IntoIterator for &'a OperandSet<T, MIN> {
43    type Item = &'a T;
44    type IntoIter = std::slice::Iter<'a, T>;
45    fn into_iter(self) -> Self::IntoIter {
46        self.as_slice().iter()
47    }
48}
49impl<T: PartialEq, const MIN: usize> PartialEq for OperandSet<T, MIN> {
50    fn eq(&self, other: &Self) -> bool {
51        self.iter().all(|member| other.contains(member))
52            && other.iter().all(|member| self.contains(member))
53    }
54}
55impl<T: Eq, const MIN: usize> Eq for OperandSet<T, MIN> {}
56impl<T: Hash, const MIN: usize> Hash for OperandSet<T, MIN> {
57    fn hash<H: Hasher>(&self, state: &mut H) {
58        hash_unordered(self.as_slice(), state);
59    }
60}
61/// Exactly two unordered source occurrences, including a repeated property.
62#[derive(Debug, Clone, Serialize, Deserialize)]
63#[serde(transparent)]
64pub struct UnorderedPair<T>([T; 2]);
65impl<T> UnorderedPair<T> {
66    /// Construct an unordered pair with type-checked arity.
67    #[must_use]
68    pub fn new(first: T, second: T) -> Self {
69        Self([first, second])
70    }
71    /// Return both source occurrences.
72    #[must_use]
73    pub fn as_slice(&self) -> &[T] {
74        &self.0
75    }
76}
77impl<T: PartialEq> PartialEq for UnorderedPair<T> {
78    fn eq(&self, other: &Self) -> bool {
79        (self.0[0] == other.0[0] && self.0[1] == other.0[1])
80            || (self.0[0] == other.0[1] && self.0[1] == other.0[0])
81    }
82}
83impl<T: Eq> Eq for UnorderedPair<T> {}
84impl<T: Hash> Hash for UnorderedPair<T> {
85    fn hash<H: Hasher>(&self, state: &mut H) {
86        hash_unordered(&self.0, state);
87    }
88}
89fn hash_unordered<T: Hash, H: Hasher>(members: &[T], state: &mut H) {
90    // bounded: one digest per occurrence in this caller-budgeted structural value.
91    let mut hashes: Vec<u64> = members
92        .iter()
93        .map(|member| {
94            let mut digest = DefaultHasher::new();
95            member.hash(&mut digest);
96            digest.finish()
97        })
98        .collect();
99    hashes.sort_unstable();
100    hashes.dedup();
101    hashes.hash(state);
102}