You are on page 1of 36

List and Iterators

Reading: Chapter 6
Data Structures and Algorithms in C++
Second Edition

1
C++ STL Vector
• STL vector objects behave in many respects like C++ arrays, but
unlike C++ arrays, STL vectors can be dynamically resized
• Principal member functions of the vector class:
vector(n) : Construct a vector with space for n elements; if no argument is
given, create an empty vector.
size() : Return the number of elements in V.
empty() : Return true if V is empty and false otherwise.
resize(n) : Resize V, so that it has space for n elements.
reserve(n) : Request that the allocated storage space be large enough to hold
n elements.
operator[i] : Return a reference to the ith element of V.
at(i): Same as V[i], but throw an out of range exception if i is out of
bounds, that is, if i < 0 or i ≥V.size().
front(): Return a reference to the first element of V.
back() : Return a reference to the last element of V.
push_back(e): Append a copy of the element e to the end of V, thus increasing
its size by one.
pop_back() : Remove the last element of V, thus reducing its size by one. 2
#include <iostream>
#include <vector> C++ STL Vector
using namespace std;
int main (){ Example
vector<int> myvector(10);
int sum = 0, sz = myvector.size();
cout << "Vector sise :"<< myvector.size() <<endl;
for (unsigned i=0; i<sz; i++) myvector[i]=i;
for (unsigned i=0; i<sz/2; i++) {
int temp;
temp = myvector[sz-1-i];
myvector[sz-1-i]=myvector[i];
myvector[i]=temp;
}
myvector.push_back (100);
myvector.push_back (200);
myvector.push_back (300);
cout << "Vector sise :"<< myvector.size()<<endl;
cout << "myvector contains:";
for (unsigned i=0; i<sz; i++) cout << ' ' << myvector[i];
cout << '\n';
while (!myvector.empty()){
sum+=myvector.back();
myvector.pop_back();
}
cout << "The elements of myvector add up to " << sum << '\n';
return 0;
} 3
The Vector Abstract Data Type
• vector, also called an array list, is an ADT that supports the
following fundamental functions :
at(i): Return the element of V with index i; an error condition occurs if
i is out of range.
set(i,e): Replace the element at index i with e; an error condition occurs
if i is out of range.
insert(i,e): Insert a new element e into V to have index i; an error condition
occurs if i is out of range.
erase(i): Remove fromV the element at index i; an error condition occurs
if i is out of range.
– in addition to the standard size() and empty() functions.
– index parameter i is assumed to be in the range 0 ≤ i ≤ size()−1.

4
The Vector Abstract Data Type
at(i): Return the element of V with index i; an error condition occurs if i is out
of range.
set(i,e): Replace the element at index i with e; an error condition occurs if i is out
of range.
insert(i,e): Insert a new element e into V to have index i; an error condition occurs
if i is out of range.
erase(i): Remove fromV the element at index i; an error condition occurs if i is out
of range.

5
Array-Based Implementation
• Start with a fixed array A of size N,
– A[i] stores the element at index i.
– n < N of elements in the vector.
• implementation of the functions is simple.
– To implement the at(i) operation, for example, just return A[i].
– insert(i,e) and erase(i) algorithms
Algorithm insert(i,e):
for j = n−1,n−2, . . . , i do
A[ j+1]←A[ j] {make room for the new element}
A[i]←e
n←n+1

Algorithm erase(i):
for j = i+1, i+2, . . . ,n−1 do
A[ j−1]←A[ j] {fill in for the removed element}
n←n−1
Array-Based Implementation
• Performance
• Weakness: fixed capacity, N,
– If the actual number of elements, n, is much
smaller than N, then waste space.
– if n increases past N, then program will crash.

• Solution: extendable Array Strategy


– in C++ we cannot actually extend the array A; but we can do a work around to
increase the capacity of the vector,
– when n=N and function insert is called, perform the following steps:

1. Allocate a new array B of capacity 2N

2. Copy A[i] to B[i], for i = 0, . . . , N −1

3. Deallocate A and reassign A to point to the new array B


#include <iostream>
using namespace std;
typedef int Elem; // base element type
class ArrayVector {
public:
ArrayVector(); // constructor
int size() const; // number of elements
bool empty() const; // is vector empty?
Elem& operator[ ](int i); // element at index
Elem& at(int i); // element at index
void erase(int i); // remove element at index
void insert(int i, const Elem& e); // insert element at index
void reserve(int N); // reserve at least N spots
private:
int capacity; // current array size
int n; // number of elements in vector
Elem* A; // array storing the elements
};
ArrayVector::ArrayVector(): capacity(0), n(0), A(NULL) { }

int ArrayVector::size() const { return n;}

bool ArrayVector::empty() const { return size() == 0; }

Elem& ArrayVector::operator[ ](int i) { return A[i]; }


Elem& ArrayVector::at(int i) {
if (i < 0 || i >= n)
throw std::out_of_range("illegal index in function at()");
return A[i];
}
void ArrayVector::erase(int i) { // remove element at index
for (int j = i+1; j < n; j++) // shift elements down
A[j-1] = A[j];
n--; // one fewer element
}
void ArrayVector::reserve(int N) { // reserve at least N spots
if (capacity >= N) return; // already big enough
Elem* B = new Elem[N]; // allocate bigger array
for (int j = 0; j < n; j++) // copy contents to new array
B[j] = A[j];
if (A != NULL) delete [ ] A; // discard old array
A = B; // make B the new array
capacity = N; // set new capacity
}
void ArrayVector::insert(int i, const Elem& e) {
if (n >= capacity) // overflow?
reserve(max(1, 2 * capacity)); // double array size
for (int j = n-1; j >= i; j--) // shift elements up
A[j+1] = A[j];
A[i] = e; // put in empty slot
n++; // one more element
}
int main (){
ArrayVector myvector;
for (unsigned i=0; i<10; i++)
myvector.insert(i, 2*i);
cout << "Vector sise :"<< myvector.size()<<endl;
int sz = myvector.size();
for (unsigned i=0; i<sz/2; i++) {
int temp;
temp = myvector[sz-1-i];
myvector[sz-1-i]=myvector[i];
myvector[i]=temp;
}

cout << "myvector contains:";


for (unsigned i=0; i<myvector.size(); i++)
cout << ' ' << myvector[i];
cout << '\n';

return 0;
}
C++STL List
list(n): Construct a list with n elements; if no argument list is given, empty list is created.
size(): Return the number of elements in L.
empty(): Return true if L is empty and false otherwise.
front(): Return a reference to the first element of L.
back(): Return a reference to the last element of L.
push_front(e): Insert a copy of e at the beginning of L.
push_back(e): Insert a copy of e at the end of L.
pop_front(): Remove the fist element of L.
pop_back(): Remove the last element of L.
#include <iostream>
#include <list>
using namespace std;
int main () {
list<int> mylist;
mylist.push_back (100);
mylist.push_back (200);
mylist.push_back (300);
cout << "Popping out the elements in mylist:";
while (!mylist.empty()){
cout << ' ' << mylist.front();
mylist.pop_front();
}
cout << "\nFinal size of mylist is " << mylist.size() << '\n';
return 0; 11
}
Containers and Iterators
• Container is a data structure that stores a collection of elements.
• The STL provides a variety of different container classes such as
vector, deque, list , stack, queue , set, map

• A container supports element access through iterators.


• Why?
– This example style is valid for some containers
– Not all containers can be accessed through indexing.
• List for example. int vectorSum1(const vector<int>& V) {
int sum = 0;
for (int i = 0; i < V.size(); i++)
sum += V[i];
return sum;
}

• We need a uniform mechanism for accessing elements in all


containers
Containers and Iterators
• Every STL container defines special associated class called iterator.
• iterator is an object that specifies a position within a container with
the ability to navigate to other positions.
– iterator type: container<base>::iterator
– Example: typedef vector<int>::iterator Iterator;

• Each STL container provides two member functions to facilitate


access through iterators
– begin(): returns an iterator to the first element
– end(): return an iterator to an imaginary position just after the last element

• An iterator behaves like a pointer to an element


– If p is an iterator that refers to some position within a container
• *p: returns the element referenced by this iterator
• ++p (or p++): advances to the next element

– Extends the concept of position by adding a traversal capability


Iterating through a Container
• Let V be a container and p be an iterator for V
for (p = V.begin(); p != V.end(); ++p)
loop_body
• Example: (with an STL vector)

int vectorSum2(const vector<int> V) {


typedef vector<int>::iterator Iterator;
int sum = 0;
for (Iterator p = V.begin(); p != V.end(); ++p)
sum += *p;
return sum;
}

14
#include <iostream>
#include <vector>
using namespace std;
Iterating through a
int main (){ Container
vector<int> myvector (3,100); //100 100 100
vector<int>::iterator it;
it = myvector.begin();
it = myvector.insert ( it , 200 ); // 200 100 100 100

myvector.insert (it,2,300); //300 300 200 100 100 100

// "it" no longer valid, get a new one:


it = myvector.begin();

vector<int> anothervector (2,400);


myvector.insert (it+2,anothervector.begin(),anothervector.end());
//300 300 400 400 200 100 100 100
int myarray [] = { 501,502,503 };
myvector.insert (myvector.begin(), myarray, myarray+3);

cout << "myvector contains:";


for (it= myvector.begin(); it<myvector.end(); it++) cout << ' ' << *it;

cout << '\n';


//501 502 503 300 300 400 400 200 100 100 100
return 0;
} 15 Iterators and Sequences
Containers and Iterators
• Different containers provide iterators with different capabilities.
• Most containers (including lists, sets, and maps) provide the
ability to move not only forwards, but backwards as well. Hence
various notions of iterator exist:
– (standard) iterator: allows read-write access to elements
– const iterator: provides read-only access to elements
– bidirectional iterator: supports both ++p and --p
– random-access iterator: supports both p+i and p-i
• programmer should make sure that an iterator points to a valid
element before attempting to dereference it.
– Attempting to dereference an invalid iterator can result in your program
aborting.
• iterators can become invalid if the position that it refers to is deleted
Containers and Iterators
• In vectorSum3, argument passed by value
– Better to passing by reference
• const_iterator: provides read-only access to elements
– if p is a const iterator, it is possible to read the value of *p, but you cannot
assign it a value.
– Denoted as vector<int>::const_iterator.

int vectorSum3(const vector<int> &V) {


typedef vector<int>::const_iterator Iterator;
int sum = 0;
for (Iterator p = V.begin(); p != V.end(); ++p)
sum += *p;
return sum;
}
Implementing Iterators
• Array-based
– array A of the n elements
– index i that keeps track of the cursor
– begin() = 0
– end() = n (index following the last element)
• Linked list-based
– doubly-linked list L storing the elements, with sentinels for header and
trailer
– pointer to node containing the current element
– begin() = front node
– end() = trailer node (just after last node)

18 Iterators and Sequences


STL Iterator-Based Container Functions
• Using Vector as an example
– Let V be an STL vector of some given base type,
– Let e be an object of this base type.
– Let p and q be iterators over this base type.

• Each STL container type V supports iterators:


– V::iterator – read/write iterator type
– V::const_iterator – read-only iterator type
– V.begin(), V.end() – return start/end iterators

• iterator-based operators:
– *p: access current element
– ++p, --p: advance to next/previous element

19
STL Iterator-Based Container Functions
• STL list and STL deque supportsthese functions (constructors
are named differently).
• set, multiset, map, and multimap support all of the above
functions except assign

vector(p,q) Construct a vector by iterating between p and q,


copying each of these elements into the new vector.

assign(p, q) replace V with contents referenced by the iterator


range [p, q) (from p up to, but not including, q)

insert(p, e) insert e prior to position p.


erase(p) remove element at position p
erase(p, q) remove elements in the iterator range [p, q)
clear() Delete all these elements of V

20
STL Iterator-Based Container Functions
• Iterators p and q do not need to be drawn from the same type of
container as V, bu the container they are drawn from has the same
base type.
• Example:
– suppose L is an STL list container of integers.
– We can create a copy of L in the form of an STL vector V as follows:
list<int> L; // an STL list of integers
// . . .
vector<int> V(L.begin(), L.end()); // initialize V to be a copy of L

• Thus, we can initialize the contents of an STL container from a


standard C++ array

21
STL Vectors and Algorithms
• STL also provides algorithms that operate on containers in general, and
vectors in particular.
• Let p and q be iterators over some base type, and let e denote an object of this
base type. As above, these operations apply to the iterator range that starts at p
and ends just prior to q.
sort(p,q) Sort the elements in the range from p to q in ascending order.
It is assumed that less-than operator (“<”) is defined for the
base type.
random_shuffle(p,q) Rearrange elements in the range from p to q in random order
reverse(p,q) Reverse the elements in the range from p to q.
find(p,q,e) Return an iterator to the first element in the range from p to q
that is equal to e; if e is not found, q is returned
min_element(p,q) Return an iterator to minimum element in range from p to q.
max_element(p,q) Return an iterator to maximum element in range from p to q.
for each(p,q, f ) Apply the function f the elements in the range from p to q.

To access these functions use #include <algorithm>


An example of the use of the STL vector and iterators
#include <cstdlib> // provides EXIT SUCCESS
#include <iostream> // I/O definitions
#include <vector> // provides vector
#include <algorithm> // for sort, random shuffle
using namespace std; // make std:: accessible
int main () {
int a[ ] = {17, 12, 33, 15, 62, 45};
vector<int> v(a, a + 6); // v: 17 12 33 15 62 45
cout << v.size() << endl; // outputs: 6
v.pop_back(); // v: 17 12 33 15 62
cout << v.size() << endl; // outputs: 5
v.push back(19); // v: 17 12 33 15 62 19
cout << v.front() << " " << v.back() << endl; // outputs: 17 19
sort(v.begin(), v.begin() + 4); // v: (12 15 17 33) 62 19
v.erase(v.end() − 4, v.end() − 2); // v: 12 15 62 19
cout << v.size() << endl; // outputs: 4
char b[ ] = {’b’, ’r’, ’a’, ’v’, ’o’};
vector<char> w(b, b + 5); // w: b r a v o
random shuffle(w.begin(), w.end()); // w: o v r a b
w.insert(w.begin(), ’s’); // w: s o v r a b
for (vector<char>::iterator p = w.begin(); p != w.end(); ++p)
cout << *p << " "; // outputs: s o v r a b
cout << endl;
return EXIT SUCCESS;
}
Sequence ADT
• The Sequence ADT is the • List-based methods:
union of the Array List and
– begin(), end()
Node List ADTs
– insertFront(o),
• Elements accessed by insertBack(o)
– Index, or – eraseFront(),
eraseBack()
– Position
– insert (p, o),
• Generic methods: erase(p)
– size(), empty()
• Bridge methods:
• ArrayList-based methods: – atIndex(i),
– at(i), set(i, o), indexOf(p)
insert(i, o), erase(i)

24
Applications of Sequences
• The Sequence ADT is a basic, general-purpose, data
structure for storing an ordered collection of elements
• Direct applications:
– Generic replacement for stack, queue, vector, or list
– small database (e.g., address book)
• Indirect applications:
– Building block of more complex data structures

25 Iterators and Sequences


Linked List Implementation
• A doubly linked list provides a reasonable implementation of the Sequence ADT
• Nodes implement Position and store:
– element
– link to the previous node
– link to the next node
• Special trailer and header nodes: two sentinel nodes, created when the sequence
is first constructed. other elements are inserted between these sentinels.
• Position-based methods run in constant time
• Index-based methods require searching from header or trailer while keeping track
of indices; hence, run in linear time

header nodes/positions trailer

elements
26
Iterators and Sequences
typedef string Elem; // list element type
struct Node { // doubly linked list node
Elem elem; // node element value
Node* prev; // previous node in list
Node* next; // next node in list
};

class NodeSequence { // doubly linked list


public:
class Iterator { // an iterator for the sequence
public:
Elem& operator*(); // reference to the element
bool operator==(const Iterator& p) const; // compare positions
bool operator!=(const Iterator& p) const;
Iterator& operator++(); // move to next position
Iterator& operator--(); // move to previous position
friend class NodeSequence; // give NodeList access
private:
Node* v; // pointer to the node
Iterator(Node* u); // create from node
}; 27
NodeSequence(); // constructor
~NodeSequence(); // destructor
bool empty() const; // is list empty?
int size() const; // list size
Iterator begin() const; // beginning position
Iterator end() const; // (just beyond) last position
void insertFront(const Elem &e); // add to front of list
void insertBack(const Elem &e); // add to back of list
void insert(const Iterator& p, const Elem& e); //insert e before p
void eraseFront(); // remove from front
void eraseBack(); // remove from back
void erase(const Iterator& p); // remove p
Iterator atIndex(int i) const; // get position from index
int indexOf(const Iterator& p) const; // get index from position
private: // local type definitions
Node* header; // head-of-list sentinel
Node* trailer; // tail-of-list sentinel
int n;
};

28
// constructor from Node*
NodeSequence::Iterator::Iterator(Node* u)
{ v = u; }

// reference to the element


Elem& NodeSequence::Iterator::operator*()
{ return v->elem; }

// compare positions
bool NodeSequence::Iterator::operator==(const Iterator& p) const
{ return v == p.v; }
bool NodeSequence::Iterator::operator!=(const Iterator& p) const
{ return v != p.v; }

// move to next position


NodeSequence::Iterator& NodeSequence::Iterator::operator++()
{ v = v->next; return *this; }

// move to previous position


NodeSequence::Iterator& NodeSequence::Iterator::operator--()
{ v = v->prev; return *this; }

29
NodeSequence::NodeSequence() {
n = 0; // initially empty
header = new Node; // create sentinels
trailer = new Node;
header->next = trailer; // have them point to each other
trailer->prev = header;
}

bool NodeSequence::empty() const{


return (n==0);
}

int NodeSequence::size() const{ return n;}

// begin position is first item


NodeSequence::Iterator NodeSequence::begin() const
{return Iterator(header->next); }

// end position is just beyond last


NodeSequence::Iterator NodeSequence::end() const
{ return Iterator(trailer); }
30
// insert e before p
void NodeSequence::insert(const Iterator& p, const Elem& e) {
Node* w = p.v; // p’s node
Node* u = w->prev; // p’s predecessor
Node* v = new Node; // new node to insert
v->elem = e;
v->next = w; w->prev = v; // link in v before w
v->prev = u; u->next = v; // link in v after u
n++;
}
// insert at front
void NodeSequence::insertFront(const Elem& e) { insert(begin(), e); }
// insert at rear
void NodeSequence::insertBack(const Elem& e) { insert(end(), e); }

trailer
header
n=0
n=1

next null
next

null prev prev

u e

next
w
31
v prev
// remove p
void NodeSequence::erase(const Iterator& p) {
Node* v = p.v; // node to remove
Node* w = v->next; // successor
Node* u = v->prev; // predecessor
u->next = w; w->prev = u; // unlink p
delete v; // delete this node
n--; // one fewer element
}

// remove first
void NodeSequence::eraseFront() { erase(begin()); }
// remove last
void NodeSequence::eraseBack() { erase(--end()); }

trailer
header
n=1
n=0 e
next next null
next

null prev prev prev

u v w
32
// get position from index
NodeSequence::Iterator NodeSequence::atIndex(int i) const {
Iterator p = begin();
for (int j = 0; j < i; j++) ++p;
return p;
}

// get index from position


int NodeSequence::indexOf(const Iterator& p) const {
Iterator q = begin();
int j = 0;
while (q != p) { // until finding p
++q; ++j; // advance and count hops
}
return j;
}

33
int main() {
NodeSequence d;
string first = "Laure";

d.insertFront("you");
d.insertFront("are");
d.insertFront("How");
d.insertBack("handsom");
for (NodeSequence::Iterator p=d.begin(); p!=d.end(); ++p)
cout <<*p<<" ";
cout <<endl;
cout << *d.atIndex(0) <<" ";
NodeSequence::Iterator i= d.atIndex(d.size()-1);
cout <<*i<<" \n";
d.insert(i,first);
for (NodeSequence::Iterator p=d.begin(); p!=d.end(); ++p)
cout <<*p<<" ";
cout <<endl;
cout<<d.indexOf(i);

34
}
Array-based Implementation

• We use a circular
elements
array storing
positions
• A position object
stores:
– Element
– Index 0 1 2 3
• Indices f and l keep positions
track of first and last
positions
S
f l
35
Comparing Sequence Implementations

Operation Array List


size, empty 1 1
atIndex, indexOf, at 1 n
begin, end 1 1
set(p,e) 1 1
set(i,e) 1 n
insert(i,e), erase(i) n n
insertBack, eraseBack 1 1
insertFront, eraseFront n 1
insert(p,e), erase(p) n 1
36 Iterators and Sequences

You might also like