diff --git a/Cargo.lock b/Cargo.lock index 12f4005..4b495fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,27 +21,33 @@ dependencies = [ [[package]] name = "identified_vec" -version = "0.1.4" +version = "0.1.5" dependencies = [ - "identified_vec", - "maplit", - "rand", "serde", - "serde_json", "thiserror", ] +[[package]] +name = "identified_vec_macros" +version = "0.1.5" +dependencies = [ + "identified_vec", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "libc" -version = "0.2.150" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "maplit" @@ -105,9 +111,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "serde" @@ -151,6 +157,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tests" +version = "0.1.0" +dependencies = [ + "identified_vec", + "identified_vec_macros", + "maplit", + "rand", + "serde", + "serde_json", +] + [[package]] name = "thiserror" version = "1.0.50" diff --git a/Cargo.toml b/Cargo.toml index ed3ce7a..eaa688f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,26 +1,5 @@ -[package] -name = "identified_vec" -version = "0.1.4" -edition = "2021" -authors = ["Alexander Cyon "] -description = "Like HashSet but retaining INSERTION order and without `Hash` requirement on the Element type." -license = "MIT" -readme = "README.md" -repository = "https://github.com/Sajjon/identified_vec" -keywords = ["identifiable", "vec", "orderset", "set", "hashset"] -categories = ["data-structures"] +[workspace] -[features] -serde = ["dep:serde"] -id_prim = [] +resolver = "2" -[dependencies] -serde = { version = "1.0.193", optional = true } -thiserror = "1.0.50" - -[dev-dependencies] -identified_vec = { path = ".", features = ["id_prim", "serde"] } -serde = "1.0.193" -serde_json = "1.0.108" -rand = "0.8.5" -maplit = "1.0.2" +members = ["identified_vec", "identified_vec_macros", "tests"] diff --git a/README.md b/README.md index 8bc8f18..637203b 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ You can create an identified vec with any element type that implements the `Iden ```rust extern crate identified_vec; -use identified_vec::{IdentifiedVec, Identifiable, IdentifiedVecOf}; +use identified_vec::{IsIdentifiableVec, IdentifiedVec, Identifiable, IdentifiedVecOf}; use std::cell::RefCell; #[derive(Eq, PartialEq, Clone, Debug)] @@ -133,23 +133,6 @@ assert_eq!( ); ``` -## `get_mut` - -```rust -// or mutate with `get_mut(id)` -*users.get_mut(&"u_1337").unwrap().name.get_mut() = "Yoda"; -assert_eq!( - users.elements(), - [ - User::new("u_42", "Tom Mervolo Dolder"), - User::new("u_1337", "Yoda"), - User::new("u_237", "Marie Curie"), - ] - .iter() - .collect::>() -); -``` - Or you can provide a closure that describes an element's identity: ```rust diff --git a/identified_vec/Cargo.lock b/identified_vec/Cargo.lock new file mode 100644 index 0000000..12f4005 --- /dev/null +++ b/identified_vec/Cargo.lock @@ -0,0 +1,184 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "identified_vec" +version = "0.1.4" +dependencies = [ + "identified_vec", + "maplit", + "rand", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "libc" +version = "0.2.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "2.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/identified_vec/Cargo.toml b/identified_vec/Cargo.toml new file mode 100644 index 0000000..a692f19 --- /dev/null +++ b/identified_vec/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "identified_vec" +version = "0.1.5" +edition = "2021" +authors = ["Alexander Cyon "] +description = "Like HashSet but retaining INSERTION order and without `Hash` requirement on the Element type." +license = "MIT" +readme = "README.md" +repository = "https://github.com/Sajjon/identified_vec" +keywords = ["identifiable", "vec", "orderset", "set", "hashset"] +categories = ["data-structures"] + +[features] +default = ["id_prim"] +serde = ["dep:serde"] +id_prim = [] + +[dependencies] +serde = { version = "1.0.193", optional = true } +thiserror = "1.0.50" \ No newline at end of file diff --git a/identified_vec/src/conflict_resolution_choice.rs b/identified_vec/src/conflict_resolution_choice.rs new file mode 100644 index 0000000..4d2f1c4 --- /dev/null +++ b/identified_vec/src/conflict_resolution_choice.rs @@ -0,0 +1,10 @@ +/// Representation of a choice in a conflict resolution +/// where two elements with the same Self::ID exists, if `ChooseFirst`, +/// is specified the first/current/existing value will be used, but +/// if `ChooseLast` is specified then the new/last will be replace +/// the first/current/existing. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ConflictResolutionChoice { + ChooseFirst, + ChooseLast, +} diff --git a/src/serde_error.rs b/identified_vec/src/errors.rs similarity index 100% rename from src/serde_error.rs rename to identified_vec/src/errors.rs diff --git a/src/identifiable_trait.rs b/identified_vec/src/identifiable_trait.rs similarity index 100% rename from src/identifiable_trait.rs rename to identified_vec/src/identifiable_trait.rs diff --git a/identified_vec/src/identified_vec_into_iterator.rs b/identified_vec/src/identified_vec_into_iterator.rs new file mode 100644 index 0000000..4eb7f1a --- /dev/null +++ b/identified_vec/src/identified_vec_into_iterator.rs @@ -0,0 +1,36 @@ +use crate::is_identifiable_vec::IsIdentifiableVec; +use crate::vec::IdentifiedVec; +use std::fmt::Debug; +use std::hash::Hash; + +/// An owning iterator over the items of an `IdentifiedVec`. +pub struct IdentifiedVecIntoIterator +where + I: Eq + Hash + Clone + Debug, +{ + identified_vec: IdentifiedVec, +} + +impl IdentifiedVecIntoIterator +where + I: Eq + Hash + Clone + Debug, +{ + pub fn new(identified_vec: IdentifiedVec) -> Self { + Self { identified_vec } + } +} + +impl Iterator for IdentifiedVecIntoIterator +where + I: Eq + Hash + Clone + Debug, +{ + type Item = E; + + fn next(&mut self) -> Option { + if self.identified_vec.len() == 0 { + return None; + } + let result = self.identified_vec.remove_at(0); + Some(result) + } +} diff --git a/identified_vec/src/identified_vec_iterator.rs b/identified_vec/src/identified_vec_iterator.rs new file mode 100644 index 0000000..e63a5f1 --- /dev/null +++ b/identified_vec/src/identified_vec_iterator.rs @@ -0,0 +1,42 @@ +use crate::is_identifiable_vec::IsIdentifiableVec; +use crate::vec::IdentifiedVec; +use std::fmt::Debug; +use std::hash::Hash; + +/// An iterator over the items of an `IdentifiedVec`. +pub struct IdentifiedVecIterator<'a, I, E> +where + I: Eq + Hash + Clone + Debug, +{ + identified_vec: &'a IdentifiedVec, + index: usize, +} + +impl<'a, I, E> IdentifiedVecIterator<'a, I, E> +where + I: Eq + Hash + Clone + Debug, +{ + pub fn new(identified_vec: &'a IdentifiedVec) -> Self { + Self { + identified_vec, + index: 0, + } + } +} + +impl<'a, I, E> Iterator for IdentifiedVecIterator<'a, I, E> +where + I: Eq + Hash + Clone + Debug, +{ + type Item = &'a E; + + fn next(&mut self) -> Option { + if self.index < self.identified_vec.len() { + let id = Some(&self.identified_vec.order[self.index]).unwrap(); + self.index += 1; + return self.identified_vec.get(id); + } else { + None + } + } +} diff --git a/identified_vec/src/is_identifiable_vec.rs b/identified_vec/src/is_identifiable_vec.rs new file mode 100644 index 0000000..04a5a9c --- /dev/null +++ b/identified_vec/src/is_identifiable_vec.rs @@ -0,0 +1,266 @@ +use crate::conflict_resolution_choice::ConflictResolutionChoice; +use crate::Error; +use crate::IdentifiedVecIterator; +use std::fmt::Debug; +use std::hash::Hash; + +pub trait IsIdentifiableVec: Sized +where + ID: Eq + Hash + Clone + Debug, +{ + /// Constructs a new, empty `IdentifiedVec` with the specified + /// `id_of_element` closure + fn new_identifying_element(id_of_element: fn(&Element) -> ID) -> Self; + + /// Creates a new `identified_vec` from the elements in the given sequence, using a combining closure to + /// determine the element for any elements with duplicate identity. + /// + /// You use this initializer to create an `identified_vec` when you have an arbitrary sequence of elements + /// that may not have unique ids. This initializer calls the `combine` closure with the current + /// and new elements for any duplicate ids. Pass a closure as `combine` that returns the element + /// to use in the resulting `identified_vec`: The closure can choose between the two elements, combine them + /// to produce a new element, or even throw an error. + /// + /// - Parameters: + /// - elements: A sequence of elements to use for the new `identified_vec`. + /// - id_of_element: The function which extracts the identifier for an element, + /// - combine: Closure trying to combine elements `(index, first, last)` with duplicate ids, returning which element to use, by use of ConflictResolutionChoice (`ChooseFirst` or `ChooseLast`), or `Err` if you prefer. + /// - Returns: A new `identified_vec` initialized with the unique elements of `elements`. + /// - Complexity: Expected O(*n*) on average, where *n* is the count of elements, if `ID` + /// implements high-quality hashing. + fn try_from_iter_select_unique_ids_with( + elements: It, + id_of_element: fn(&Element) -> ID, + combine: fn((usize, &Element, &Element)) -> Result, + ) -> Result + where + It: IntoIterator; + + fn from_iter_select_unique_ids_with( + elements: It, + id_of_element: fn(&Element) -> ID, + combine: fn((usize, &Element, &Element)) -> ConflictResolutionChoice, + ) -> Self + where + It: IntoIterator; + + ///The ids contained in this `identified_vec`, as an `Vec` (cloned) + fn ids(&self) -> Vec; + + /// Returns the number of elements in the `identified_vec`, also referred to as its 'length'. + fn len(&self) -> usize; + + /// Returns the index for the given id. + /// + /// If an element identified by the given id is found in the `identified_vec`, this method returns an index + /// into the array that corresponds to the element. + /// + /// ``` + /// extern crate identified_vec; + /// use identified_vec::{IsIdentifiableVec, IsIdentifiableVecOf, IdentifiedVec, Identifiable, IdentifiedVecOf}; + /// + /// #[derive(Eq, PartialEq, Clone, Debug, Hash)] + /// struct User { + /// id: &'static str, + /// } + /// + /// impl User { + /// fn new(id: &'static str) -> Self { + /// Self { id } + /// } + /// } + /// + /// impl Identifiable for User { + /// type ID = &'static str; + /// fn id(&self) -> Self::ID { + /// self.id + /// } + /// } + /// + /// let mut users = + /// IdentifiedVecOf::::from_iter([User::new("u_42"), User::new("u_1729")]); + /// + /// assert_eq!(users.index_of_id(&"u_1729"), Some(1)); + /// assert_eq!(users.index_of_id(&"u_1337"), None); + /// ``` + /// + /// - Parameter id: The id to find in the `identified_vec`. + /// - Returns: The index for the element identified by `id` if found in the `identified_vec`; otherwise, + /// `None`. + /// - Complexity: Expected to be O(1) on average, if `ID` implements high-quality hashing. + fn index_of_id(&self, id: &ID) -> Option; + + fn elements(&self) -> Vec<&Element>; + + /// Returns `true` if the `identified_vec` contains the `element.` + fn contains(&self, element: &Element) -> bool; + + /// Returns `true if the `identified_vec` contains an element for the specified `id` + fn contains_id(&self, id: &ID) -> bool; + + /// Returns a reference to the element corresponding to the `id`, if found, else `None`. + fn get(&self, id: &ID) -> Option<&Element>; + + /// Returns a reference to the element at index if found, else `None`. + fn get_at_index(&self, index: usize) -> Option<&Element>; + + /// Append a new member to the end of the `identified_vec`, if the `identified_vec` doesn't already contain it. + /// + /// - Parameter item: The element to add to the `identified_vec`. + /// - Returns: A pair `(inserted, index)`, where `inserted` is a Boolean value indicating whether + /// the operation added a new element, and `index` is the index of `item` in the resulting + /// `identified_vec`. + /// - Complexity: The operation is expected to perform O(1) copy, hash, and compare operations on + /// the `ID` type, if it implements high-quality hashing. + fn append(&mut self, element: Element) -> (bool, usize); + + /// Append the contents of an iterator to the end of the set, excluding elements that are already + /// members. + /// + /// - Parameter elements: A finite sequence of elements to append. + /// - Complexity: The operation is expected to perform amortized O(1) copy, hash, and compare + /// operations on the `Element` type, if it implements high-quality hashing. + fn append_other(&mut self, other: It) + where + It: IntoIterator; + + /// Adds the given element to the `identified_vec` unconditionally, either appending it to the `identified_vec``, or + /// replacing an existing value if it's already present. + /// + /// - Parameter item: The value to append or replace. + /// - Returns: The original element that was replaced by this operation, or `None` if the value was + /// appended to the end of the collection. + /// - Complexity: The operation is expected to perform amortized O(1) copy, hash, and compare + /// operations on the `ID` type, if it implements high-quality hashing. + fn update_or_append(&mut self, element: Element) -> Option; + + /// Replace the member at the given index with a new value of the same identity. + /// + /// - Parameter item: The new value that should replace the original element. `item` must match + /// the identity of the original value. + /// - Parameter index: The index of the element to be replaced. + /// - Returns: The original element that was replaced. + fn update_at(&mut self, element: Element, index: usize) -> Element; + + fn update_with(&mut self, id: &ID, mutate: F) -> bool + where + F: Fn(&mut Element); + + /// Insert a new member to this identified_vec at the specified index, if the identified_vec doesn't already contain + /// it. + /// + /// - Parameter element: The element to insert. + /// - Returns: A pair `(inserted, index)`, where `inserted` is a Boolean value indicating whether + /// the operation added a new element, and `index` is the index of `element` in the resulting + /// identified_vec. If `inserted` is true, then the returned `index` may be different from the index + /// requested. + /// + /// - Complexity: The operation is expected to perform amortized O(`self.count`) copy, hash, and + /// compare operations on the `ID` type, if it implements high-quality hashing. (Insertions need + /// to make room in the storage identified_vec to add the inserted element.) + fn insert(&mut self, element: Element, at: usize) -> (bool, usize); + + /// Adds the given element into the set unconditionally, either inserting it at the specified + /// index, or replacing an existing value if it's already present. + /// + /// - Parameter item: The value to append or replace. + /// - Parameter index: The index at which to insert the new member if `item` isn't already in the + /// set. + /// - Returns: The original element that was replaced by this operation, or `None` if the value was + /// newly inserted into the collection. + /// - Complexity: The operation is expected to perform amortized O(1) copy, hash, and compare + /// operations on the `ID` type, if it implements high-quality hashing. + fn update_or_insert(&mut self, element: Element, index: usize) -> (Option, usize); + + /// Try to update the given element to the `identified_vec` if a element with the same ID is already present. + /// + /// - Parameter item: The value to append or replace. + /// - Returns: A Result with either the original element that was replaced by this operation, or a Error, `Error::ExpectedElementNotPresent`, specifying that the expected element is not present within the collection. + /// - Complexity: The operation is expected to perform amortized O(1) copy, hash, and compare + /// operations on the `ID` type, if it implements high-quality hashing. + fn try_update(&mut self, element: Element) -> Result; + + ///////////// + // Remove // + ///////////// + + /// Removes the element identified by the given id from the `identified_vec`. + /// + /// ``` + /// extern crate identified_vec; + /// use identified_vec::{IsIdentifiableVec, IsIdentifiableVecOf, IdentifiedVec, Identifiable, IdentifiedVecOf}; + /// + /// #[derive(Eq, PartialEq, Clone, Debug, Hash)] + /// struct User { + /// id: &'static str, + /// } + /// + /// impl User { + /// fn new(id: &'static str) -> Self { + /// Self { id } + /// } + /// } + /// + /// impl Identifiable for User { + /// type ID = &'static str; + /// fn id(&self) -> Self::ID { + /// self.id + /// } + /// } + /// + /// let mut users = + /// IdentifiedVecOf::::from_iter([User::new("u_42"), User::new("u_1729")]); + /// + /// assert_eq!(users.remove_by_id(&"u_1729"), Some(User::new("u_1729"))); + /// assert_eq!(users.elements(), [&User::new("u_42")]); + /// assert_eq!(users.remove_by_id(&"u_1337"), None); + /// assert_eq!(users.len(), 1); + /// ``` + /// + /// - Parameter id: The id of the element to be removed from the `identified_vec`. + /// - Returns: The element that was removed, or `None` if the element was not present in the array. + /// - Complexity: O(`count`) + fn remove_by_id(&mut self, id: &ID) -> Option; + + /// Removes the given element from the `identified_vec`. + /// + /// If the element is found in the `identified_vec`, this method returns the element. + /// + /// If the element isn't found in the `identified_vec`, `remove` returns `None`. + /// + /// - Parameter element: The element to remove. + /// - Returns: The value that was removed, or `None` if the element was not present in the `identified_vec`. + /// - Complexity: O(`count`) + fn remove(&mut self, element: &Element) -> Option; + + /// Removes and returns the element at the specified position. + /// + /// All the elements following the specified position are moved to close the resulting gap. + /// + /// - Parameter index: The position of the element to remove. + /// - Returns: The removed element. + /// - Precondition: `index` must be a valid index of the collection that is not equal to the + /// collection's end index. + /// - Complexity: O(`count`) + fn remove_at(&mut self, index: usize) -> Element; + + /// Removes all the elements at the specified `offsets` from the `identified_vec`. + /// + /// - Parameter offsets: The offsets of all elements to be removed. + /// - Complexity: O(*n*) where *n* is the length of the `identified_vec`. + fn remove_at_offsets(&mut self, offsets: It) + where + It: IntoIterator; + + /// Try append a new member to the end of the `identified_vec`, if the `identified_vec` already contains the element a Error will be returned. + /// + /// - Parameter item: The element to add to the `identified_vec`. + /// - Returns: Either a Ok() with a pair `(inserted, index)`, where `inserted` is a Boolean value indicating whether + /// the operation added a new element, and `index` is the index of `item` in the resulting + /// `identified_vec`. If the given ID pre-exists within the collection the function call returns `Error::ElementWithSameIDFound`. + /// - Complexity: The operation is expected to perform O(1) copy, hash, and compare operations on + /// the `ID` type, if it implements high-quality hashing. + fn try_append_new(&mut self, element: Element) -> Result<(bool, usize), Error>; + + fn iter(&self) -> IdentifiedVecIterator; +} diff --git a/identified_vec/src/is_identifiable_vec_of.rs b/identified_vec/src/is_identifiable_vec_of.rs new file mode 100644 index 0000000..247fe7a --- /dev/null +++ b/identified_vec/src/is_identifiable_vec_of.rs @@ -0,0 +1,65 @@ +use crate::identifiable_trait::Identifiable; +use crate::is_identifiable_vec::IsIdentifiableVec; +use crate::ConflictResolutionChoice; + +pub trait IsIdentifiableVecOf: + IsIdentifiableVec +{ + /// Constructs a new, empty `IdentifiedVec`, using `id()` on `Element` + /// as id function. + fn new() -> Self; + + fn from_iter(unique_elements: It) -> Self + where + It: IntoIterator; + + /// Creates a new `identified_vec` from the elements in the given sequence, using a combining closure to + /// determine the element for any elements with duplicate ids. + /// + /// You use this initializer to create an `identified_vec` when you have an arbitrary sequence of elements + /// that may not have unique ids. This initializer calls the `combine` closure with the current + /// and new elements for any duplicate ids. Pass a closure as `combine` that returns the element + /// to use in the resulting `identified_vec`: The closure can choose between the two elements, combine them + /// to produce a new element, or even throw an error. + /// + /// - Parameters: + /// - elements: A sequence of elements to use for the new `identified_vec`. + /// - combine: Closure trying to combine elements `(index, first, last)` with duplicate ids, returning which element to use, by use of ConflictResolutionChoice (`ChooseFirst` or `ChooseLast`), or `Err` if you prefer. + /// - Returns: A new `identified_vec` initialized with the unique elements of `elements`. + /// - Complexity: Expected O(*n*) on average, where *n* is the count of elements, if `ID` + /// implements high-quality hashing. + fn try_from_iter_select_unique_with( + elements: I, + combine: fn((usize, &Element, &Element)) -> Result, + ) -> Result + where + I: IntoIterator, + { + Self::try_from_iter_select_unique_ids_with(elements, |e| e.id(), combine) + } + + /// Creates a new `identified_vec` from the elements in the given sequence, using a combining closure to + /// determine the element for any elements with duplicate ids. + /// + /// You use this initializer to create an `identified_vec` when you have an arbitrary sequence of elements + /// that may not have unique ids. This initializer calls the `combine` closure with the current + /// and new elements for any duplicate ids. Pass a closure as `combine` that returns the element + /// to use in the resulting `identified_vec`: The closure can choose between the two elements, combine them + /// to produce a new element, or even throw an error. + /// + /// - Parameters: + /// - elements: A sequence of elements to use for the new `identified_vec`. + /// - combine: Closure used combine elements `(index, first, last)` with duplicate ids, returning which element to use, by use of ConflictResolutionChoice (`ChooseFirst` or `ChooseLast`) + /// - Returns: A new `identified_vec` initialized with the unique elements of `elements`. + /// - Complexity: Expected O(*n*) on average, where *n* is the count of elements, if `ID` + /// implements high-quality hashing. + fn from_iter_select_unique_with( + elements: I, + combine: fn((usize, &Element, &Element)) -> ConflictResolutionChoice, + ) -> Self + where + I: IntoIterator, + { + Self::from_iter_select_unique_ids_with(elements, |e| e.id(), combine) + } +} diff --git a/identified_vec/src/is_identified_vec_via.rs b/identified_vec/src/is_identified_vec_via.rs new file mode 100644 index 0000000..9ada762 --- /dev/null +++ b/identified_vec/src/is_identified_vec_via.rs @@ -0,0 +1,215 @@ +use crate::conflict_resolution_choice::ConflictResolutionChoice; +use crate::vec::ItemsCloned; +use crate::{ + Error, Identifiable, IdentifiedVecIterator, IdentifiedVecOf, IsIdentifiableVec, + IsIdentifiableVecOf, +}; + +/// https://stackoverflow.com/a/66537661/1311272 +pub trait ViaMarker {} + +pub trait IsIdentifiableVecOfVia: + IsIdentifiableVecOf + IntoIterator + ViaMarker +where + Element: Identifiable, +{ + fn from_identified_vec_of(identified_vec_of: IdentifiedVecOf) -> Self; + fn via_mut(&mut self) -> &mut IdentifiedVecOf; + fn via(&self) -> &IdentifiedVecOf; +} + +impl IsIdentifiableVecOf for U +where + U: ViaMarker, + Element: Identifiable, + U: IsIdentifiableVecOfVia, +{ + #[inline] + fn new() -> Self { + Self::from_identified_vec_of(IdentifiedVecOf::new()) + } + + #[inline] + fn from_iter(unique_elements: It) -> Self + where + It: IntoIterator, + { + Self::from_identified_vec_of(IdentifiedVecOf::from_iter(unique_elements)) + } +} + +impl ItemsCloned for U +where + U: ViaMarker, + Element: Identifiable + Clone, + U: IsIdentifiableVecOfVia, +{ + fn items(&self) -> Vec { + self.via().items() + } +} + +impl IsIdentifiableVec for U +where + U: ViaMarker, + Element: Identifiable, + U: IsIdentifiableVecOfVia, +{ + #[inline] + fn new_identifying_element( + id_of_element: fn(&Element) -> ::ID, + ) -> Self { + Self::from_identified_vec_of(IdentifiedVecOf::new_identifying_element(id_of_element)) + } + + #[inline] + fn try_from_iter_select_unique_ids_with( + elements: It, + id_of_element: fn(&Element) -> ::ID, + combine: fn((usize, &Element, &Element)) -> Result, + ) -> Result + where + It: IntoIterator, + { + IdentifiedVecOf::try_from_iter_select_unique_ids_with(elements, id_of_element, combine) + .map(|via| Self::from_identified_vec_of(via)) + } + + #[inline] + fn from_iter_select_unique_ids_with( + elements: It, + id_of_element: fn(&Element) -> ::ID, + combine: fn((usize, &Element, &Element)) -> ConflictResolutionChoice, + ) -> Self + where + It: IntoIterator, + { + Self::from_identified_vec_of(IdentifiedVecOf::from_iter_select_unique_ids_with( + elements, + id_of_element, + combine, + )) + } + + #[inline] + fn ids(&self) -> Vec<::ID> { + self.via().ids() + } + + #[inline] + fn len(&self) -> usize { + self.via().len() + } + + #[inline] + fn index_of_id(&self, id: &::ID) -> Option { + self.via().index_of_id(id) + } + + #[inline] + fn elements(&self) -> Vec<&Element> { + self.via().elements() + } + + #[inline] + fn contains(&self, element: &Element) -> bool { + self.via().contains(element) + } + + #[inline] + fn contains_id(&self, id: &::ID) -> bool { + self.via().contains_id(id) + } + + #[inline] + fn get(&self, id: &::ID) -> Option<&Element> { + self.via().get(id) + } + + #[inline] + fn get_at_index(&self, index: usize) -> Option<&Element> { + self.via().get_at_index(index) + } + + #[inline] + fn append(&mut self, element: Element) -> (bool, usize) { + self.via_mut().append(element) + } + + #[inline] + fn append_other(&mut self, other: It) + where + It: IntoIterator, + { + self.via_mut().append_other(other) + } + + #[inline] + fn update_or_append(&mut self, element: Element) -> Option { + self.via_mut().update_or_append(element) + } + + #[inline] + fn update_at(&mut self, element: Element, index: usize) -> Element { + self.via_mut().update_at(element, index) + } + + #[inline] + fn insert(&mut self, element: Element, at: usize) -> (bool, usize) { + self.via_mut().insert(element, at) + } + + #[inline] + fn update_or_insert(&mut self, element: Element, index: usize) -> (Option, usize) { + self.via_mut().update_or_insert(element, index) + } + + #[inline] + fn try_update(&mut self, element: Element) -> Result { + self.via_mut().try_update(element) + } + + #[inline] + fn update_with(&mut self, id: &::ID, mutate: F) -> bool + where + F: Fn(&mut Element), + { + self.via_mut().update_with(id, mutate) + } + + #[inline] + fn try_append_new(&mut self, element: Element) -> Result<(bool, usize), Error> { + self.via_mut().try_append_new(element) + } + + ///////////// + // Remove // + ///////////// + #[inline] + fn remove_by_id(&mut self, id: &::ID) -> Option { + self.via_mut().remove_by_id(id) + } + + #[inline] + fn remove(&mut self, element: &Element) -> Option { + self.via_mut().remove(element) + } + + #[inline] + fn remove_at(&mut self, index: usize) -> Element { + self.via_mut().remove_at(index) + } + + #[inline] + fn remove_at_offsets(&mut self, offsets: It) + where + It: IntoIterator, + { + self.via_mut().remove_at_offsets(offsets) + } + + #[inline] + fn iter(&self) -> IdentifiedVecIterator<::ID, Element> { + self.via().iter() + } +} diff --git a/src/lib.rs b/identified_vec/src/lib.rs similarity index 86% rename from src/lib.rs rename to identified_vec/src/lib.rs index 5c7c2a2..6dd5419 100644 --- a/src/lib.rs +++ b/identified_vec/src/lib.rs @@ -10,7 +10,7 @@ //! //! ``` //! extern crate identified_vec; -//! use identified_vec::{IdentifiedVec, IdentifiedVecOf, Identifiable}; +//! use identified_vec::{IsIdentifiableVec, IsIdentifiableVecOf, IdentifiedVec, IdentifiedVecOf, Identifiable}; //! use std::cell::RefCell; //! //! #[derive(Eq, PartialEq, Clone, Debug)] @@ -104,39 +104,36 @@ //! .iter() //! .collect::>() //! ); -//! -//! // or mutate with `get_mut(id)` -//! *users.get_mut(&"u_1337").unwrap().name.get_mut() = "Yoda"; -//! assert_eq!( -//! users.elements(), -//! [ -//! User::new("u_42", "Tom Mervolo Dolder"), -//! User::new("u_1337", "Yoda"), -//! User::new("u_237", "Marie Curie"), -//! ] -//! .iter() -//! .collect::>() -//! ); //! ``` //! //! Or you can provide a closure that describes an element's identity: //! //! ``` //! extern crate identified_vec; -//! use identified_vec::{IdentifiedVec, IdentifiedVecOf, Identifiable}; +//! use identified_vec::{IsIdentifiableVec, IdentifiedVec, IdentifiedVecOf, Identifiable}; //! //! // closure which plucks out an ID from an element. //! let numbers = IdentifiedVec::::new_identifying_element(|e| *e); //! ``` +mod conflict_resolution_choice; +mod errors; mod identifiable_trait; +mod identified_vec_into_iterator; +mod identified_vec_iterator; +mod is_identifiable_vec; +mod is_identifiable_vec_of; +mod is_identified_vec_via; mod primitives_identifiable; -mod serde_error; mod vec; mod vec_of; pub mod identified_vec { //! A collection of unique identifiable elements which retains **insertion** order. + pub use crate::conflict_resolution_choice::*; + pub use crate::identified_vec_into_iterator::*; + pub use crate::identified_vec_iterator::*; + pub use crate::is_identifiable_vec::*; pub use crate::vec::*; } @@ -147,13 +144,14 @@ pub mod identified_vec_of { //! skip the `id_of_element: fn(&Element) -> ID` closure when //! initializing a new identified vec. pub use crate::identifiable_trait::*; + pub use crate::is_identifiable_vec_of::*; + pub use crate::is_identified_vec_via::*; pub use crate::vec_of::*; #[cfg(feature = "id_prim")] pub use crate::primitives_identifiable::*; - #[cfg(feature = "serde")] - pub use crate::serde_error::*; + pub use crate::errors::*; } pub use crate::identified_vec::*; diff --git a/src/primitives_identifiable.rs b/identified_vec/src/primitives_identifiable.rs similarity index 100% rename from src/primitives_identifiable.rs rename to identified_vec/src/primitives_identifiable.rs diff --git a/src/vec.rs b/identified_vec/src/vec.rs similarity index 73% rename from src/vec.rs rename to identified_vec/src/vec.rs index 506d818..9957d02 100644 --- a/src/vec.rs +++ b/identified_vec/src/vec.rs @@ -1,18 +1,12 @@ -use crate::serde_error::Error; +use crate::conflict_resolution_choice::ConflictResolutionChoice; +use crate::errors::Error; +use crate::identified_vec_into_iterator::IdentifiedVecIntoIterator; +use crate::identified_vec_iterator::IdentifiedVecIterator; use std::collections::HashMap; use std::fmt::{Debug, Display}; use std::hash::{Hash, Hasher}; -/// Representation of a choice in a conflict resolution -/// where two elements with the same ID exists, if `ChooseFirst`, -/// is specified the first/current/existing value will be used, but -/// if `ChooseLast` is specified then the new/last will be replace -/// the first/current/existing. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum ConflictResolutionChoice { - ChooseFirst, - ChooseLast, -} +use crate::is_identifiable_vec::IsIdentifiableVec; /// An ordered collection of identifiable elements. /// @@ -32,7 +26,7 @@ pub enum ConflictResolutionChoice { /// /// ``` /// extern crate identified_vec; -/// use identified_vec::{IdentifiedVec, Identifiable, IdentifiedVecOf}; +/// use identified_vec::{IdentifiedVec, Identifiable, IdentifiedVecOf, IsIdentifiableVec, IsIdentifiableVecOf}; /// use std::cell::RefCell; /// /// #[derive(Eq, PartialEq, Clone, Debug)] @@ -85,7 +79,7 @@ pub enum ConflictResolutionChoice { /// .collect::>() /// ); /// -/// // Element with same ID is not appended: +/// // E with same I is not appended: /// users.append(User::new("u_42", "Tom Mervolo Dolder")); /// assert_eq!( /// users.elements(), @@ -98,7 +92,7 @@ pub enum ConflictResolutionChoice { /// .collect::>() /// ); /// -/// // Element with same ID replaces existing if an `update_*` method is used: +/// // E with same I replaces existing if an `update_*` method is used: /// // e.g. `update_or_insert`: /// users.update_or_insert(User::new("u_42", "Tom Mervolo Dolder"), 0); /// assert_eq!( @@ -124,26 +118,13 @@ pub enum ConflictResolutionChoice { /// .iter() /// .collect::>() /// ); -/// -/// // or mutate with `get_mut(id)` -/// *users.get_mut(&"u_1337").unwrap().name.get_mut() = "Yoda"; -/// assert_eq!( -/// users.elements(), -/// [ -/// User::new("u_42", "Tom Mervolo Dolder"), -/// User::new("u_1337", "Yoda"), -/// User::new("u_237", "Marie Curie"), -/// ] -/// .iter() -/// .collect::>() -/// ); /// ``` /// /// Or you can provide a closure that describes an element's identity: /// /// ``` /// /// extern crate identified_vec; -/// use identified_vec::{IdentifiedVec, Identifiable, IdentifiedVecOf}; +/// use identified_vec::{IdentifiedVec, Identifiable, IdentifiedVecOf, IsIdentifiableVec, IsIdentifiableVecOf}; /// /// let numbers = IdentifiedVec::::new_identifying_element(|e| *e); /// ``` @@ -157,7 +138,7 @@ pub enum ConflictResolutionChoice { /// # Performance /// /// Like the standard `HashMap` type, the performance of hashing operations in -/// `IdentifiedVec` is highly sensitive to the quality of hashing implemented by the `ID` +/// `IdentifiedVec` is highly sensitive to the quality of hashing implemented by the `I` /// type. Failing to correctly implement hashing can easily lead to unacceptable performance, with /// the severity of the effect increasing with the size of the underlying hash table. /// @@ -166,7 +147,7 @@ pub enum ConflictResolutionChoice { /// ensure hashed collection types exhibit their target performance, it is important to ensure that /// such collisions cannot be induced merely by adding a particular list of members to the set. /// -/// When `ID` implements `Hash` correctly, testing for membership in an ordered set is expected +/// When `I` implements `Hash` correctly, testing for membership in an ordered set is expected /// to take O(1) equality checks on average. Hash collisions can still occur organically, so the /// worst-case lookup performance is technically still O(*n*) (where *n* is the size of the set); /// however, long lookup chains are unlikely to occur in practice. @@ -177,46 +158,39 @@ pub enum ConflictResolutionChoice { /// should not be mutated in place, as it will drift from its associated dictionary key. Identified /// bec is designed to avoid this invariant. Mutating an element's id will result in a runtime error. #[derive(Debug, Clone)] -pub struct IdentifiedVec +pub struct IdentifiedVec where - ID: Eq + Hash + Clone + Debug, + I: Eq + Hash + Clone + Debug, { /// The holder of the insertion order - pub(crate) order: Vec, + pub(crate) order: Vec, /// The storage of elements. - pub(crate) elements: HashMap, + pub(crate) elements: HashMap, - /// Function which extracts the ID of an Element. - pub(crate) _id_of_element: fn(&Element) -> ID, + /// Function which extracts the I of an E. + pub(crate) _id_of_element: fn(&E) -> I, } -/////////////////////// -//// Constructors /// -/////////////////////// -impl IdentifiedVec +impl IsIdentifiableVec for IdentifiedVec where - ID: Eq + Hash + Clone + Debug, + I: Eq + Hash + Clone + Debug, { - /// Constructs a new, empty `IdentifiedVec` with the specified + //////////////////// + // Constructors // + //////////////////// + + /// Constructs a new, empty `IdentifiedVec` with the specified /// `id_of_element` closure #[inline] - pub fn new_identifying_element(id_of_element: fn(&Element) -> ID) -> Self { + fn new_identifying_element(id_of_element: fn(&E) -> I) -> Self { Self { order: Vec::new(), elements: HashMap::new(), _id_of_element: id_of_element, } } -} -/////////////////////// -//// Constructors /// -/////////////////////// -impl IdentifiedVec -where - ID: Eq + Hash + Clone + Debug, -{ /// Creates a new `identified_vec` from the elements in the given sequence, using a combining closure to /// determine the element for any elements with duplicate identity. /// @@ -231,20 +205,20 @@ where /// - id_of_element: The function which extracts the identifier for an element, /// - combine: Closure trying to combine elements `(index, first, last)` with duplicate ids, returning which element to use, by use of ConflictResolutionChoice (`ChooseFirst` or `ChooseLast`), or `Err` if you prefer. /// - Returns: A new `identified_vec` initialized with the unique elements of `elements`. - /// - Complexity: Expected O(*n*) on average, where *n* is the count of elements, if `ID` + /// - Complexity: Expected O(*n*) on average, where *n* is the count of elements, if `I` /// implements high-quality hashing. #[cfg(not(tarpaulin_include))] // false negative #[inline] - pub fn try_from_iter_select_unique_ids_with( - elements: I, - id_of_element: fn(&Element) -> ID, - combine: fn((usize, &Element, &Element)) -> Result, - ) -> Result + fn try_from_iter_select_unique_ids_with( + elements: It, + id_of_element: fn(&E) -> I, + combine: fn((usize, &E, &E)) -> Result, + ) -> Result where - I: IntoIterator, + It: IntoIterator, { - let mut _order = Vec::::new(); - let mut _elements = HashMap::::new(); + let mut _order = Vec::::new(); + let mut _elements = HashMap::::new(); for element in elements.into_iter() { let id = id_of_element(&element); @@ -289,20 +263,20 @@ where /// - id_of_element: The function which extracts the identifier for an element, /// - combine: Closure used combine elements `(index, first, last)` with duplicate ids, returning which element to use, by use of ConflictResolutionChoice (`ChooseFirst` or `ChooseLast`) /// - Returns: A new `identified_vec` initialized with the unique elements of `elements`. - /// - Complexity: Expected O(*n*) on average, where *n* is the count of elements, if `ID` + /// - Complexity: Expected O(*n*) on average, where *n* is the count of elements, if `I` /// implements high-quality hashing. #[cfg(not(tarpaulin_include))] // false negative #[inline] - pub fn from_iter_select_unique_ids_with( - elements: I, - id_of_element: fn(&Element) -> ID, - combine: fn((usize, &Element, &Element)) -> ConflictResolutionChoice, + fn from_iter_select_unique_ids_with( + elements: It, + id_of_element: fn(&E) -> I, + combine: fn((usize, &E, &E)) -> ConflictResolutionChoice, ) -> Self where - I: IntoIterator, + It: IntoIterator, { - let mut _order = Vec::::new(); - let mut _elements = HashMap::::new(); + let mut _order = Vec::::new(); + let mut _elements = HashMap::::new(); for element in elements.into_iter() { let id = id_of_element(&element); @@ -325,26 +299,22 @@ where elements: _elements, } } -} -/////////////////////// -//// Public Get /// -/////////////////////// -impl IdentifiedVec -where - ID: Eq + Hash + Clone + Debug, -{ - /// A read-only collection view for the ids contained in this `identified_vec`, as an `&Vec`. + //////////////////// + // Public Get // + //////////////////// + + /// A read-only collection view for the ids contained in this `identified_vec`, as an `&Vec`. /// /// - Complexity: O(1) #[inline] - pub fn ids(&self) -> &Vec { - &self.order + fn ids(&self) -> Vec { + self.order.clone() } /// Returns the number of elements in the `identified_vec`, also referred to as its 'length'. #[inline] - pub fn len(&self) -> usize { + fn len(&self) -> usize { if cfg!(debug_assertions) { assert_eq!(self.order.len(), self.elements.len()); } @@ -358,7 +328,7 @@ where /// /// ``` /// extern crate identified_vec; - /// use identified_vec::{IdentifiedVec, Identifiable, IdentifiedVecOf}; + /// use identified_vec::{IsIdentifiableVec, IsIdentifiableVecOf, IdentifiedVec, Identifiable, IdentifiedVecOf}; /// /// #[derive(Eq, PartialEq, Clone, Debug, Hash)] /// struct User { @@ -388,213 +358,52 @@ where /// - Parameter id: The id to find in the `identified_vec`. /// - Returns: The index for the element identified by `id` if found in the `identified_vec`; otherwise, /// `None`. - /// - Complexity: Expected to be O(1) on average, if `ID` implements high-quality hashing. + /// - Complexity: Expected to be O(1) on average, if `I` implements high-quality hashing. #[inline] - pub fn index_of_id(&self, id: &ID) -> Option { + fn index_of_id(&self, id: &I) -> Option { self.order.iter().position(|i| i == id) } - /// Returns a mutable reference to the element identified by `id` if any, else None. - /// - /// - Parameter id: The id to find in the `identified_vec`. - /// - Returns: The mutable reference to the element identified by `id` if found in the `identified_vec`; otherwise, - /// `None`. - /// - Complexity: Expected to be O(1) on average, if `ID` implements high-quality hashing. - #[inline] - pub fn get_mut(&mut self, id: &ID) -> Option<&mut Element> { - self.elements.get_mut(id) - } + //////////////////// + // Public Get // + //////////////////// /// A read-only collection of references to the elements contained in this array, as a `Vec<&Elements>`. /// /// N.B. that this method is not constant time. /// - /// If `Element` implements `Clone` you can use `self.items()` which returns a `Vec`, of cloned elements. + /// If `E` implements `Clone` you can use `self.items()` which returns a `Vec`, of cloned elements. /// /// - Complexity: O(n) #[inline] - pub fn elements(&self) -> Vec<&Element> { + fn elements(&self) -> Vec<&E> { self.iter().collect() } /// Returns `true` if the `identified_vec` contains the `element.` #[inline] - pub fn contains(&self, element: &Element) -> bool { + fn contains(&self, element: &E) -> bool { self.elements.contains_key(&self.id(&element)) } /// Returns `true if the `identified_vec` contains an element for the specified `id` #[inline] - pub fn contains_id(&self, id: &ID) -> bool { + fn contains_id(&self, id: &I) -> bool { self.elements.contains_key(id) } /// Returns a reference to the element corresponding to the `id`, if found, else `None`. #[inline] - pub fn get(&self, id: &ID) -> Option<&Element> { + fn get(&self, id: &I) -> Option<&E> { self.elements.get(id) } /// Returns a reference to the element at index if found, else `None`. #[inline] - pub fn get_at_index(&self, index: usize) -> Option<&Element> { + fn get_at_index(&self, index: usize) -> Option<&E> { self.order.get(index).and_then(|id| self.get(id)) } -} - -impl IdentifiedVec -where - Element: Clone, - ID: Eq + Hash + Clone + Debug, -{ - /// A read-only collection of clones of the elements contained in this array, as a `Vec`. - /// - /// N.B. that this method is not constant time. - /// - /// Use `self.elements()` if you are looking for a collection of references. - /// - /// - Complexity: O(n) - #[inline] - pub fn items(&self) -> Vec { - self.iter().map(|e| e.clone()).collect() - } -} - -/// An iterator over the items of an `IdentifiedVec`. -pub struct IdentifiedVecIterator<'a, ID, Element> -where - ID: Eq + Hash + Clone + Debug, -{ - identified_vec: &'a IdentifiedVec, - index: usize, -} - -impl<'a, ID, Element> Iterator for IdentifiedVecIterator<'a, ID, Element> -where - ID: Eq + Hash + Clone + Debug, -{ - type Item = &'a Element; - - fn next(&mut self) -> Option { - if self.index < self.identified_vec.len() { - let id = Some(&self.identified_vec.order[self.index]).unwrap(); - self.index += 1; - return self.identified_vec.get(id); - } else { - None - } - } -} - -impl IdentifiedVec -where - ID: Eq + Hash + Clone + Debug, -{ - pub fn iter(&self) -> IdentifiedVecIterator { - IdentifiedVecIterator { - identified_vec: self, - index: 0, - } - } -} - -/// An owning iterator over the items of an `IdentifiedVec`. -pub struct IdentifiedVecIntoIterator -where - ID: Eq + Hash + Clone + Debug, -{ - identified_vec: IdentifiedVec, -} - -impl Iterator for IdentifiedVecIntoIterator -where - ID: Eq + Hash + Clone + Debug, -{ - type Item = Element; - - fn next(&mut self) -> Option { - if self.identified_vec.len() == 0 { - return None; - } - let result = self.identified_vec.remove_at(0); - Some(result) - } -} - -impl IntoIterator for IdentifiedVec -where - ID: Eq + Hash + Clone + Debug, -{ - type Item = Element; - type IntoIter = IdentifiedVecIntoIterator; - - fn into_iter(self) -> Self::IntoIter { - Self::IntoIter { - identified_vec: self, - } - } -} - -impl IdentifiedVec -where - ID: Eq + Hash + Clone + Debug, - Element: Eq + Debug, -{ - /// Try append a new unique member to the end of the `identified_vec`, if the `identified_vec` already contains the Value or ID a Error will be returned. - /// - /// - Parameter item: The element to add to the `identified_vec`. - /// - Returns: Either a Ok() with a pair `(inserted, index)`, where `inserted` is a Boolean value indicating whether - /// the operation added a new element, and `index` is the index of `item` in the resulting - /// `identified_vec`. If the given ID already exist `Error::ElementWithSameIDFound` willl be returned and if the value pre-exists within the collection the function call returns `Error::ElementWithSameValueFound`. - /// - Complexity: The operation is expected to perform O(1) copy, hash, and compare operations on - /// the `ID` type, if it implements high-quality hashing. - #[inline] - pub fn try_append_unique_element(&mut self, element: Element) -> Result<(bool, usize), Error> { - let id = self.id(&element); - - if let Some(value) = self.get(&id) { - if value == &element { - return Err(Error::ElementWithSameValueFound(format!("{:?}", value))); - } else { - return Err(Error::ElementWithSameIDFound(format!("{:?}", id))); - } - } - - Ok(self.append(element)) - } -} - -impl IdentifiedVec -where - ID: Eq + Hash + Clone + Debug, -{ - /// Try append a new member to the end of the `identified_vec`, if the `identified_vec` already contains the element a Error will be returned. - /// - /// - Parameter item: The element to add to the `identified_vec`. - /// - Returns: Either a Ok() with a pair `(inserted, index)`, where `inserted` is a Boolean value indicating whether - /// the operation added a new element, and `index` is the index of `item` in the resulting - /// `identified_vec`. If the given ID pre-exists within the collection the function call returns `Error::ElementWithSameIDFound`. - /// - Complexity: The operation is expected to perform O(1) copy, hash, and compare operations on - /// the `ID` type, if it implements high-quality hashing. - #[inline] - pub fn try_append_new(&mut self, element: Element) -> Result<(bool, usize), Error> { - let id = self.id(&element); - - if self.contains_id(&id) { - return Err(Error::ElementWithSameIDFound(format!("{:#?}", id))); - } - - Ok(self.append(element)) - } -} -/////////////////////// -//// Public Insert /// -/////////////////////// -impl IdentifiedVec -where - ID: Eq + Hash + Clone + Debug, -{ /// Append a new member to the end of the `identified_vec`, if the `identified_vec` doesn't already contain it. /// /// - Parameter item: The element to add to the `identified_vec`. @@ -602,9 +411,9 @@ where /// the operation added a new element, and `index` is the index of `item` in the resulting /// `identified_vec`. /// - Complexity: The operation is expected to perform O(1) copy, hash, and compare operations on - /// the `ID` type, if it implements high-quality hashing. + /// the `I` type, if it implements high-quality hashing. #[inline] - pub fn append(&mut self, element: Element) -> (bool, usize) { + fn append(&mut self, element: E) -> (bool, usize) { self.insert(element, self.end_index()) } @@ -613,11 +422,11 @@ where /// /// - Parameter elements: A finite sequence of elements to append. /// - Complexity: The operation is expected to perform amortized O(1) copy, hash, and compare - /// operations on the `Element` type, if it implements high-quality hashing. + /// operations on the `E` type, if it implements high-quality hashing. #[inline] - pub fn append_other(&mut self, other: I) + fn append_other(&mut self, other: It) where - I: IntoIterator, + It: IntoIterator, { other.into_iter().for_each(|i| _ = self.append(i)) } @@ -629,9 +438,9 @@ where /// - Returns: The original element that was replaced by this operation, or `None` if the value was /// appended to the end of the collection. /// - Complexity: The operation is expected to perform amortized O(1) copy, hash, and compare - /// operations on the `ID` type, if it implements high-quality hashing. + /// operations on the `I` type, if it implements high-quality hashing. #[inline] - pub fn update_or_append(&mut self, element: Element) -> Option { + fn update_or_append(&mut self, element: E) -> Option { let id = self.id(&element); self._update_value(element, id) } @@ -644,7 +453,7 @@ where /// - Returns: The original element that was replaced. /// - Complexity: Amortized O(1). #[inline] - pub fn update_at(&mut self, element: Element, index: usize) -> Element { + fn update_at(&mut self, element: E, index: usize) -> E { let old_id = self .order .get(index) @@ -660,6 +469,23 @@ where .expect("Replaced old value"); } + /// Returns `false` if no element of `id` was found, otherwise if found, this + /// existing element gets updated by `mutate` closure and this function returns + /// `true`. + #[inline] + fn update_with(&mut self, id: &I, mutate: F) -> bool + where + F: Fn(&mut E), + { + if !self.contains_id(id) { + return false; + } + let mut existing = self.elements.remove(id).expect("Element for existing id"); + mutate(&mut existing); + self.elements.insert(id.clone(), existing); + true + } + /// Try to update the given element to the `identified_vec` if a element with the same ID is already present. /// /// - Parameter item: The value to append or replace. @@ -667,7 +493,7 @@ where /// - Complexity: The operation is expected to perform amortized O(1) copy, hash, and compare /// operations on the `ID` type, if it implements high-quality hashing. #[inline] - pub fn try_update(&mut self, element: Element) -> Result { + fn try_update(&mut self, element: E) -> Result { let id = self.id(&element); if self.get(&id).is_none() { return Err(Error::ExpectedElementNotPresent(format!("{:#?}", id))); @@ -688,11 +514,11 @@ where /// requested. /// /// - Complexity: The operation is expected to perform amortized O(`self.count`) copy, hash, and - /// compare operations on the `ID` type, if it implements high-quality hashing. (Insertions need + /// compare operations on the `I` type, if it implements high-quality hashing. (Insertions need /// to make room in the storage identified_vec to add the inserted element.) #[cfg(not(tarpaulin_include))] // false negative #[inline] - pub fn insert(&mut self, element: Element, at: usize) -> (bool, usize) { + fn insert(&mut self, element: E, at: usize) -> (bool, usize) { let id = self.id(&element); if let Some(existing) = self.index_of_id(&id) { return (false, existing.clone()); @@ -710,26 +536,21 @@ where /// - Returns: The original element that was replaced by this operation, or `None` if the value was /// newly inserted into the collection. /// - Complexity: The operation is expected to perform amortized O(1) copy, hash, and compare - /// operations on the `ID` type, if it implements high-quality hashing. + /// operations on the `I` type, if it implements high-quality hashing. #[inline] - pub fn update_or_insert(&mut self, element: Element, index: usize) -> (Option, usize) { + fn update_or_insert(&mut self, element: E, index: usize) -> (Option, usize) { let id = self.id(&element); self._update_value_inserting_at(element, id, index) } -} -/////////////////////// -//// Public Remove /// -/////////////////////// -impl IdentifiedVec -where - ID: Eq + Hash + Clone + Debug, -{ + //////////////////// + // Public Remove // + //////////////////// /// Removes the element identified by the given id from the `identified_vec`. /// /// ``` /// extern crate identified_vec; - /// use identified_vec::{IdentifiedVec, Identifiable, IdentifiedVecOf}; + /// use identified_vec::{IsIdentifiableVec, IsIdentifiableVecOf, IdentifiedVec, Identifiable, IdentifiedVecOf}; /// /// #[derive(Eq, PartialEq, Clone, Debug, Hash)] /// struct User { @@ -763,7 +584,7 @@ where /// - Complexity: O(`count`) #[cfg(not(tarpaulin_include))] // false negative #[inline] - pub fn remove_by_id(&mut self, id: &ID) -> Option { + fn remove_by_id(&mut self, id: &I) -> Option { match self.index_of_id(id) { Some(index) => { self.order.remove(index); @@ -786,7 +607,7 @@ where /// - Returns: The value that was removed, or `None` if the element was not present in the `identified_vec`. /// - Complexity: O(`count`) #[inline] - pub fn remove(&mut self, element: &Element) -> Option { + fn remove(&mut self, element: &E) -> Option { self.remove_by_id(&self.id(element)) } @@ -800,12 +621,12 @@ where /// collection's end index. /// - Complexity: O(`count`) #[inline] - pub fn remove_at(&mut self, index: usize) -> Element { + fn remove_at(&mut self, index: usize) -> E { let id = self .order .get(index) .expect("Precondition failure, index out of bounds"); - let removed = self.elements.remove(id).expect("Element of existing id"); + let removed = self.elements.remove(id).expect("E of existing id"); self.order.remove(index); return removed; } @@ -815,9 +636,9 @@ where /// - Parameter offsets: The offsets of all elements to be removed. /// - Complexity: O(*n*) where *n* is the length of the `identified_vec`. #[inline] - pub fn remove_at_offsets(&mut self, offsets: I) + fn remove_at_offsets(&mut self, offsets: It) where - I: IntoIterator, + It: IntoIterator, { let mut internal_offset = 0; offsets.into_iter().for_each(|i| { @@ -825,35 +646,98 @@ where internal_offset += 1; }) } + + /// Try append a new member to the end of the `identified_vec`, if the `identified_vec` already contains the element a Error will be returned. + /// + /// - Parameter item: The element to add to the `identified_vec`. + /// - Returns: Either a Ok() with a pair `(inserted, index)`, where `inserted` is a Boolean value indicating whether + /// the operation added a new element, and `index` is the index of `item` in the resulting + /// `identified_vec`. If the given ID pre-exists within the collection the function call returns `Error::ElementWithSameIDFound`. + /// - Complexity: The operation is expected to perform O(1) copy, hash, and compare operations on + /// the `ID` type, if it implements high-quality hashing. + #[inline] + fn try_append_new(&mut self, element: E) -> Result<(bool, usize), Error> { + let id = self.id(&element); + + if self.contains_id(&id) { + return Err(Error::ElementWithSameIDFound(format!("{:#?}", id))); + } + + Ok(self.append(element)) + } + + #[inline] + fn iter(&self) -> IdentifiedVecIterator { + IdentifiedVecIterator::new(self) + } +} + +pub trait ItemsCloned +where + Element: Clone, +{ + fn items(&self) -> Vec; +} + +impl ItemsCloned for IdentifiedVec +where + E: Clone, + I: Eq + Hash + Clone + Debug, +{ + /// A read-only collection of clones of the elements contained in this array, as a `Vec`. + /// + /// N.B. that this method is not constant time. + /// + /// Use `self.elements()` if you are looking for a collection of references. + /// + /// - Complexity: O(n) + #[inline] + fn items(&self) -> Vec { + self.iter().map(|e| e.clone()).collect() + } +} + +impl IntoIterator for IdentifiedVec +where + I: Eq + Hash + Clone + Debug, +{ + type Item = E; + type IntoIter = IdentifiedVecIntoIterator; + + fn into_iter(self) -> Self::IntoIter { + Self::IntoIter::new(self) + } } +impl IdentifiedVec where I: Eq + Hash + Clone + Debug {} + /////////////////////// //// Eq /// /////////////////////// -impl PartialEq for IdentifiedVec +impl PartialEq for IdentifiedVec where - Element: PartialEq, - ID: Eq + Hash + Clone + Debug, + E: PartialEq, + I: Eq + Hash + Clone + Debug, { fn eq(&self, other: &Self) -> bool { self.elements() == other.elements() } } -impl Eq for IdentifiedVec +impl Eq for IdentifiedVec where - Element: Eq, - ID: Eq + Hash + Clone + Debug, + E: Eq, + I: Eq + Hash + Clone + Debug, { } /////////////////////// //// Hash /// /////////////////////// -impl Hash for IdentifiedVec +impl Hash for IdentifiedVec where - Element: Hash, - ID: Eq + Hash + Clone + Debug, + E: Hash, + I: Eq + Hash + Clone + Debug, { fn hash(&self, state: &mut H) { self.elements().hash(state); @@ -863,10 +747,10 @@ where /////////////////////// //// Display /// /////////////////////// -impl Display for IdentifiedVec +impl Display for IdentifiedVec where - Element: Debug, - ID: Eq + Hash + Clone + Debug, + E: Debug, + I: Eq + Hash + Clone + Debug, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.elements().fmt(f) @@ -876,9 +760,9 @@ where /////////////////////// //// PRIVATE /// /////////////////////// -impl IdentifiedVec +impl IdentifiedVec where - ID: Eq + Hash + Clone + Debug, + I: Eq + Hash + Clone + Debug, { /// Next index for element appended #[inline] @@ -886,16 +770,16 @@ where self.len() } - /// Returns the ID of an Element + /// Returns the I of an E #[inline] - fn id(&self, of: &Element) -> ID { + fn id(&self, of: &E) -> I { (self._id_of_element)(of) } - /// Inserting ID at an index, returning if it did, if not, the index of the existing. + /// Inserting I at an index, returning if it did, if not, the index of the existing. #[cfg(not(tarpaulin_include))] // false negative #[inline] - fn _insert_id_at(&mut self, id: ID, index: usize) -> (bool, usize) { + fn _insert_id_at(&mut self, id: I, index: usize) -> (bool, usize) { match self.index_of_id(&id) { Some(existing) => (false, existing), None => { @@ -906,7 +790,7 @@ where } #[inline] - fn _update_value(&mut self, element: Element, for_key: ID) -> Option { + fn _update_value(&mut self, element: E, for_key: I) -> Option { let value = element; let key = for_key; @@ -924,10 +808,10 @@ where #[inline] fn _update_value_inserting_at( &mut self, - element: Element, - for_key: ID, + element: E, + for_key: I, index: usize, - ) -> (Option, usize) { + ) -> (Option, usize) { let id = for_key; let value = element; @@ -943,22 +827,31 @@ where } } -/////////////////////// -//// DEBUG /// -/////////////////////// impl IdentifiedVec where - Element: Debug, ID: Eq + Hash + Clone + Debug, + Element: Eq + Debug, { - #[cfg(not(tarpaulin_include))] - #[cfg(debug_assertions)] - pub fn debug(&self) { - println!("{}", self.debug_str()); - } + /// Try append a new unique member to the end of the `identified_vec`, if the `identified_vec` already contains the Value or ID a Error will be returned. + /// + /// - Parameter item: The element to add to the `identified_vec`. + /// - Returns: Either a Ok() with a pair `(inserted, index)`, where `inserted` is a Boolean value indicating whether + /// the operation added a new element, and `index` is the index of `item` in the resulting + /// `identified_vec`. If the given ID already exist `Error::ElementWithSameIDFound` will be returned and if the value pre-exists within the collection the function call returns `Error::ElementWithSameValueFound`. + /// - Complexity: The operation is expected to perform O(1) copy, hash, and compare operations on + /// the `ID` type, if it implements high-quality hashing. + #[inline] + pub fn try_append_unique_element(&mut self, element: Element) -> Result<(bool, usize), Error> { + let id = self.id(&element); - #[cfg(debug_assertions)] - pub fn debug_str(&self) -> String { - format!("order: {:?}\nelements: {:?}", self.order, self.elements) + if let Some(value) = self.get(&id) { + if value == &element { + return Err(Error::ElementWithSameValueFound(format!("{:?}", value))); + } else { + return Err(Error::ElementWithSameIDFound(format!("{:?}", id))); + } + } + + Ok(self.append(element)) } } diff --git a/src/vec_of.rs b/identified_vec/src/vec_of.rs similarity index 91% rename from src/vec_of.rs rename to identified_vec/src/vec_of.rs index f6801c1..1270110 100644 --- a/src/vec_of.rs +++ b/identified_vec/src/vec_of.rs @@ -4,11 +4,12 @@ use std::collections::HashMap; use std::fmt::Debug; #[cfg(feature = "serde")] -use crate::serde_error::IdentifiedVecOfSerdeFailure; +use crate::errors::IdentifiedVecOfSerdeFailure; +use crate::IsIdentifiableVecOf; use crate::{ - identified_vec_of::Identifiable, - vec::{ConflictResolutionChoice, IdentifiedVec}, + conflict_resolution_choice::ConflictResolutionChoice, is_identifiable_vec::IsIdentifiableVec, }; +use crate::{identified_vec_of::Identifiable, vec::IdentifiedVec}; #[cfg(feature = "serde")] use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; @@ -27,16 +28,13 @@ use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; /// `Element` implements serde serialization/deserialization of course. pub type IdentifiedVecOf = IdentifiedVec<::ID, Element>; -////////////////////////////////////////////// -/// Identifiable Element Constructors /// -////////////////////////////////////////////// -impl IdentifiedVec +impl IsIdentifiableVecOf for IdentifiedVecOf where Element: Identifiable, { /// Constructs a new, empty `IdentifiedVec`, using `id()` on `Element` /// as id function. - pub fn new() -> Self { + fn new() -> Self { Self { order: Vec::new(), elements: HashMap::new(), @@ -56,9 +54,9 @@ where /// - Complexity: Expected O(*n*) on average, where *n* is the count of elements, if `ID` /// implements high-quality hashing. #[inline] - pub fn from_iter(unique_elements: I) -> Self + fn from_iter(unique_elements: It) -> Self where - I: IntoIterator, + It: IntoIterator, { let mut _self = Self::new(); unique_elements @@ -83,7 +81,7 @@ where /// - Complexity: Expected O(*n*) on average, where *n* is the count of elements, if `ID` /// implements high-quality hashing. #[inline] - pub fn try_from_iter_select_unique_with( + fn try_from_iter_select_unique_with( elements: I, combine: fn((usize, &Element, &Element)) -> Result, ) -> Result @@ -109,7 +107,7 @@ where /// - Complexity: Expected O(*n*) on average, where *n* is the count of elements, if `ID` /// implements high-quality hashing. #[inline] - pub fn from_iter_select_unique_with( + fn from_iter_select_unique_with( elements: I, combine: fn((usize, &Element, &Element)) -> ConflictResolutionChoice, ) -> Self diff --git a/identified_vec_macros/Cargo.toml b/identified_vec_macros/Cargo.toml new file mode 100644 index 0000000..62c03bb --- /dev/null +++ b/identified_vec_macros/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "identified_vec_macros" +version = "0.1.5" +edition = "2021" +authors = ["Alexander Cyon "] + +[dependencies] +identified_vec = { path = "../identified_vec" } +proc-macro2 = "1.0.70" +quote = "1.0" +syn = { version = "2.0", features = ["extra-traits", "full", "parsing"] } diff --git a/identified_vec_macros/src/lib.rs b/identified_vec_macros/src/lib.rs new file mode 100644 index 0000000..c7ee743 --- /dev/null +++ b/identified_vec_macros/src/lib.rs @@ -0,0 +1,90 @@ +//! The `newtype_identified_vec` macro allows you to create +//! a newtype wrapping an `IdentifiedVecOf` of the item type +//! you pass in, but it gets super powers! It implements the +//! traits `IsIdentifiableVecOfVia`, which implements the trait +//! `IsIdentifiableVecOf`, meaning that the declared newtype, +//! gets all the methods and functions of a `IdentifiedVecOf`, +//! and if you use the `"serde"` feature, it is also +//! (de)serializable. +//! +//! You use it like so: +//! ``` +//! extern crate identified_vec; +//! extern crate identified_vec_macros; +//! use identified_vec_macros::newtype_identified_vec; +//! use identified_vec::{IsIdentifiableVecOfVia, ViaMarker, IsIdentifiableVec, IsIdentifiableVecOf, IdentifiedVec, IdentifiedVecOf, Identifiable}; +//! +//! newtype_identified_vec!(of: u32, named: Ints);; +//! +//! let mut ints = Ints::new(); +//! ints.append(5); +//! ``` +//! +#[macro_export] +macro_rules! newtype_identified_vec { + (of: $item_ty: ty, named: $struct_name: ident) => { + #[derive(Debug, Clone, Eq, PartialEq)] + pub struct $struct_name(IdentifiedVecOf<$item_ty>); + + impl ViaMarker for $struct_name {} + impl IsIdentifiableVecOfVia<$item_ty> for $struct_name { + fn via_mut(&mut self) -> &mut IdentifiedVecOf<$item_ty> { + &mut self.0 + } + + fn via(&self) -> &IdentifiedVecOf<$item_ty> { + &self.0 + } + + fn from_identified_vec_of(identified_vec_of: IdentifiedVecOf<$item_ty>) -> Self { + Self(identified_vec_of) + } + } + + impl std::fmt::Display for $struct_name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } + } + + impl IntoIterator for $struct_name { + type Item = $item_ty; + type IntoIter = + identified_vec::IdentifiedVecIntoIterator<<$item_ty as Identifiable>::ID, $item_ty>; + + fn into_iter(self) -> Self::IntoIter { + Self::IntoIter::new(self.0) + } + } + + #[cfg(any(test, feature = "serde"))] + impl Serialize for $struct_name + where + $item_ty: Serialize + Identifiable + Debug + Clone, + { + fn serialize( + &self, + serializer: S, + ) -> Result<::Ok, ::Error> + where + S: Serializer, + { + IdentifiedVecOf::serialize(&self.0, serializer) + } + } + + #[cfg(any(test, feature = "serde"))] + impl<'de> Deserialize<'de> for $struct_name + where + $item_ty: Deserialize<'de> + Identifiable + Debug + Clone, + { + #[cfg(not(tarpaulin_include))] // false negative + fn deserialize>( + deserializer: D, + ) -> Result<$struct_name, D::Error> { + let id_vec_of = IdentifiedVecOf::<$item_ty>::deserialize(deserializer)?; + return Ok(Self::from_identified_vec_of(id_vec_of)); + } + } + }; +} diff --git a/tests/Cargo.toml b/tests/Cargo.toml new file mode 100644 index 0000000..bee30e4 --- /dev/null +++ b/tests/Cargo.toml @@ -0,0 +1,22 @@ +# https://stackoverflow.com/a/71461114/1311272 +[package] +name = "tests" +version = "0.1.0" +edition = "2021" +publish = false + +[features] +default = ["serde"] +serde = ["dep:serde"] + +[dependencies] +identified_vec = { path = "../identified_vec", features = ["id_prim", "serde"] } +identified_vec_macros = { path = "../identified_vec_macros" } +serde = { version = "1.0.193", features = ["derive"], optional = true } +serde_json = "1.0.108" +rand = "0.8.5" +maplit = "1.0.2" + +[[test]] +name = "integration_tests" +path = "tests.rs" diff --git a/tests/tests.rs b/tests/tests.rs index 8354457..e273681 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,18 +1,22 @@ -#![cfg(feature = "id_prim")] - use std::{cell::RefCell, collections::HashSet, fmt::Debug, ops::Deref}; use identified_vec::{ ConflictResolutionChoice, Error, Identifiable, IdentifiedVec, IdentifiedVecOf, - IdentifiedVecOfSerdeFailure, + IdentifiedVecOfSerdeFailure, IsIdentifiableVec, IsIdentifiableVecOf, IsIdentifiableVecOfVia, + ItemsCloned, ViaMarker, }; +#[cfg(any(test, feature = "serde"))] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use identified_vec_macros::newtype_identified_vec; + #[derive(Eq, PartialEq, Clone)] +#[cfg_attr(any(test, feature = "serde"), derive(Serialize, Deserialize))] pub struct User { pub id: u16, pub name: RefCell, } - impl User { fn new(id: u16, name: &str) -> Self { if name.is_empty() { @@ -51,8 +55,8 @@ impl Identifiable for User { } } -type SUT = IdentifiedVecOf; -type Users = IdentifiedVecOf; +newtype_identified_vec!(of: u32, named: SUT); +newtype_identified_vec!(of: User, named: Users); #[test] fn new_is_empty() { @@ -65,14 +69,6 @@ fn ids() { assert_eq!(identified_vec.ids(), &[1, 2, 3]) } -#[test] -fn debug_str() { - let identified_vec = SUT::from_iter([1, 2, 3]); - assert!(identified_vec - .debug_str() - .starts_with("order: [1, 2, 3]\nelements: {"),) -} - #[test] fn elements() { let vec = vec![User::blob(), User::blob_jr(), User::blob_sr()]; @@ -92,6 +88,16 @@ fn into_iter() { } } +#[test] +fn into_iter_identified_vec() { + type Users = IdentifiedVecOf; + let vec = vec![User::blob(), User::blob_jr(), User::blob_sr()]; + let identified_vec = Users::from_iter(vec.clone()); + for (idx, element) in identified_vec.into_iter().enumerate() { + assert_eq!(vec[idx], element) + } +} + #[test] fn iter() { let vec = vec![User::blob(), User::blob_jr(), User::blob_sr()]; @@ -111,12 +117,8 @@ fn get() { // 1 let mut id: &u16 = &1; - identified_vec - .get_mut(id) - .unwrap() - .name - .borrow_mut() - .push_str(", Esq."); + identified_vec.update_with(id, |u| u.name.borrow_mut().push_str(", Esq.")); + assert_eq!( identified_vec.get(id), Some(&User::new(id.clone(), "Blob, Esq.")) @@ -124,22 +126,12 @@ fn get() { // 2 id = &2; - identified_vec - .get_mut(id) - .unwrap() - .name - .borrow_mut() - .drain(4..9); + identified_vec.update_with(id, |u| _ = u.name.borrow_mut().drain(4..9)); assert_eq!(identified_vec.get(id), Some(&User::new(id.clone(), "Blob"))); // 3 id = &3; - identified_vec - .get_mut(id) - .unwrap() - .name - .borrow_mut() - .drain(4..9); + identified_vec.update_with(id, |u| _ = u.name.borrow_mut().drain(4..9)); assert_eq!(identified_vec.get(id), Some(&User::new(id.clone(), "Blob"))); identified_vec.remove_by_id(id); @@ -316,6 +308,7 @@ fn append() { #[test] fn try_append_unique_element() { + type SUT = IdentifiedVecOf; let mut identified_vec = SUT::from_iter([1, 2, 3]); let result = identified_vec.try_append_unique_element(4); assert!(result.is_ok()); @@ -347,7 +340,7 @@ fn try_append_new_unique_element() { assert_eq!(result.unwrap().1, 3); assert_eq!(identified_vec.items(), [1, 2, 3, 4]); - let mut identified_vec: Users = IdentifiedVecOf::new(); + let mut identified_vec = Users::new(); identified_vec.append(User::blob()); identified_vec.append(User::blob_jr()); identified_vec.append(User::blob_sr()); @@ -367,7 +360,7 @@ fn try_append_new_unique_element() { #[test] fn try_append_element_with_existing_id() { - let mut identified_vec: Users = IdentifiedVecOf::new(); + let mut identified_vec = Users::new(); identified_vec.append(User::blob()); identified_vec.append(User::blob_jr()); identified_vec.append(User::blob_sr()); @@ -406,6 +399,15 @@ fn update_at() { assert_eq!(identified_vec.update_at(2, 1), 2) } +#[test] +fn update_with() { + let mut sut = Users::new(); + sut.append(User::new(2, "Blob, Jr.")); + sut.update_with(&2, |u| u.name.borrow_mut().make_ascii_uppercase()); + assert_eq!(sut.items(), [User::new(2, "BLOB, JR.")]); + assert_eq!(sut.update_with(&999, |_| panic!("not called")), false); +} + #[test] #[should_panic(expected = "Expected element at index {index}")] fn update_at_expect_panic_unknown_index() { @@ -447,7 +449,7 @@ fn try_update() { ); assert_eq!(identified_vec.items(), [1, 2, 3]); - let mut identified_vec: Users = IdentifiedVecOf::new(); + let mut identified_vec = Users::new(); identified_vec.append(User::blob()); identified_vec.append(User::blob_jr()); identified_vec.append(User::blob_sr()); @@ -488,7 +490,7 @@ fn remove_at_out_of_bounds() { } #[test] -fn serde() { +fn serde_identified_vec_of() { let identified_vec = SUT::from_iter([1, 2, 3]); assert_eq!( serde_json::to_value(identified_vec.clone()) @@ -511,8 +513,37 @@ fn serde() { assert!(serde_json::from_str::("invalid").is_err(),); } +#[cfg(any(test, feature = "serde"))] +#[test] +fn serde_is_identified_vec() { + newtype_identified_vec!(of: u32, named: Ints); + + let identified_vec = Ints::from_iter([1, 2, 3]); + let cloned = identified_vec.clone(); + assert_eq!(&cloned, &identified_vec); + assert_eq!( + serde_json::to_value(identified_vec.clone()) + .and_then(|j| serde_json::from_value::(j)) + .unwrap(), + identified_vec + ); + assert_eq!( + serde_json::from_str::("[1,2,3]").unwrap(), + identified_vec + ); + assert_eq!(serde_json::to_string(&identified_vec).unwrap(), "[1,2,3]"); + assert_eq!( + serde_json::from_str::("[1,1,1]") + .expect_err("should fail") + .to_string(), + "Duplicate element at offset 1" + ); + + assert!(serde_json::from_str::("invalid").is_err(),); +} + #[test] -fn serde_via_vec() { +fn serde_using_vec() { let vec = vec![1, 2, 3]; let json_from_vec = serde_json::to_value(vec).unwrap(); let mut identified_vec = serde_json::from_value::(json_from_vec).unwrap(); @@ -524,8 +555,8 @@ fn serde_via_vec() { #[test] fn eq() { - #[derive(Eq, PartialEq, Clone, Hash, Debug)] - struct Foo { + #[derive(Eq, PartialEq, Clone, Hash, Debug, Serialize, Deserialize)] + pub struct Foo { id: &'static str, value: String, } @@ -546,16 +577,17 @@ fn eq() { } // Create `IdentifiedVec` using all of the initializers - let mut vecs: Vec> = vec![ - IdentifiedVecOf::new(), - IdentifiedVecOf::new_identifying_element(|e| e.id()), - IdentifiedVecOf::from_iter_select_unique_with([], |_| ConflictResolutionChoice::ChooseLast), - IdentifiedVecOf::from_iter_select_unique_ids_with( + newtype_identified_vec!(of: Foo, named: SUT); + let mut vecs: Vec = vec![ + SUT::new(), + SUT::new_identifying_element(|e| e.id()), + SUT::from_iter_select_unique_with([], |_| ConflictResolutionChoice::ChooseLast), + SUT::from_iter_select_unique_ids_with( [], |e| e.id(), |_| ConflictResolutionChoice::ChooseLast, ), - IdentifiedVecOf::try_from_iter_select_unique_ids_with( + SUT::try_from_iter_select_unique_ids_with( [], |e: &Foo| e.id(), |_| Result::<_, ()>::Ok(ConflictResolutionChoice::ChooseLast), @@ -564,7 +596,7 @@ fn eq() { ]; assert_eq!( - IdentifiedVecOf::try_from_iter_select_unique_ids_with( + SUT::try_from_iter_select_unique_ids_with( [Foo::new(), Foo::new()], |e: &Foo| e.id(), |_| Err(IdentifiedVecOfSerdeFailure::DuplicateElementsAtIndex(1)), @@ -573,7 +605,7 @@ fn eq() { ); assert_eq!( - IdentifiedVecOf::try_from_iter_select_unique_with([Foo::new(), Foo::new()], |_| Err( + SUT::try_from_iter_select_unique_with([Foo::new(), Foo::new()], |_| Err( IdentifiedVecOfSerdeFailure::DuplicateElementsAtIndex(1) ),), Err(IdentifiedVecOfSerdeFailure::DuplicateElementsAtIndex(1)) @@ -617,9 +649,26 @@ fn display() { #[test] fn hash() { + type SUT = IdentifiedVecOf; let identified_vec = SUT::from_iter([1, 2, 3]); assert_eq!( HashSet::>::from_iter([identified_vec.clone()]), HashSet::from_iter([identified_vec.clone(), identified_vec]) ) } + +#[test] +fn test_macro() { + newtype_identified_vec!(of: User, named: CollectionOfUsers); + + let mut sut = CollectionOfUsers::new(); + sut.append(User::blob_jr()); + assert_eq!(sut.items(), [User::blob_jr()]); + sut.remove_at(0); + assert_eq!(sut.len(), 0); + sut.update_or_append(User::blob_sr()); + sut.update_or_append(User::blob_sr()); + assert_eq!(sut.items(), [User::blob_sr()]); + sut.update_or_append(User::blob_jr()); + assert_eq!(sut.items(), [User::blob_sr(), User::blob_jr()]); +}