Note: You are looking at a static copy of the former PineWiki site, used for class notes by James Aspnes from 2003 to 2012. Many mathematical formulas are broken, and there are likely to be other bugs as well. These will most likely not be fixed. You may be able to find more up-to-date versions of some of these notes at http://www.cs.yale.edu/homes/aspnes/#classes.
1. The problem
Suppose we have an abstract data type that represents some sort of container, such as a list or dictionary. We’d like to be able to do something to every element of the container; say, count them up. How can we write operations on the abstract data type to allow this, without exposing the implementation?
To make the problem more concrete, let’s suppose we have an abstract data type that represents the set of all non-negative numbers less than some fixed bound. The core of its interface might look like this:
1.1. nums.h
1 2 3 4 5 typedef struct nums * Nums ; 6 7 8 Nums nums_create ( int bound ); ); 9 10 11 void nums_destroy ( Nums ); ); 12 13 14 int nums_contains ( Nums nums , int element ); );
1.2. nums.c
1 # include 2 # include “nums.h” 3 4 struct nums { 5 int bound ; 6 }; }; 7 8 Nums nums_create ( int bound ) 9 { 10 struct nums * n ; 11 n = malloc ( sizeof (* n )); (*)); 12 n -> bound = bound ; -> 13 return n ; 14 } 15 16 void nums_destroy ( Nums n ) { free ( n ); } ) {); } 17 18 int nums_contains ( Nums n , int element ) 19 { 20 return element >= 0 && element < n -> bound ; >=&&-> 21 }
From the outside, a Nums acts like the set of numbers from 0 to bound – 1 ; nums_contains will insist that it contains any int that is in this set and contains no int that is not in this set.
Let’s suppose now that we want to loop over all elements of some Nums , say to add them together. In particular, we’d like to implement the following pseudocode, where nums is some Nums instance:
1 sum = 0 ; 2 for ( each i in nums ) { ) { 3 sum += i ; += 4 }
One way to do this would be to build the loop into some operation in nums.c , including its body. But we’d like to be able to substitute any body for the sum += i line. Since we can’t see the inside of a Nums , we need to have some additional operation or operations on a Nums that lets us write the loop. How can we do this?
2. Option 1: Function that returns a sequence
A data-driven approach might be to add a nums_contents function that returns a sequence of all elements of some instance, perhaps in the form of an array or linked list. The advantage of this approach is that once you have the sequence, you don’t need to worry about changes to (or destruction of) the original object. The disadvantage is that you have to deal with storage management issues, and have to pay the costs in time and space of allocating and filling in the sequence. This can be particularly onerous for a “virtual” container like Nums , since we could conceivably have a Nums instance with billions of elements.
Bearing these facts in mind, let’s see what this approach might look like. We’ll define a new function nums_contents that returns an array of int s, terminated by a -1 sentinel:
1 int * 2 nums_contents ( Nums n ) 3 { 4 int * a ; 5 int i ; 6 a = malloc ( sizeof (* a ) * ( n -> bound + 1 )); (*) * (->)); 7 for ( i = 0 ; i < n -> bound ; i ++) a [ i ] = i ; ->++)] = 8 a [ n -> bound ] = – 1 ; ->] = – 9 return a ; 10 }
We might use it like this:
1 sum = 0 ; 2 contents = nums_contents ( nums ); ); 3 for ( p = contents ; * p != – 1 ; p ++) { ; *!= -++) { 4 sum += * p ; += * 5 } 6 free ( contents ); );
Despite the naturalness of the approach, returning a sequence in this case leads to the most code complexity of the options we will examine.
3. Option 2: Iterator with first/done/next operations
If we don’t want to look at all the elements at once, but just want to process them one at a time, we can build an iterator. An iterator is an object that allows you to step through the contents of another object, by providing convenient operations for getting the first element, testing when you are done, and getting the next element if you are not. In C, we try to design iterators to have operations that fit well in the top of a for loop.
For the Nums type, we’ll make each Nums its own iterator. The new operations are given here:
1 int nums_first ( Nums n ) { return 0 ; } ) {; } 2 int nums_done ( Nums n , int val ) { return val >= n -> bound ; } ) {>=->; } 3 int nums_next ( Nums n , int val ) { return val + 1 ; } ) {; }
And we use them like this:
1 sum = 0 ; 2 for ( i = nums_first ( nums ); ! nums_done ( nums , i ); i = nums_next ( nums , i )) { ); !);)) { 3 sum += i ; += 4 }
Not only do we completely avoid the overhead of building a sequence, we also get much cleaner code. It helps in this case that all we need to find the next value is the previous one; for a more complicated problem we might have to create and destroy a separate iterator object that holds the state of the loop. But for many tasks in C, the first/done/next idiom is a pretty good one.
4. Option 3: Iterator with function argument
Suppose we have a very complicated iteration, say one that might require several nested loops or even a recursion to span all the elements. In this case it might be very difficult to provide first/done/next operations, because it would be hard to encode the state of the iteration so that we could easily pick up in the next operation where we previously left off. What we’d really like to do is to be able to plug arbitrary code into the innermost loop of our horrible iteration procedure, and do it in a way that is reasonably typesafe and doesn’t violate our abstraction barrier. This is a job for function pointers, and an example of the functional programming style in action.
We’ll define a nums_foreach function that takes a function as an argument:
1 void nums_foreach ( Nums n , void (* f )( int , void *), void * f_data ) (*)(*), 2 { 3 int i ; 4 for ( i = 0 ; i < n -> bound ; i ++) f ( i , f_data ); ->++)); 5 }
The f_data argument is used to pass extra state into the passed-in function f ; it’s a void * because we want to let f work on any sort of extra state it likes.
Now to do our summation, we first define an extra function sum_helper , which adds each element to an accumulator pointed to by f_data :
1 static void sum_helper ( int i , void * f_data ) 2 { 3 *(( int *) f_data ) += i ; *((*)) += 4 }
We then feed sum_helper to the nums_foreach function:
1 sum = 0 ; 2 nums_foreach ( nums , sum_helper , ( void *) & sum ); , (*) &);
There is a bit of a nuisance in having to define the auxiliary sum_helper function and in all the casts to and from void , but on the whole the complexity of this solution is not substantially greater than the first/done/next approach. Which you should do depends on whether it’s harder to encapsulate the state of the iterator (in which case the functional approach is preferable) or of the loop body (in which case the first/done/next approach is preferable), and whether you need to bail out of the loop early (which would require special support from the foreach procedure, perhaps checking a return value from the function). However, it’s almost always straightforward to encapsulate the state of a loop body; just build a struct containing all the variables that it uses, and pass a pointer to this struct as f_data .
5. Appendix: Complete code for Nums
Here’s a grand unified Nums implementation that provides all the interfaces we’ve discussed:
5.1. nums.h
1 2 3 4 5 typedef struct nums * Nums ; 6 7 8 Nums nums_create ( int bound ); ); 9 10 11 void nums_destroy ( Nums ); ); 12 13 14 int nums_contains ( Nums nums , int element ); ); 15 16 17 18 19 int * nums_contents ( Nums n ); ); 20 21 22 int nums_first ( Nums n ); ); 23 int nums_done ( Nums n , int val ); ); 24 int nums_next ( Nums n , int val ); ); 25 26 27 void nums_foreach ( Nums n , void (* f )( int , void * f_data ), void * f_data ); (*)(),);
5.2. nums.c
1 # include 2 # include “nums.h” 3 4 struct nums { 5 int bound ; 6 }; }; 7 8 Nums nums_create ( int bound ) 9 { 10 struct nums * n ; 11 n = malloc ( sizeof (* n )); (*)); 12 n -> bound = bound ; -> 13 return n ; 14 } 15 16 void nums_destroy ( Nums n ) { free ( n ); } ) {); } 17 18 int nums_contains ( Nums n , int element ) 19 { 20 return element >= 0 && element < n -> bound ; >=&&-> 21 } 22 23 int * 24 nums_contents ( Nums n ) 25 { 26 int * a ; 27 int i ; 28 a = malloc ( sizeof (* a ) * ( n -> bound + 1 )); (*) * (->)); 29 for ( i = 0 ; i < n -> bound ; i ++) a [ i ] = i ; ->++)] = 30 a [ n -> bound ] = – 1 ; ->] = – 31 return a ; 32 } 33 34 int nums_first ( Nums n ) { return 0 ; } ) {; } 35 int nums_done ( Nums n , int val ) { return val >= n -> bound ; } ) {>=->; } 36 int nums_next ( Nums n , int val ) { return val + 1 ; } ) {; } 37 38 void nums_foreach ( Nums n , void (* f )( int , void *), void * f_data ) (*)(*), 39 { 40 int i ; 41 for ( i = 0 ; i < n -> bound ; i ++) f ( i , f_data ); ->++)); 42 }
And here’s some test code to see if it all works:
5.3. test-nums.c
1 # include 2 # include 3 # include 4 # include 5 # include 6 7 # include “nums.h” 8 # include “tester.h” 9 10 static void sum_helper ( int i , void * f_data ) 11 { 12 *(( int *) f_data ) += i ; *((*)) += 13 } 14 15 int 16 main ( int argc , char ** argv ) ** 17 { 18 Nums nums ; 19 int sum ; 20 int * contents ; 21 int * p ; 22 int i ; 23 24 tester_init (); (); 25 26 TRY { nums = nums_create ( 100 ); } ENDTRY ; ); } 27 TEST ( nums_contains ( nums , – 1 ), 0 ); , -),); 28 TEST ( nums_contains ( nums , 0 ), 1 ); ),); 29 TEST ( nums_contains ( nums , 1 ), 1 ); ),); 30 TEST ( nums_contains ( nums , 98 ), 1 ); ),); 31 TEST ( nums_contains ( nums , 99 ), 1 ); ),); 32 TEST ( nums_contains ( nums , 100 ), 0 ); ),); 33 34 sum = 0 ; 35 contents = nums_contents ( nums ); ); 36 for ( p = contents ; * p != – 1 ; p ++) { ; *!= -++) { 37 sum += * p ; += * 38 } 39 free ( contents ); ); 40 TEST ( sum , 4950 ); ); 41 42 sum = 0 ; 43 for ( i = nums_first ( nums ); ! nums_done ( nums , i ); i = nums_next ( nums , i )) { ); !);)) { 44 sum += i ; += 45 } 46 TEST ( sum , 4950 ); ); 47 48 sum = 0 ; 49 nums_foreach ( nums , sum_helper , ( void *) & sum ); , (*) &); 50 TEST ( sum , 4950 ); ); 51 tester_report ( stdout , argv [ 0 ]); ]); 52 return tester_result (); (); 53 }
$ make test gcc -g3 -ansi -pedantic -Wall -c -o test-nums.o test-nums.c gcc -g3 -ansi -pedantic -Wall -c -o nums.o nums.c gcc -g3 -ansi -pedantic -Wall -c -o tester.o tester.c gcc -g3 -ansi -pedantic -Wall -o test-nums test-nums.o nums.o tester.o ./test-nums OK!
CategoryProgrammingNotes
Prerequisite : Introduction to Iterators
Iterators are used to point at the memory addresses of STL containers. They are primarily used in sequences of numbers, characters etc. They reduce the complexity and execution time of the program.
Operations of iterators :-
1. begin() :- This function is used to return the beginning position of the container.
2. end() :- This function is used to return the after end position of the container.
#include #include // for iterators #include // for vectors using namespace std; int main() { vector< int > ar = { 1, 2, 3, 4, 5 }; vector< int >::iterator ptr; cout << "The vector elements are : " ; for (ptr = ar.begin(); ptr < ar.end(); ptr++) cout << *ptr << " " ; return 0; }
Output:
The vector elements are : 1 2 3 4 5
3. advance() :- This function is used to increment the iterator position till the specified number mentioned in its arguments.
#include #include // for iterators #include // for vectors using namespace std; int main() { vector< int > ar = { 1, 2, 3, 4, 5 }; vector< int >::iterator ptr = ar.begin(); advance(ptr, 3); cout << "The position of iterator after advancing is : " ; cout << *ptr << " " ; return 0; }
Output:
The position of iterator after advancing is : 4
4. next() :- This function returns the new iterator that the iterator would point after advancing the positions mentioned in its arguments.
5. prev() :- This function returns the new iterator that the iterator would point after decrementing the positions mentioned in its arguments.
#include #include // for iterators #include // for vectors using namespace std; int main() { vector< int > ar = { 1, 2, 3, 4, 5 }; vector< int >::iterator ptr = ar.begin(); vector< int >::iterator ftr = ar.end(); auto it = next(ptr, 3); auto it1 = prev(ftr, 3); cout << "The position of new iterator using next() is : " ; cout << *it << " " ; cout << endl; cout << "The position of new iterator using prev() is : " ; cout << *it1 << " " ; cout << endl; return 0; }
Output:
The position of new iterator using next() is : 4 The position of new iterator using prev() is : 3
6. inserter() :- This function is used to insert the elements at any position in the container. It accepts 2 arguments, the container and iterator to position where the elements have to be inserted.
#include #include // for iterators #include // for vectors using namespace std; int main() { vector< int > ar = { 1, 2, 3, 4, 5 }; vector< int > ar1 = {10, 20, 30}; vector< int >::iterator ptr = ar.begin(); advance(ptr, 3); copy(ar1.begin(), ar1.end(), inserter(ar,ptr)); cout << "The new vector after inserting elements is : " ; for ( int &x : ar) cout << x << " " ; return 0; }
Output:
The new vector after inserting elements is : 1 2 3 10 20 30 4 5
Types of Iterators :
This article is contributed by Manjeet Singh .If you like GeeksforGeeks and would like to contribute, you can also write an article using write.geeksforgeeks.org or mail your article to [email protected]. See your article appearing on the GeeksforGeeks main page and help other Geeks.
Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.
header
Iterator definitions
Anis any object that, pointing to some element in a range of elements (such as an array or a container ), has the ability to iterate through the elements of that range using a set of operators (with at least the increment () and dereference () operators).The most obvious form of iterator is a: A pointer can point to elements in an array, and can iterate through them using the increment operator (). But other kinds of iterators are possible. For example, each container type (such as a) has a specifictype designed to iterate through its elements.Notice that while a pointer is a form of iterator, not all iterators have the same functionality of pointers; Depending on the properties supported by iterators, they are classified into five different categories:
Iterator categories
category properties valid expressions all categories copy-constructible, copy-assignable and destructible X b(a);
b = a; Can be incremented ++a
a++ Random Access Bidirectional Forward Input Supports equality/inequality comparisons a == b
a != b Can be dereferenced as an rvalue *a
a->m Output Can be dereferenced as an lvalue
(only for mutable iterator types) *a = t
*a++ = t default-constructible X a;
X() Multi-pass: neither dereferencing nor incrementing affects dereferenceability { b=a; *a++; *b; } Can be decremented –a
a–
*a– Supports arithmetic operators + and – a + n
n + a
a – n
a – b Supports inequality comparisons ( < , > , <= and >= ) between iterators a < b
a > b
a <= b
a >= b Supports compound assignment operations += and -= a += n
a -= n Supports offset dereference operator ( [] ) a[n]
X
a
b
t
n
Iterators are classified into five categories depending on the functionality they implement: Input and output iterators are the most limited types of iterators: they can perform sequential single-pass input or output operations. Forward iterators have all the functionality of input iterators and -if they are not- also the functionality of output iterators , although they are limited to one direction in which to iterate through a range (forward). All standard containers support at least forward iterator types. Bidirectional iterators are like forward iterators but can also be iterated through backwards. Random-access iterators implement all the functionality of bidirectional iterators , and also have the ability to access ranges non-sequentially: distant elements can be accessed directly by applying an offset value to an iterator without iterating through all the elements in between. These iterators have a similar functionality to standard pointers (pointers are iterators of this category).The properties of each iterator category are:Whereis an iterator type,andare objects of this iterator type,is an object of the type pointed by the iterator type, andis an integer value.For more details, see the references for input iterator bidirectional iterator and random-access iterator