Skip to content

Commit

Permalink
Tie lookup keys to comparators and tidy up
Browse files Browse the repository at this point in the history
  • Loading branch information
eggyal committed Jan 1, 2023
1 parent 1ce160b commit 6d9d519
Show file tree
Hide file tree
Showing 16 changed files with 562 additions and 689 deletions.
4 changes: 1 addition & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "copse"
version = "0.1.1"
version = "0.2.0"
license = "MIT OR Apache-2.0"
repository = "https://github.com/eggyal/copse.git"
description = "Direct ports of the standard library's B-Tree collections, but that sort according to a specified comparator rather than the `Ord` trait"
Expand All @@ -25,7 +25,6 @@ unstable = [
"rustc_attrs",
"slice_ptr_get",
"specialization",
"type_alias_impl_trait",
]
allocator_api = []
bound_map = []
Expand All @@ -42,7 +41,6 @@ new_uninit = []
rustc_attrs = []
slice_ptr_get = []
specialization = []
type_alias_impl_trait = []
std = []

[dependencies]
Expand Down
86 changes: 48 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,45 +13,57 @@ possible because the [`Borrow`] trait stipulates that borrowed values must prese
order.

However, copse's collections do not use the [`Ord`] trait; instead, lookups can only ever
be performed using the [`Comparator<T>`] supplied upon collection creation. This comparator
can only compare values of type `&T` for which it was defined, and hence such type must be
reachable from any key type `&Q` used to perform lookups in the collection. copse ensures
this via its [`Sortable`] trait, which will typically be implemented by the stored key type
`K`; its [`State`][Sortable::State] associated type then specifies the `T` in which
comparisons will be performed, and values of type `&Q` can be used as lookup keys provided
that `Q: Borrow<T>`.
be performed using the [`Comparator`] supplied upon collection creation. This comparator
can only compare values of its [`Key`][Comparator::Key] associated type, and hence keys used
for lookups must implement [`LookupKey<C>`] in order that the conversion can be performed.

For example, a collection using a `Comparator<str>` comparator can store keys of type
`String` because `String` implements `Sortable<State = str>`; moreover, lookups can be
performed using keys of type `&str` because `str` implements `Borrow<str>` (due to the
reflexive blanket implementation).
# Example
```rust
// define a comparator
struct NthByteComparator {
n: usize, // runtime state
}

Implementations of [`Sortable`] are provided for primitive and some common standard library
types, but storing keys of other foreign types may require newtyping.
impl Comparator for NthByteComparator {
type Key = str;
fn cmp(&self, this: &str, that: &str) -> Ordering {
match (this.as_bytes().get(self.n), that.as_bytes().get(self.n)) {
(Some(lhs), Some(rhs)) => lhs.cmp(rhs),
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
(None, None) => Ordering::Equal,
}
}
}

# Function item types
In addition to the type parameters familiar from the standard library collections, copse's
collections are additionally parameterised by the type of the [`Comparator`]. If the
comparator type is not explicitly named, it defaults to the type of the [`Ord::cmp`]
function for `K::State`. As noted in the documentation of the [`CmpFn`] type alias, this
is only a zero-sized function item type if the unstable `type_alias_impl_trait` feature is
enabled; otherwise it is a function pointer type, with ensuing size and indirect call
implications. Collections built using the zero-sized function item type can still be
used in stable code, however; just not using the default type parameter. For example:
// define lookup key types for collections sorted by our comparator
impl LookupKey<NthByteComparator> for String {
fn key(&self) -> &str { self.as_str() }
}
impl LookupKey<NthByteComparator> for str {
fn key(&self) -> &str { self }
}

```rust
let mut ord_map = BTreeMap::new(Ord::cmp);
// create a collection using our comparator
let mut set = BTreeSet::new(NthByteComparator { n: 10 });
assert!(set.insert("abcdefghij".to_string()));
assert!(!set.insert("xxxxxxxxxj".to_string()));
assert!(set.contains("jjjjjjjjjj"));
```

However, naming this type carries the usual problems associated with anonymous types like
closures; in certain situations you may be able to use `impl Comparator` for the type
parameter, but in other situations (in stable code) the function pointer may be
unavoidable.
# Collection type parameters
In addition to the type parameters familiar from the standard library collections, copse's
collections are additionally parameterised by the type of the [`Comparator`]. If the
comparator type is not explicitly named, it defaults to the [`OrdComparator`] for the
storage key's [`DefaultComparisonKey`][OrdStoredKey::DefaultComparisonKey], which yields
behaviour analagous to the standard library collections (i.e. sorted by the `Ord` trait).
If you find yourself using these items, then you should probably ditch copse for the
standard library instead.

# Crate feature flags
This crate defines a number of feature flags, none of which are enabled by default:

* the `std` feature provides [`Sortable`] implementations for some libstd types
* the `std` feature provides [`OrdStoredKey`] implementations for some libstd types
that are not available in libcore + liballoc, namely [`OsString`] and [`PathBuf`];

* the `unstable` feature enables all other crate features, each of which enables the
Expand All @@ -61,11 +73,8 @@ This crate defines a number of feature flags, none of which are enabled by defau
they are nevertheless included to ease tracking of the stdlib implementations.

The most visible differences to library users will be:
* `allocator_api` enables the `new_in` methods for use of custom allocators;
* `specialization` adds the collection type name to some panic messages;
* `type_alias_impl_trait`, as mentioned above, ensures that the *default*
[`Comparator`] type parameter for the collections is the zero-sized function
item type of the `K::State::cmp` function.
* `allocator_api` enables the `new_in` methods for use of custom allocators; and
* `specialization` adds the collection type name to some panic messages.

[std::collections::BTreeMap]: https://doc.rust-lang.org/std/collections/struct.BTreeMap.html
[std::collections::BTreeSet]: https://doc.rust-lang.org/std/collections/struct.BTreeSet.html
Expand All @@ -76,8 +85,9 @@ This crate defines a number of feature flags, none of which are enabled by defau
[`OsString`]: https://doc.rust-lang.org/std/ffi/os_str/struct.OsString.html
[`PathBuf`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html

[`CmpFn`]: https://docs.rs/copse/latest/copse/type.CmpFn.html
[`Comparator`]: https://docs.rs/copse/latest/copse/trait.Comparator.html
[`Comparator<T>`]: https://docs.rs/copse/latest/copse/trait.Comparator.html
[`Sortable`]: https://docs.rs/copse/latest/copse/trait.Sortable.html
[Sortable::State]: https://docs.rs/copse/latest/copse/trait.Sortable.html#associatedtype.State
[Comparator::Key]: https://docs.rs/copse/latest/copse/trait.Comparator.html#associatedtype.Key
[`LookupKey<C>`]: https://docs.rs/copse/latest/copse/trait.LookupKey.html
[`OrdComparator`]: https://docs.rs/copse/latest/copse/struct.OrdComparator.html
[`OrdStoredKey`]: https://docs.rs/copse/latest/copse/trait.OrdStoredKey.html
[OrdStoredKey::DefaultComparisonKey]: https://docs.rs/copse/latest/copse/trait.OrdStoredKey.html#associatedtype.DefaultComparisonKey
9 changes: 5 additions & 4 deletions src/btree/append.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::merge_iter::MergeIterInner;
use super::node::{self, Root};
use crate::{polyfill::*, Comparator};
use crate::polyfill::*;
use core::cmp::Ordering;
use core::iter::FusedIterator;

impl<K, V> Root<K, V> {
Expand All @@ -20,7 +21,7 @@ impl<K, V> Root<K, V> {
left: I,
right: I,
length: &mut usize,
comparator: impl Comparator<(K, V)>,
comparator: impl Fn(&(K, V), &(K, V)) -> Ordering,
alloc: A,
) where
I: Iterator<Item = (K, V)> + FusedIterator,
Expand Down Expand Up @@ -95,14 +96,14 @@ struct MergeIter<K, V, C, I: Iterator<Item = (K, V)>>(MergeIterInner<I>, C);

impl<K, V, C, I> Iterator for MergeIter<K, V, C, I>
where
C: Comparator<(K, V)>,
C: Fn(&(K, V), &(K, V)) -> Ordering,
I: Iterator<Item = (K, V)> + FusedIterator,
{
type Item = (K, V);

/// If two keys are equal, returns the key-value pair from the right source.
fn next(&mut self) -> Option<(K, V)> {
let (a_next, b_next) = self.0.nexts(&self.1);
let (a_next, b_next) = self.0.nexts(|this, that| self.1(this, that));
b_next.or(a_next)
}
}
12 changes: 4 additions & 8 deletions src/btree/dedup_sorted_iter.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use core::iter::Peekable;

use crate::{Comparator, Sortable};
use crate::{Comparator, LookupKey};

/// A iterator for deduping the key of a sorted iterator.
/// When encountering the duplicated key, only the last key-value pair is yielded.
Expand Down Expand Up @@ -30,8 +30,8 @@ where

impl<K, V, C, I> Iterator for DedupSortedIter<'_, K, V, C, I>
where
K: Sortable,
C: Comparator<K::State>,
K: LookupKey<C>,
C: Comparator,
I: Iterator<Item = (K, V)>,
{
type Item = (K, V);
Expand All @@ -48,11 +48,7 @@ where
None => return Some(next),
};

if self
.comparator
.cmp(next.0.borrow(), peeked.0.borrow())
.is_ne()
{
if self.comparator.cmp(next.0.key(), peeked.0.key()).is_ne() {
return Some(next);
}
}
Expand Down
Loading

0 comments on commit 6d9d519

Please sign in to comment.