Loading pageā¦
Rust walkthroughs
Loading pageā¦
indexmap::IndexSet::get_full return both index and value unlike standard HashSet?IndexSet::get_full returns a tuple (usize, &T) containing both the insertion-order index and a reference to the value, whereas HashSet::get only returns Option<&T>. This dual return is possible because IndexSet maintains insertion order in a contiguous array alongside its hash tableāeach element has a stable index that reflects when it was inserted. The method name "get_full" indicates that you get the "full" information about the element's position and value. This capability is fundamentally unavailable in standard HashSet because it doesn't track or expose insertion order; elements are stored according to their hash, and there's no meaningful index to return. When you need to know both "what is this value" and "when was it inserted," IndexSet::get_full provides both pieces of information in a single lookup.
use indexmap::IndexSet;
fn main() {
let mut set = IndexSet::new();
set.insert("apple");
set.insert("banana");
set.insert("cherry");
// get_full returns (index, &value) when found
if let Some((index, value)) = set.get_full(&"banana") {
println!("Found 'banana' at index {} with value '{}'", index, value);
// Output: Found 'banana' at index 1 with value 'banana'
}
// Compare with standard HashSet behavior
use std::collections::HashSet;
let mut std_set = HashSet::new();
std_set.insert("apple");
std_set.insert("banana");
std_set.insert("cherry");
// HashSet::get only returns &T, no index
if let Some(value) = std_set.get(&"banana") {
println!("Found '{}' in HashSet (no index available)", value);
}
// Index 0: apple (first inserted)
// Index 1: banana (second inserted)
// Index 2: cherry (third inserted)
}get_full returns the insertion-order index and a reference to the value; HashSet::get only returns the reference.
use indexmap::IndexSet;
fn main() {
let mut set = IndexSet::new();
// Elements are indexed by insertion order
set.insert("first"); // index 0
set.insert("second"); // index 1
set.insert("third"); // index 2
// Indices are stable and reflect insertion order
assert_eq!(set.get_full(&"first"), Some((0, &"first")));
assert_eq!(set.get_full(&"second"), Some((1, &"second")));
assert_eq!(set.get_full(&"third"), Some((2, &"third")));
// If an element already exists, insert returns the existing index
let existing_index = set.insert_full("second");
assert_eq!(existing_index, (1, false)); // (index, inserted: false)
// The index doesn't change for existing elements
assert_eq!(set.get_full(&"second"), Some((1, &"second")));
// Inserting new elements continues the index sequence
set.insert("fourth"); // index 3
assert_eq!(set.get_full(&"fourth"), Some((3, &"fourth")));
println!("All elements in insertion order:");
for (index, value) in set.iter().enumerate() {
println!(" index {}: {}", index, value);
}
}Indices are assigned based on insertion order and remain stable for existing elements.
use indexmap::IndexSet;
use std::collections::HashSet;
fn main() {
// IndexSet: ordered, indexed
let mut index_set = IndexSet::new();
index_set.insert("a");
index_set.insert("b");
index_set.insert("c");
// Can iterate in insertion order
println!("IndexSet iteration order:");
for (i, v) in index_set.iter().enumerate() {
println!(" [{}] = {}", i, v);
}
// Always: a, b, c
// Can get index
let (idx, _) = index_set.get_full(&"b").unwrap();
println!("'b' is at index {}", idx); // Always 1
// HashSet: unordered, no indices
let mut hash_set = HashSet::new();
hash_set.insert("a");
hash_set.insert("b");
hash_set.insert("c");
// Iteration order is not guaranteed
println!("\nHashSet iteration order (unspecified):");
for v in &hash_set {
println!(" {}", v);
}
// Order could be anything!
// No way to get an index
// hash_set.get(&"b") returns Option<&str>, no index
// There's no equivalent to get_full
// If you need the index in HashSet, you'd have to:
// 1. Convert to a Vec and search (O(n))
// 2. Maintain a separate index mapping (complex)
}HashSet has no index concept; IndexSet maintains insertion order with stable indices.
use indexmap::IndexSet;
fn main() {
let mut set = IndexSet::new();
set.insert("zero");
set.insert("one");
set.insert("two");
set.insert("three");
// get_full gives you the index, which enables index-based operations
// 1. Remove by index (not possible with HashSet)
let removed = set.swap_remove_index(1);
println!("Removed at index 1: {:?}", removed); // Some("one")
// After removal, indices shift (swap_remove) or the element is swapped
println!("After swap_remove:");
for (i, v) in set.iter().enumerate() {
println!(" [{}] = {}", i, v);
}
// 2. Get by index
if let Some(value) = set.get_index(0) {
println!("Value at index 0: {}", value);
}
// 3. Get both index and value from get_full
if let Some((index, value)) = set.get_full(&"two") {
println!("'two' is at index {} with value '{}'", index, value);
}
// 4. Use index for cross-referencing with other ordered data
let metadata = vec!["meta-zero", "meta-one", "meta-two", "meta-three"];
if let Some((index, _)) = set.get_full(&"two") {
// Use the index to look up related data
println!("Metadata for 'two': {}", metadata[index]);
}
}The index from get_full enables index-based operations like removal and cross-referencing.
use indexmap::IndexSet;
fn main() {
let mut set = IndexSet::new();
// insert_full returns (index, inserted)
let (idx1, was_new1) = set.insert_full("first");
println!("Inserted 'first' at index {}, was_new: {}", idx1, was_new1);
let (idx2, was_new2) = set.insert_full("second");
println!("Inserted 'second' at index {}, was_new: {}", idx2, was_new2);
// Inserting duplicate returns existing index, inserted = false
let (idx3, was_new3) = set.insert_full("first");
println!("Tried 'first' again: index {}, was_new: {}", idx3, was_new3);
// index is still 0, was_new is false
// get_full confirms the same index
if let Some((index, value)) = set.get_full(&"first") {
assert_eq!(index, 0);
assert_eq!(value, &"first");
}
// Pattern: use get_full to check if element exists and get its position
if let Some((index, _)) = set.get_full(&"second") {
println!("Found 'second' at index {}", index);
// Now you can use index for other operations
} else {
// Element doesn't exist, could insert it
set.insert("second");
}
}insert_full and get_full work together: both return the index for consistent position tracking.
use indexmap::IndexSet;
fn main() {
// Scenario: Track the order in which unique users joined a chat
let mut users = IndexSet::new();
users.insert("alice");
users.insert("bob");
users.insert("charlie");
users.insert("diana");
// Later, find out when a user joined
fn get_join_position(users: &IndexSet<&str>, username: &str) -> Option<usize> {
users.get_full(username).map(|(index, _)| index)
}
println!("Alice joined at position: {:?}", get_join_position(&users, "alice"));
println!("Bob joined at position: {:?}", get_join_position(&users, "bob"));
println!("Eve joined at position: {:?}", get_join_position(&users, "eve")); // None
// First user has index 0, second has 1, etc.
// The index tells you the relative join order
// This is impossible with HashSet without additional data structures
}get_full enables position-based queries that would require extra bookkeeping with HashSet.
use indexmap::IndexSet;
fn main() {
// Scenario: Maintain ordered unique items and slice by index range
let mut items = IndexSet::new();
for i in 0..10 {
items.insert(format!("item-{}", i));
}
// Find an item and get everything after it
if let Some((start_index, _)) = items.get_full(&"item-3".to_string()) {
println!("Items from 'item-3' onwards:");
for (i, item) in items.iter().enumerate().skip(start_index) {
println!(" [{}] {}", i, item);
}
}
// Get items before a certain point
if let Some((end_index, _)) = items.get_full(&"item-7".to_string()) {
println!("\nItems before 'item-7':");
for (i, item) in items.iter().enumerate().take(end_index) {
println!(" [{}] {}", i, item);
}
}
// With HashSet, you'd need to collect into a Vec first
// and then search (O(n) instead of O(1))
}The index from get_full enables range operations relative to an element's position.
use indexmap::IndexSet;
fn main() {
// Scenario: Ordered set of column names with parallel data vectors
let mut columns = IndexSet::new();
columns.insert("name");
columns.insert("age");
columns.insert("email");
// Parallel data storage indexed by column position
let mut data: Vec<Vec<String>> = vec![
vec!["Alice".to_string(), "Bob".to_string()],
vec!["30".to_string(), "25".to_string()],
vec!["alice@example.com".to_string(), "bob@example.com".to_string()],
];
// Query: get the age column data
if let Some((age_idx, col_name)) = columns.get_full(&"age") {
println!("Column '{}' at index {}: {:?}", col_name, age_idx, data[age_idx]);
}
// Query: find which column a value belongs to
fn find_column_for_value(
columns: &IndexSet<&str>,
data: &[Vec<String>],
value: &str
) -> Option<(&str, usize)> {
for (idx, col_name) in columns.iter().enumerate() {
if data[idx].iter().any(|v| v == value) {
return Some((*col_name, idx));
}
}
None
}
if let Some((col, idx)) = find_column_for_value(&columns, &data, "Bob") {
println!("Found 'Bob' in column '{}' (index {})", col, idx);
}
}get_full provides the index needed to cross-reference between ordered sets and parallel arrays.
use indexmap::IndexSet;
fn main() {
// IndexSet maintains insertion order, not sorted order
let mut set = IndexSet::new();
set.insert("cherry");
set.insert("apple");
set.insert("banana");
// Iteration order is insertion order, not sorted
println!("Insertion order:");
for v in &set {
println!(" {}", v); // cherry, apple, banana
}
// If you need sorted access, you can:
// 1. Get indices and sort by values
let mut indices: Vec<usize> = (0..set.len()).collect();
indices.sort_by_key(|&i| set.get_index(i).unwrap());
println!("\nSorted access using indices:");
for i in indices {
println!(" [{}] {}", i, set.get_index(i).unwrap());
}
// 2. Use get_full in combination with binary search on a sorted view
let sorted_values: Vec<&&str> = set.iter().sorted().collect();
// Find in sorted view, then get original index
let target = "banana";
if let Ok(pos) = sorted_values.binary_search(&&target) {
let value = sorted_values[pos];
if let Some((original_index, _)) = set.get_full(*value) {
println!("\n'{}' found at sorted position {}, original index {}",
target, pos, original_index);
}
}
}
// Helper trait for sorted iteration
trait SortedIter<'a, T: 'a + Ord>: Iterator<Item = &'a T> + Sized {
fn sorted(self) -> std::vec::IntoIter<&'a T> {
let mut v: Vec<&'a T> = self.collect();
v.sort();
v.into_iter()
}
}
impl<'a, T: 'a + Ord, I: Iterator<Item = &'a T>> SortedIter<'a, T> for I {}IndexSet maintains insertion order; combine get_full with sorting for ordered access patterns.
use indexmap::IndexSet;
use std::collections::HashSet;
fn main() {
// Memory: IndexSet stores both a hash table and a contiguous array
// HashSet stores only a hash table
// Performance comparison:
// IndexSet::get_full: O(1) average, returns (index, &T)
// HashSet::get: O(1) average, returns &T only
let mut index_set = IndexSet::new();
let mut hash_set = HashSet::new();
for i in 0..1000 {
index_set.insert(i);
hash_set.insert(i);
}
// Both are O(1) for lookup
// But IndexSet gives you the index for free
let start = std::time::Instant::now();
for i in 0..1000 {
let _ = index_set.get_full(&i);
}
println!("IndexSet::get_full x1000: {:?}", start.elapsed());
let start = std::time::Instant::now();
for i in 0..1000 {
let _ = hash_set.get(&i);
}
println!("HashSet::get x1000: {:?}", start.elapsed());
// Memory overhead:
// IndexSet: additional Vec<usize> for indices
// HashSet: just the hash table
println!("\nIndexSet memory is slightly higher due to index storage");
println!("Trade-off: extra memory for ordered iteration and indices");
}IndexSet has slightly higher memory overhead but provides indices and ordered iteration.
use indexmap::IndexSet;
use std::collections::HashSet;
fn main() {
let mut index_set = IndexSet::new();
index_set.insert("a");
index_set.insert("b");
index_set.insert("c");
let mut hash_set = HashSet::new();
hash_set.insert("a");
hash_set.insert("b");
hash_set.insert("c");
// IndexSet methods that return index:
// get_full: returns Option<(usize, &T)>
println!("IndexSet::get_full: {:?}", index_set.get_full(&"b"));
// Some((1, "b"))
// get_index: returns Option<&T> by index
println!("IndexSet::get_index: {:?}", index_set.get_index(1));
// Some("b")
// insert_full: returns (usize, bool) - index and whether inserted
let (idx, inserted) = index_set.insert_full("d");
println!("IndexSet::insert_full: index={}, inserted={}", idx, inserted);
// index=3, inserted=true
// HashSet methods:
// get: returns Option<&T> - no index
println!("HashSet::get: {:?}", hash_set.get(&"b"));
// Some(&"b")
// insert: returns bool - was it new?
let inserted = hash_set.insert("d");
println!("HashSet::insert: inserted={}", inserted);
// inserted=true
// No equivalent to get_full, get_index, or insert_full in HashSet
}IndexSet provides index-aware variants of get and insert that HashSet cannot offer.
Method comparison:
| Operation | IndexSet | HashSet |
|-----------|----------|---------|
| Get value | get(&T) -> Option<&T> | get(&T) -> Option<&T> |
| Get with index | get_full(&T) -> Option<(usize, &T)> | Not available |
| Get by index | get_index(usize) -> Option<&T> | Not available |
| Insert | insert(T) -> bool | insert(T) -> bool |
| Insert with index | insert_full(T) -> (usize, bool) | Not available |
| Remove | remove(&T) -> bool | remove(&T) -> bool |
| Remove by index | swap_remove_index(usize) -> Option<T> | Not available |
| Iterate | Ordered by insertion | Unspecified order |
When IndexSet::get_full is valuable:
| Use Case | Benefit | |----------|---------| | Position tracking | Know when an element was inserted | | Cross-referencing | Index into parallel arrays/vectors | | Ordered slicing | Get elements before/after a found element | | Index-based operations | Use index for removal or other operations | | Two-way lookup | Find element by value, use index for related data |
Performance comparison:
| Operation | IndexSet | HashSet | |-----------|----------|---------| | Lookup | O(1) average | O(1) average | | Insert | O(1) average | O(1) average | | Iteration | O(n), ordered | O(n), unordered | | Memory | Higher (array + hash table) | Lower (hash table only) |
Key insight: IndexSet::get_full bridges the gap between set semantics (uniqueness) and sequence semantics (ordered positions). Standard HashSet provides uniqueness with O(1) lookups but no positional informationāelements exist in an unspecified hash table order, and there's no stable index associated with each element. IndexSet adds a contiguous array alongside its hash table, where each element's position in the array reflects its insertion order. This dual structure enables get_full to return both the index (position in the array) and the value (reference to the element) in a single O(1) operation. The index is meaningful: it tells you the element's relative insertion order, enables cross-referencing with other ordered data structures, and allows index-based operations like get_index and swap_remove_index. The trade-off is slightly higher memory usage and the constraint that indices can change when elements are removed (unless you use shift_remove which shifts all subsequent elements). For any application where you need both uniqueness guarantees and position trackingāsuch as ordered unique lists, column headers in data tables, or tracking join order in a chat roomāIndexSet::get_full provides information that would require additional bookkeeping data structures with HashSet.