1104 lines
37 KiB
Text
1104 lines
37 KiB
Text
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"# Collections"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Types of Collections in Rust\n",
|
||
"\n",
|
||
"| Collection Type | Description | C++ Equivalent |\n",
|
||
"| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | --------------------- |\n",
|
||
"| `Vec<T>` | A resizable array type. It allows you to store multiple values of the same type in a single data structure. | `std::vector` |\n",
|
||
"| `String` | A growable, mutable, UTF-8 encoded string type. | `std::string` |\n",
|
||
"| `VecDeque<T>` | A double-ended queue. Like `Vec<T>` but better for use as FIFO queue. | `std::deque` |\n",
|
||
"| `LinkedList<T>` | A doubly-linked list. Like `VecDeque<T>` and adds fast concatenation. Slower than `Vec<T>` and `VecDeque<T>`. | `std::list` |\n",
|
||
"| `HashSet<T> where T: Eq + Hash` | A set of values of type `T`. Adding and removing values is fast, and it’s fast to ask whether a given value is in the set or not. | `std::unordered_set` |\n",
|
||
"| `HashMap<K, V> where K: Eq + Hash` | A table of key-value pairs. Looking up a value by its key is fast. The entries are stored in an arbitrary order. | `std::unordered_map` |\n",
|
||
"| `BTreeSet<T> where T: Ord` | A set implemented as a B-Tree. It allows you to store unique values in sorted order. | `std::set` |\n",
|
||
"| `BTreeMap<K, V> where K: Ord` | A map implemented as a B-Tree. It allows you to store key-value pairs in sorted order. | `std::map` |\n",
|
||
"| `BinaryHeap<T> where T: Ord` | A binary heap, which is a priority queue implemented with a binary heap. | `std::priority_queue` |\n",
|
||
"```"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Vec\\<T\\>"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Creating a vector"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 42,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"let mut numbers = Vec::<i32>::new(); // empty vector\n",
|
||
"\n",
|
||
"let words = vec![\"hello\", \"world\"]; // vector with two elements\n",
|
||
"let mut buffer = vec![0u8; 1024]; // vector with 1024 elements, all initialized to 0"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 48,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"[2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98]"
|
||
]
|
||
},
|
||
"execution_count": 48,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"let mut numbers: Vec<i32> = (1..100).filter(|&x| x & 1 == 0).collect(); // vector with numbers from 1 to 99\n",
|
||
"numbers"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 49,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]"
|
||
]
|
||
},
|
||
"execution_count": 49,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"let head = numbers[..10].to_vec(); // first 10 elements\n",
|
||
"head"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Accessing elements\n",
|
||
"\n",
|
||
"* `.first()`\n",
|
||
"* `.first_mut()`\n",
|
||
"* `.last()`\n",
|
||
"* `.last_mut()`\n",
|
||
"* `.get(index)`\n",
|
||
"* `.get_mut(index)`\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 50,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"[2, 4, 6, 8, 20, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98]"
|
||
]
|
||
},
|
||
"execution_count": 50,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"assert_eq!(numbers.first(), Some(&2));\n",
|
||
"assert_eq!(numbers.last(), Some(&98));\n",
|
||
"assert_eq!(numbers.get(4), Some(&10));\n",
|
||
"\n",
|
||
"{\n",
|
||
" let item: &mut i32 = numbers.get_mut(4).unwrap();\n",
|
||
" *item *= 2;\n",
|
||
"}\n",
|
||
"\n",
|
||
"numbers"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Iteration\n",
|
||
"\n",
|
||
"* Iterating over a `Vec<T>` produces items of type `T`. The elements are moved out of the vector one by one, consuming it.\n",
|
||
"* Iterating over a value of type `&[T; N]`, `&[T]`, or `&Vec<T>` - that is, a reference to an array, slice, or vector—produces items of type `&T`, references to the individual elements, which are not moved.\n",
|
||
"* Iterating over a value of type `&mut [T; N]`,` &mut [T]`, or` &mut Vec<T>` produces items of type `&mut T`."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Growing & Shrinking"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"#### Length\n",
|
||
"\n",
|
||
"The length of an array, slice, or vector is the number of elements it contains.\n",
|
||
"\n",
|
||
"* `slice.len()` returns a slice’s length, as a `usize`.\n",
|
||
"\n",
|
||
"* `slice.is_empty()` is `true` if slice contains no elements (that is, `slice.len() == 0`)."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"#### Managing the capacity\n",
|
||
"\n",
|
||
"* `Vec::with_capacity(n)` creates a new, empty vector with capacity `n`.\n",
|
||
"\n",
|
||
"* `vec.capacity()` returns vec’s capacity, as a `usize`. It’s always true that `vec.capacity() >= vec.len()`.\n",
|
||
"\n",
|
||
"* `vec.reserve(n)` makes sure the vector has at least enough spare capacity for n more elements: that is, `vec.capacity()` is at least `vec.len() + n`. If there’s already enough room, this does nothing. If not, this \n",
|
||
"allocates a larger buffer and moves the vector’s contents into it.\n",
|
||
"\n",
|
||
"* `vec.reserve_exact(n)` is like` vec.reserve(n)`, but tells vec not to allocate any extra capacity for future growth, beyond `n`. Afterward, `vec.capacity()` is exactly `vec.len() + n`.\n",
|
||
"\n",
|
||
"* `vec.shrink_to_fit()` tries to free up the extra memory if `vec.capacity()` is greater than `vec.len()`."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Adding & removing elements"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"vscode": {
|
||
"languageId": "rust"
|
||
}
|
||
},
|
||
"source": [
|
||
"* `vec.push(value)` adds the given `value` to the end of `vec`.\n",
|
||
"\n",
|
||
"* `vec.pop()` removes and returns the last element. The return type is `Option<T>`. This returns `Some(x)` if the popped element is x and None if the vector was already empty.\n",
|
||
"\n",
|
||
"* `vec.insert(index, value)` inserts the given value at `vec[index]`, sliding any existing values in `vec[index..]` one spot to the right to make room. Panics if `index > vec.len()`.\n",
|
||
"\n",
|
||
"* `vec.remove(index)` removes and returns `vec[index]`, sliding any existing values in `vec[index+1..]` one spot to the left to close the gap. Panics if `index >= vec.len()`, since in that case there is no element `vec[index]` to remove. The longer the vector, the slower this operation gets. If you find yourself doing `vec.remove(0)` a lot, consider using a `VecDeque` instead of a `Vec`.\n",
|
||
"\n",
|
||
"* `vec.resize(new_len, value)` changes the length of `vec` to `new_len`. If `new_len > vec.len()`, this appends `new_len - vec.len()` copies of `value` to the end of `vec`. If `new_len < vec.len()`, this truncates `vec` to `new_len`.\n",
|
||
"\n",
|
||
"* `vec.truncate(new_len)` truncates `vec` to `new_len`. If `new_len > vec.len()`, this does nothing. If `new_len < vec.len()`, this drops the last `vec.len() - new_len` elements.\n",
|
||
"\n",
|
||
"* `vec.split_off(index)` is like `vec.truncate(index)`, except that it returns a `Vec<T>` containing the values removed from the end of `vec`. It’s like a multivalue version of `.pop()`.\n",
|
||
"\n",
|
||
"* `vec.clear()` removes all elements from `vec`. This is equivalent to `vec.truncate(0)`.\n",
|
||
"\n",
|
||
"* `vec.extend(iter)` adds all elements from `iter` to the end of `vec`. The iterator’s element type must be `T`.\n",
|
||
"\n",
|
||
"* `vec.append(&mut other)` moves all elements from `other` to the end of `vec`. Afterward, `other` is empty.\n",
|
||
"\n",
|
||
"* `vec.drain(range)` removes all elements in `range` from `vec` and returns an iterator over the removed elements. The range can be a `Range`, `RangeFrom`, `RangeTo`, or `RangeFull`. The iterator’s element type is `T`."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 67,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"vec = [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"let mut vec = vec![1, 2, 3, 4, 5];\n",
|
||
"vec.push(6);\n",
|
||
"vec.insert(0, 0);\n",
|
||
"vec.append(&mut vec![7, 8, 9, 10]);\n",
|
||
"vec.extend(11..20);\n",
|
||
"vec.drain(0..5);\n",
|
||
"println!(\"vec = {vec:?}\");"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 68,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"removed tail = [15, 16, 17, 18, 19]\n",
|
||
"vec = [5, 6, 7, 8, 9, 10, 11, 12, 13, 14]\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"let removed_tail = vec.split_off(10);\n",
|
||
"println!(\"removed tail = {removed_tail:?}\");\n",
|
||
"println!(\"vec = {vec:?}\");"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"* `vec.retain(predicate)` removes all elements from `vec` for which `predicate` returns `false`. The predicate’s argument type is `&T`."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 70,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"vec = [6, 8, 10, 12, 14]\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"vec.retain(|x| x & 1 == 0);\n",
|
||
"println!(\"vec = {vec:?}\");"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"* `vec.dedup()` - drops repeated elements\n",
|
||
"* `vec.dedup_by(same)` - drops repeated elements based on a comparison function\n",
|
||
"* `vec.dedup_by_key(key_fn)` - drops repeated elements based on a key function\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 71,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"[1, 2, 3, 4, 5]"
|
||
]
|
||
},
|
||
"execution_count": 71,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"let mut data = vec![1, 2, 2, 3, 3, 3, 4, 4, 4, 5];\n",
|
||
"data.dedup();\n",
|
||
"data"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Joining"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"* Two methods work on *arrays of arrays* (array, slice or vector whose elements are themselves arrays, slices, or vectors):\n",
|
||
" * `slices.concat()` concatenates the elements of the outer array, producing a single vector.\n",
|
||
" * `array.join(&separator)` concatenates the elements of the outer array, producing a single vector"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 2,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"[1, 2, 3, 4, 5, 6]"
|
||
]
|
||
},
|
||
"execution_count": 2,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"[[1, 2], [3, 4], [5, 6]].concat()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 4,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"[1, 2, 3, 0, 4, 5, 6, 0, 7, 8, 9]"
|
||
]
|
||
},
|
||
"execution_count": 4,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"[[1, 2, 3], [4, 5, 6], [7, 8, 9]].join(&0)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Splitting"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"* `slice.split_at(index)` and `slice.split_at_mut(index)` break a slice in two, returning a pair. `slice.split_at(index)` is equivalent to `(&slice[..index], &slice[index..])`. These methods panic if index is out of bounds.\n",
|
||
"\n",
|
||
"* `slice.split_first()` and `slice.split_first_mut()` also return a pair: a reference to the first element `(slice[0])` and a slice reference to all the rest `(slice[1..])`.\n",
|
||
" * The return type of `.split_first()` is `Option<(&T, &[T])>`; the result is `None` if slice is empty.\n",
|
||
"\n",
|
||
"* `slice.split_last()` and `slice.split_last_mut()` are analogous but split off the last element rather than the first.\n",
|
||
" * The return type of `.split_last()` is `Option<(&T, &[T])>`.\n",
|
||
"\n",
|
||
"* `slice.split(is_sep)` and `slice.split_mut(is_sep)` split slice into one or more subslices, using the function or closure `is_sep` to figure out where to split. They return an iterator over the subslices.\n",
|
||
" * As you consume the iterator, it calls `is_sep(&element)` for each element in the slice. If `is_sep(&element)` is true, the element is a separator. Separators are not included in any output subslice.\n",
|
||
"\n",
|
||
"* `slice.splitn(n, is_sep)` and `slice.splitn_mut(n, is_sep)` are the same, but they produce at most `n` subslices. After the first `n-1` slices are found, `is_sep` is not called again. The last subslice contains all the remaining elements.\n",
|
||
"\n",
|
||
"* `slice.rsplitn(n, is_sep)` and `slice.rsplitn_mut(n, is_sep)` are just like `.splitn()` and `.splitn_mut()` except that the slice is scanned in reverse order. That is, these methods split on the `last n-1` separators in the slice, rather than the first, and the subslices are produced starting from the end.\n",
|
||
"\n",
|
||
"* `slice.chunks(n)` and `slice.chunks_mut(n)` return an iterator over non-overlapping subslices of length `n`.\n",
|
||
" * If `slice.len()` is not a multiple of `n`, then the last subslice will have a length less than `n`.\n",
|
||
"\n",
|
||
"* `slice.windows(n)` and `slice.windows_mut(n)` return an iterator over overlapping subslices of length `n`."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 5,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"data = [5, 4, 3, 2, 1, 10, 9, 8, 7, 6, 15, 14, 13, 12, 11, 20, 19, 18, 17, 16]\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"let mut data = (1..=20).collect::<Vec<i32>>();\n",
|
||
"\n",
|
||
"data.chunks_mut(5).for_each(|chunk| {\n",
|
||
" chunk.reverse();\n",
|
||
"});\n",
|
||
"\n",
|
||
"println!(\"data = {data:?}\");"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 10,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"data.windows(3) = [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6], [5, 6, 7], [6, 7, 8], [7, 8, 9], [8, 9, 10]]\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"()"
|
||
]
|
||
},
|
||
"execution_count": 10,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"{\n",
|
||
" let data = (1..=10).collect::<Vec<i32>>();\n",
|
||
"\n",
|
||
" let windows = data.windows(3).collect::<Vec<_>>();\n",
|
||
"\n",
|
||
" println!(\"data.windows(3) = {:?}\", windows);\n",
|
||
"}"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Swapping"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"* `slice.swap(i, j)` swaps the two elements `slice[i]` and `slice[j]`.\n",
|
||
"\n",
|
||
"Vectors have a related method for efficiently removing any element:\n",
|
||
"* `vec.swap_remove(i)` removes and returns `vec[i]`. This is like `vec.remove(i)` except that instead of sliding the rest of the vector’s elements over to close the gap, it simply moves vec’s last element into the gap. It’s useful when you don’t care about the order of the items left in the vector."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 11,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"[1, 2, 3, 4, 10, 6, 7, 8, 9]"
|
||
]
|
||
},
|
||
"execution_count": 11,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"let mut data = (1..=10).collect::<Vec<i32>>();\n",
|
||
"data.swap_remove(4);\n",
|
||
"data"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Sorting"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"* `slice.sort()` sorts the elements into increasing order. This method is present only when the element type implements `Ord`.\n",
|
||
"\n",
|
||
"* `slice.sort_by(cmp)` sorts the elements of slice using a function or closure cmp to specify the sort order. `cmp` must implement `Fn(&T, &T) -> std::cmp::Ordering`."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 15,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"#[derive(Debug)]\n",
|
||
"struct Person {\n",
|
||
" first_name: &'static str,\n",
|
||
" last_name: &'static str,\n",
|
||
" age: u8,\n",
|
||
"}\n",
|
||
"\n",
|
||
"let mut people = vec![\n",
|
||
" Person { first_name: \"John\", last_name: \"Doe\", age: 30 },\n",
|
||
" Person { first_name: \"Alice\", last_name: \"Smith\", age: 45 },\n",
|
||
" Person { first_name: \"Jane\", last_name: \"Doe\", age: 25 },\n",
|
||
" Person { first_name: \"Bob\", last_name: \"Smith\", age: 50 },\n",
|
||
"];"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 18,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"[Person { first_name: \"Jane\", last_name: \"Doe\", age: 25 }, Person { first_name: \"John\", last_name: \"Doe\", age: 30 }, Person { first_name: \"Alice\", last_name: \"Smith\", age: 45 }, Person { first_name: \"Bob\", last_name: \"Smith\", age: 50 }]"
|
||
]
|
||
},
|
||
"execution_count": 18,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"people.sort_by(|a, b| a.age.cmp(&b.age));\n",
|
||
"people"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 20,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"[Person { first_name: \"Jane\", last_name: \"Doe\", age: 25 }, Person { first_name: \"John\", last_name: \"Doe\", age: 30 }, Person { first_name: \"Alice\", last_name: \"Smith\", age: 45 }, Person { first_name: \"Bob\", last_name: \"Smith\", age: 50 }]"
|
||
]
|
||
},
|
||
"execution_count": 20,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"people.sort_by(|a, b| {\n",
|
||
" let a_key = (a.last_name, a.first_name);\n",
|
||
" let b_key = (b.last_name, b.first_name);\n",
|
||
" a_key.cmp(&b_key) // use tuple comparison\n",
|
||
"});\n",
|
||
"people"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"* `slice.sort_by_key(key_fn)` sorts the elements of slice into increasing order by a sort key, given by the function or closure key. The type of key must implement `Fn(&T) -> K where K: Ord`."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 23,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"[Person { first_name: \"Jane\", last_name: \"Doe\", age: 25 }, Person { first_name: \"John\", last_name: \"Doe\", age: 30 }, Person { first_name: \"Alice\", last_name: \"Smith\", age: 45 }, Person { first_name: \"Bob\", last_name: \"Smith\", age: 50 }]"
|
||
]
|
||
},
|
||
"execution_count": 23,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"people.sort_by_key(|p| p.age);\n",
|
||
"people"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"* `slice.reverse()` reverses a slice in place"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"* `slice.binary_search(&value)`, `slice.binary_search_by(&value, cmp)`, and `slice.binary_search_by_key(&value, key)` all search for value in the given sorted slice. Note that value is passed by reference.\n",
|
||
" * The return type of these methods is `Result<usize, usize>`. They return `Ok(index)` if `slice[index]` equals value under the specified sort order. If there is no such index, then they return `Err(insertion_point)` such that inserting value at insertion_point would preserve the order.\n",
|
||
" * Requires sorted order. If the slice is not sorted, the result is unspecified.\n",
|
||
"\n",
|
||
"* `slice.contains(&value)` returns `true` if any element of slice is equal to value. This simply checks each element of the slice until a match is found. Slice may be in any order."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Comparing slices\n",
|
||
"\n",
|
||
"If a type `T` supports the `==` and `!=` operators (the `PartialEq` trait, described in “Equality Tests”), then arrays `[T; N]`, slices `[T]`, and vectors `Vec<T>` support them too. Two slices are equal if they’re the same length and their corresponding elements are equal. The same goes for arrays and vectors."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"* `slice.starts_with(other)` returns `true` if slice starts with a sequence of values that are equal to the elements of the slice other:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 24,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"assert_eq!([1, 2, 3, 4].starts_with(&[1, 2]), true);\n",
|
||
"assert_eq!([1, 2, 3, 4].starts_with(&[2, 3]), false);"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"* `slice.ends_with(other)` is similar but checks the end of slice:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 25,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"assert_eq!([1, 2, 3, 4].ends_with(&[3, 4]), true);"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## VecDeque\\<T\\>\n",
|
||
"\n",
|
||
"Rust’s `std::collections::VecDeque<T>` is a deque (pronounced “deck”), a double-ended queue. It supports efficient add and remove operations at both the front and the back."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"* `deque.push_front(value)` adds a value at the front of the queue.\n",
|
||
"\n",
|
||
"* `deque.push_back(value)` adds a value at the end. (This method is used much more than `.push_front()`, because the usual convention for queues is that values are added at the back and removed at the front, like people waiting in a line.)\n",
|
||
"\n",
|
||
"* `deque.pop_front()` removes and returns the front value of the queue, returning an `Option<T>` that is `None` if the queue is empty, like `vec.pop()`.\n",
|
||
"\n",
|
||
"* `deque.pop_back()` removes and returns the value at the back, again returning an `Option<T>`.\n",
|
||
"\n",
|
||
"* `deque.front()` and `deque.back()` work like `vec.first()` and `vec.last()`. They return a reference to the front or back element of the queue. The return value is an `Option<&T>` that is `None` if the queue is empty.\n",
|
||
"\n",
|
||
"* `deque.front_mut()` and `deque.back_mut()` work like `vec.first_mut()` and `vec.last_mut()`, returning `Option<&mut T>`."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Deques don’t store their elements contiguously in memory, they don’t inherit all the methods of slices. One way to perform vector and slice operations on deque data is to convert the `VecDeque` to a `Vec`, do the operation, and then change it back:\n",
|
||
"\n",
|
||
"* `Vec<T>` implements `From<VecDeque<T>>`, so `Vec::from(deque)` turns a deque into a vector. This costs `O(n)` time, since it may require rearranging the elements.\n",
|
||
"\n",
|
||
"* `VecDeque<T>` implements `From<Vec<T>>`, so `VecDeque::from(vec)` turns a vector into a deque. This is also `O(n)`, but it’s usually fast, even if the vector is large, because the vector’s heap allocation can simply be moved to the new deque.\n",
|
||
"\n",
|
||
"This method makes it easy to create a deque with specified elements, even though there is no standard `vec_deque![]` macro:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 28,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"v = [0, 1, 2, 3, 4, 5]\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"use std::collections::VecDeque;\n",
|
||
"\n",
|
||
"let mut v = VecDeque::from(vec![1, 2, 3, 4]);\n",
|
||
"\n",
|
||
"v.push_front(0);\n",
|
||
"v.push_back(5);\n",
|
||
"\n",
|
||
"println!(\"v = {:?}\", v);"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## LinkedList\\<T\\>"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"LinkedList<T> is a doubly-linked list. It’s like VecDeque<T> but with some differences:\n",
|
||
"\n",
|
||
"* each element is stored in a separate heap allocation, so it’s slower to access elements by index\n",
|
||
"* it’s faster to concatenate two `LinkedList<T>`s"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 33,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"lst_1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n",
|
||
"lst_2 = []\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"use std::collections::LinkedList;\n",
|
||
"\n",
|
||
"let mut lst_1 = (1..=5).collect::<LinkedList<i32>>();\n",
|
||
"let mut lst_2 = (6..=10).collect::<LinkedList<i32>>();\n",
|
||
"\n",
|
||
"lst_1.append(&mut lst_2);\n",
|
||
"\n",
|
||
"println!(\"lst_1 = {:?}\", lst_1);\n",
|
||
"println!(\"lst_2 = {:?}\", lst_2);"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## HashMap\\<K, V\\> & BTreeMap\\<K, V\\>"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"* A **map** is a collection of **key-value** pairs (called entries). No two entries have the same key, and the entries are kept organized so that if you have a key, you can efficiently look up the corresponding value in a map. \n",
|
||
"* A map is a lookup table."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"* A `HashMap` stores the keys and values in a hash table, so it requires a key type `K` that implements `Hash` and `Eq`, the standard traits for hashing and equality.\n",
|
||
"* A `BTreeMap` stores the keys and values in a B-Tree, so it requires a key type `K` that implements `Ord`, the standard trait for ordering."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Creating a map"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"* `HashMap::new()` and `BTreeMap::new()` create new, empty maps.\n",
|
||
"\n",
|
||
"* `iter.collect()` can be used to create and populate a new `HashMap` or `BTreeMap` from key-value pairs. iter must be an `Iterator<Item=(K, V)>`.\n",
|
||
"\n",
|
||
"* `HashMap::with_capacity(n)` creates a new, empty hash map with room for at least n entries. `HashMaps`, like vectors, store their data in a single heap allocation, so they have a capacity and the related methods `hash_map.capacity()`, `hash_map.reserve(additional)`, and `hash_map.shrink_to_fit()`. `BTreeMaps` do not."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Working with keys & values"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"* `map.len()` returns the number of entries.\n",
|
||
"\n",
|
||
"* `map.is_empty()` returns true if map has no entries.\n",
|
||
"\n",
|
||
"* `map.contains_key(&key)` returns true if the map has an entry for the given key.\n",
|
||
"\n",
|
||
"* `map.get(&key)` searches map for an entry with the given key. If a matching entry is found, this returns Some(r), where r is a reference to the corresponding value. Otherwise, this returns None.\n",
|
||
"\n",
|
||
"* `map.get_mut(&key)` is similar, but it returns a mut reference to the value.\n",
|
||
"\n",
|
||
"* `map.insert(key, value)` inserts the entry `(key, value)` into map. If there’s already an entry for key in the map, the newly inserted value overwrites the old one.\n",
|
||
" * Returns the old value, if any. The return type is `Option<V>`.\n",
|
||
"\n",
|
||
"* `map.extend(iterable)` iterates over the `(K, V)` items of iterable and inserts each of those key-value pairs into map.\n",
|
||
"\n",
|
||
"* `map.append(&mut map2)` moves all entries from `map2` into map. Afterward, `map2` is empty.\n",
|
||
"\n",
|
||
"* `map.remove(&key)` finds and removes any entry with the given key from map.\n",
|
||
" * Returns the removed value, if any. The return type is `Option<V>`.\n",
|
||
"\n",
|
||
"* `map.clear()` removes all entries.\n",
|
||
"\n",
|
||
"* `map[&key]` - maps implement the `Index` built-in trait. However, this panics if there is not already an entry for the given key, like an out-of-bounds array access, so use this syntax only if the entry you’re looking up is sure to be populated."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Because a `BTreeMap<K, V>` keeps its entries sorted by key, it supports an additional operation:\n",
|
||
"\n",
|
||
"* `btree_map.split_at(&key)` splits `btree_map` in two. Entries with keys less than key are left in `btree_map`. Returns a new `BTreeMap<K, V>` containing the other entries."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Entries"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"* Both `HashMap` and `BTreeMap` have a corresponding `Entry` type. The point of entries is to eliminate redundant map lookups\n",
|
||
"\n",
|
||
"* All `Entry` values are created by the same method:\n",
|
||
" * `map.entry(key)` returns an `Entry` for the given key. If there’s no such key in the map, this returns a vacant `Entry`."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 46,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"dict_entry = \"three\"\n",
|
||
"dictionary = {3: \"three\", 2: \"two\", 1: \"one\", 4: \"four\"}\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"()"
|
||
]
|
||
},
|
||
"execution_count": 46,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"{\n",
|
||
" use std::collections::HashMap;\n",
|
||
"\n",
|
||
" let mut dictionary: HashMap<i32, String> = vec![(1, \"one\".to_string()), (2, \"two\".to_string()), (4, \"four\".to_string())].into_iter().collect();\n",
|
||
"\n",
|
||
" {\n",
|
||
" let dict_entry = dictionary.entry(3).or_insert(\"three\".to_string());\n",
|
||
" println!(\"dict_entry = {:?}\", dict_entry);\n",
|
||
" }\n",
|
||
" \n",
|
||
" println!(\"dictionary = {:?}\", dictionary);\n",
|
||
"}"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Entry values provide two methods for filling in vacant entries:\n",
|
||
"\n",
|
||
"* `map.entry(key).or_insert(value)` ensures that map contains an entry with the given key, inserting a new entry with the given default value if needed. It returns a `mut` reference to the new or existing value."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 44,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"vote_counts: {\"John\": 3, \"Alice\": 2, \"Jane\": 2}\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"let ballots = vec![\"John\", \"Jane\", \"Alice\", \"Jane\", \"John\", \"Alice\", \"John\"];\n",
|
||
"\n",
|
||
"let mut vote_counts: HashMap<String, usize> = HashMap::new();\n",
|
||
"\n",
|
||
"for name in ballots {\n",
|
||
" let count = vote_counts.entry(name.to_string()).or_insert(0);\n",
|
||
" *count += 1;\n",
|
||
"}\n",
|
||
"\n",
|
||
"println!(\"vote_counts: {:?}\", vote_counts);"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"* `map.entry(key).or_insert_with(default_fn)` is the same, except that if it needs to create a new entry, it calls `default_fn()` to produce the default value. If there’s already an entry for key in the map, then `default_fn` is not used."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Map Iteration"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"* Iterating by value `for (k, v) in map` produces `(K, V)` pairs. This consumes the map.\n",
|
||
"\n",
|
||
"* Iterating over a shared reference `for (k, v) in &map` produces `(&K, &V)` pairs.\n",
|
||
"\n",
|
||
"* Iterating over a mut reference `for (k, v) in &mut map` produces `(&K, &mut V)` pairs.\n",
|
||
"\n",
|
||
"Like vectors, maps have `.iter()` and `.iter_mut()` methods which return by-reference iterators, just like iterating over `&map` or `&mut map`. In addition:\n",
|
||
"\n",
|
||
"* `map.keys()` returns an iterator over just the keys, by reference.\n",
|
||
"\n",
|
||
"* `map.values()` returns an iterator over the values, by reference.\n",
|
||
"\n",
|
||
"* `map.values_mut()` returns an iterator over the values, by mut reference."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Hashing"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"* `std::hash::Hash` is the standard library trait for hashable types. `HashMap` keys and `HashSet` elements must implement both `Hash` and `Eq`.\n",
|
||
"\n",
|
||
"* Most built-in types that implement `Eq` also implement `Hash`. The integer types, `char`, and `String` are all hashable; so are tuples, arrays, slices, and vectors, as long as their elements are hashable.\n",
|
||
"\n",
|
||
"* One principle of the standard library is that a value should have the same hash code regardless of where you store it or how you point to it. Therefore, a reference has the same hash code as the value it refers to, and a `Box` has the same hash code as the boxed value. A vector vec has the same hash code as the slice containing all its data, `&vec[..]`. A `String` has the same hash code as a `&str` with the same characters.\n",
|
||
"\n",
|
||
"* Structs and enums don’t implement `Hash` by default, but an implementation can be derived (all fields must implement `Hash`):"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 48,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"#[derive(Debug, Eq, PartialEq, Hash)]\n",
|
||
"struct GadgetId(u64);"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"If you implement `PartialEq` by hand for a type, you should also implement `Hash` by hand:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 51,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"struct Gadget {\n",
|
||
" id: GadgetId,\n",
|
||
" name: String,\n",
|
||
" description: String \n",
|
||
"}\n",
|
||
"\n",
|
||
"impl PartialEq for Gadget {\n",
|
||
" fn eq(&self, other: &Self) -> bool {\n",
|
||
" self.id == other.id && self.name == other.name\n",
|
||
" }\n",
|
||
"}\n",
|
||
"\n",
|
||
"impl Eq for Gadget {}\n",
|
||
"\n",
|
||
"impl std::hash::Hash for Gadget {\n",
|
||
" fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {\n",
|
||
" self.id.hash(hasher);\n",
|
||
" self.name.hash(hasher);\n",
|
||
" }\n",
|
||
"}"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"* `std::hash::BuildHasher`, is the trait for types that represent the initial state of a hashing algorithm. Each `Hasher` is single-use, like an iterator: you use it once and throw it away. A `BuildHasher` is reusable."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 52,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"use std::hash::{Hash, Hasher, BuildHasher};\n",
|
||
"\n",
|
||
"fn compute_hash<B, T>(builder: &B, value: &T) -> u64\n",
|
||
" where B: BuildHasher, T: Hash\n",
|
||
"{\n",
|
||
" let mut hasher = builder.build_hasher(); // 1. start the algorithm\n",
|
||
" value.hash(&mut hasher); // 2. feed it data\n",
|
||
" hasher.finish() // 3. finish, producing a u64\n",
|
||
"}"
|
||
]
|
||
}
|
||
],
|
||
"metadata": {
|
||
"kernelspec": {
|
||
"display_name": "Rust",
|
||
"language": "rust",
|
||
"name": "rust"
|
||
},
|
||
"language_info": {
|
||
"codemirror_mode": "rust",
|
||
"file_extension": ".rs",
|
||
"mimetype": "text/rust",
|
||
"name": "rust",
|
||
"pygment_lexer": "rust",
|
||
"version": ""
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 2
|
||
}
|