# CSE332S Object-Oriented Programming in C++ (Lecture 9) ## Sequential Containers Hold elements of a parameterized type (specified when the container variable is declared): `vector v; vector v1;` Elements are inserted/accessed based on their location (index) - A single location cannot be in more than 1 container - Container owns elements it contains - copied in by value, contents of container are destroyed when the container is destroyed Containers provide an appropriate interface to add, remove, and access elements - Interface provided is determined by the specifics of the container - underlying data structure _usually the provided interface of the container runs in constant time_ ### Non-random access containers Cannot access elements in constant time, must traverse the container to get to the desired element. #### Forward list - implemented as a singly linked list of elements - Elements are not contiguous in memory (no random access) - Contains a pointer to the first element (can only grow at front, supplies a `forward_iterator`) #### List - implemented as a doubly linked list of elements - Elements are not contiguous in memory (no random access) - Contains a pointer to front and back (can grow at front or back, supplies a `bidirectional_iterator`) ### Random access containers Add, remove, and access elements in constant time. #### Vector - implemented as a dynamically sized array of elements - Elements are contiguous in memory (random access) - Can only grow at the back via `push_back()` (amortized constant time, _may expand the array takes linear time_) #### Deque - double-ended queue of elements - Elements do not have to be contiguous in memory, but must be accessible in constant time (random access) - Can grow at front or back of the queue ## Iterators and iterator types Could use the subscript/indexing (operator[]) operator with a loop - Not all containers supply an [] operator, but we should still be able to traverse and access their elements Containers provide iterator types: - `vector::iterator i; // iterator over non-const elements` - `vector::const_iterator ci; // iterator over const elements` Containers provide functions for creating iterators to the beginning and just past the end of the container: ```cpp vector v = { 1, 2, 3, 4, 5 }; auto start = v.cbegin(); // cbegin() gives const iterator, you can't modify the elements, you can use .begin() to get a non-const iterator while (start != v.cend()) { // over const elements, v.cend() is not a valid element, it's just one pass the end. cout << *start << endl; ++start; } ``` ### More on iterators - Iterators generalize different uses of pointers - Most importantly, define left-inclusive intervals over the ranges of elements in a container `[begin, end)` - Iterators interface between algorithms and data structures (Iterator design pattern) - Algorithms manipulate iterators, not containers - An iterator’s value can represent 3 kinds of `states`: - `dereferencable` (points to a valid location in a range), eg `*start` - `past the end` (points just past last valid location in a range), eg `v.cend()` - `singular` (points to nothing), eg `nullptr` - Can construct, compare, copy, and assign iterators so that native and library types can inter-operate ### Properties of Iterator Intervals - Valid intervals can be traversed safely with an iterator - An empty range `[p,p)` is valid - If `[first, last)` is valid and non-empty, then `[first+1, last)` is also valid - Proof: iterative induction on the interval - If `[first, last)` is valid - and position `mid` is reachable from `first` - and `last` is reachable from `mid` - then `[first, mid)` and `[mid, last)` are also valid - Proof: divide and conquer induction on the interval - If `[first, mid)` and `[mid, last)` are valid, then `[first, last)` is valid - Proof: divide and conquer induction on the interval ### Interface supplied by different iterator types - Output iterators: used in output operations (write), - "destructive" read at head of stream (istream) - Input iterators: used in input operations (read), - "transient" write to stream (ostream) - Forward iterators: used in forward operations (read, write), common used in forward linked list - Value _persists_ after read/write - Bidirectional iterators: used in bidirectional operations (read, write), common used in doubly linked list - Value have _locations_ - Random access iterators: used in random access operations (read, write), common used in vector - Can express _distance_ between two iterators | Category/Operation | Output | Input | Forward | Bidirectional | Random Access | | ------------------ | -------------- | -------------- | -------------- | -------------- | ----------------- | | Read | N/A | `=*p`(r-value) | `=*p`(r-value) | `=*p`(r-value) | `=*p`(r-value) | | Access | N/A | `->` | `->` | `->` | `->,[]` | | Write | `*p=`(l-value) | N/A | `*p=`(l-value) | `*p=`(l-value) | `*p=`(l-value) | | Iteration | `++` | `++` | `++` | `++,--` | `++,--,+,-,+=,-=` | | Comparison | N/A | `==,!=` | `==,!=` | `==,!=` | `==,!=,<,>,<=,>=` | ## Generic algorithms in CPP A standard collection of generic algorithms - Applicable to various types and containers - E.g., sorting integers (`int`) vs. intervals (`pair`) - E.g., sorting elements in a `vector` vs. in a C-style array - Polymorphic even without inheritance relationships - interface polymorphism - Types substituted need not have a common base class - Must only provide the interface the algorithm requires - Common iterator interfaces allow algorithms to work with many types of containers, without knowing the implementation details of the container - Significantly used with the sequence containers - To reorder elements within a container’s sequence - To store/fetch values into/from a container - To calculate various values and properties from it ### Organization of C++ Algorithm Libraries The `` header file contains - Non-modifying sequence operations - Do some calculation but don’t change sequence itself - Examples include `count`, `count_if` - Mutating sequence operations - Modify the order or values of the sequence elements - Examples include `copy`, `random_shuffle` - Sorting and related operations - Modify the order in which elements appear in a sequence - Examples include `sort`, `next_permutation` - The `` header file contains - General numeric operations - Scalar and matrix algebra, especially used with `vector` - Examples include `accumulate`, `inner_product` ### Using Algorithms Example using `std::sort()` - `sort` algorithm - Reorders a given range - Can also plug in a functor to change the ordering function - http://www.cplusplus.com/reference/algorithm/sort/ - Requires random access iterators. - Requires elements being sorted implement `operator<` (less than) ```cpp #include #include #include using namespace std; int main(int argc, char* argv[]) { vector v = { 3, 1, 4, 1, 5, 9 }; sort(v.begin(), v.end()); // sort the vector for (int i : v) { cout << i << " "; } return 0; } ``` Sort forward list of strings ```cpp forward_list fl = { "hello", "world", "this", "is", "a", "test" }; sort(fl.begin(), fl.end()); ``` **This is not valid because forward list does not support random access iterators.** Sort vector of strings ```cpp vector v = { "hello", "world", "this", "is", "a", "test" }; sort(v.begin(), v.end()); ```