Lesson 10 - Vectors
10.1 Vector of integers
// ex10-1.cpp - vector of integers
#include <iostream>
#include <vector>
using namespace std;
int main()
{
// create a vector of 3 integers
vector<int> list(3);
// assign them some values
list[0]=10; list[1]=20; list[2]=30;
// print out the values
int i;
for (i=0;i<3;i++)
cout << "list[" << i << "]=" << list[i] << endl;
// get the number of elements
cout << "list is of length " << list.size() << endl;
// extend the list by one more element
list.push_back(40);
// print out the values now
for (i=0;i<list.size();i++)
cout << "list[" << i << "]=" << list[i] << endl;
return(0);
}
|
ex10-1
list[0]=10
list[1]=20
list[2]=30
list is of length 3
list[0]=10
list[1]=20
list[2]=30
list[3]=40
|
Example ex10-1 introduces one method of dealing with tables of values in
C++. By a table, we mean a number of values referred to under one
name: it could be a list of numbers, or a two-dimensional grid. In
computer jargon these are often referred to as arrays, since the
values are arrayed out in some space. Thus we could hold, for example,
a table of exam results for a student, where the first element of the table
is the result in English, the second element holds the result in Maths
and so on. The table might have a name, maybe the name of the student.
We could then generalise this across all students, by a 2-dimensional grid
of results in which the rows represented the students and the columns the
subjects. The name of such an object might be the name of the class.
In this example we create and use a simple list of numerical values,
called 'list'. The declaration of 'list' shows us that is of type 'vector<int>':
this means that list is not a single value but a one dimensional table
of values called a 'vector'. Furthermore, inside this table, each
value is just an integer. The declaration 'vector<int> list(3);', then
also specifies that the name of the table is 'list' and that its initial
size is 3 elements. Note that to use vectors we need to include the standard
software component 'vector' at the top of the program. To access each element
of the list, we use a square bracket notation:
vector-name [ index-expression ]
Where the name of the vector is suffixed with an integer expression inside
'[' and ']'. This expression should evaluate to a number between
0 and one less than the length of the vector: that is, vector elements
are indexed starting from 0 not from 1 (this is an endless cause of problems!).
In the example, the elements are given values with assignment statements,
and these are listed out using a 'for' loop to run from 0 to 2.
The current length of a vector is always available through the method
function 'size()', as you can see in the program. An extremely useful
property of vectors is that their size can change dynamically in the program.
In the example, the method function 'push_back()' is used to append a new
value to the end of the vector. This function firsts extends the
size of the vector to 4 elements, then stores the value specified in the
new element.
|
10.2 Vector of doubles
// ex10-2.cpp - vector of doubles
#include <iostream>
#include <iomanip>
#include <vector>
using namespace std;
// pseudo random number generator
double random()
{
static int seed=123456789;
int tmp_seed = 16807 * ( seed % 127773 ) -
2836 * (seed / 127773 );
if (tmp_seed >= 0)
seed = tmp_seed;
else
seed = tmp_seed + 2147483647;
return(seed/2147483648.0);
}
// count number of values between brackets
int countinrange(vector<double>& table,double lo,double hi)
{
int num=0;
for (int i=0;i<table.size();i++)
if ((lo <= table[i]) && (table[i] < hi))
num++;
return(num);
}
int main()
{
// create a vector of 100 doubles initialised to 0
vector<double> list(100,0);
// assign each a random value
for (int i=0;i<100;i++) list[i] = random();
// print out how many numbers in each tenth
for (double lo=0.0;lo+0.1 <= 1.0;lo=lo+0.1)
cout << "Number between " << setprecision(1)
<< setiosflags(ios::fixed)
<< lo << " and " << lo+0.1 << " = "
<< setw(2) << countinrange(list,lo,lo+0.1)
<< endl;
return(0);
}
|
ex10-2
Number between 0.0 and 0.1 = 14
Number between 0.1 and 0.2 = 10
Number between 0.2 and 0.3 = 11
Number between 0.3 and 0.4 = 8
Number between 0.4 and 0.5 = 10
Number between 0.5 and 0.6 = 8
Number between 0.6 and 0.7 = 6
Number between 0.7 and 0.8 = 12
Number between 0.8 and 0.9 = 13
Number between 0.9 and 1.0 = 8
|
There are two main new ideas presented in example ex10-2. Firstly
this example uses a vector of double variables rather than integer variables,
and secondly the example shows the recommended way of passing a vector
as an argument to a function. The declaration 'vector<double>
list(100,0);' creates a vector of 100 variables called list[0]
to list[99], and in addition initialises each of them to 0.
You see that the parameter list in the declaration of 'list' contains two
arguments: the first specifies the starting number of elements, the second
an initial value for those elements. If you do not specify an initial
value, then the initial values could be anything.
Once the list has been created, it is filled with 100 random numbers
using a function called 'random()' which exploits the fact that the remainders
after multiplication and division by prime numbers follow a pseudo-random
sequence. The function then scales the random number and returns
a value betwen 0.0 and 1.0. The random() function contains a 'static'
variable, seed, which holds its value from one call to the next.
The rest of the program tests the effctiveness
of the random number generator to generate equal numbers of values in each
interval of size 0.1 between 0.0 and 1.0. To do this it uses a function
called 'countinrange()' to scan the list of numbers and report how many
of the numbers can be found within the limits specified as arguments.
It is in the declaration of this function that you can see the recommended
method of passing a vector to a function. You will see that in the
parameter definition for the function the vector is referred to as 'vector<double>&
table'. This says that the first argument to the function is a vector
of doubles, but furthermore that this vector should not be copied
when it is passed to the function. That is: when the vector 'list'
in the main program is passed to the function, the actual vector
is passed to the function, not a copy of the vector. This means that
if in 'countinrange()' we changed any of the elements of 'table' then we would
be changing the actual elements of 'list' in the main program.
It is the presence of '&' in the definition which causes this behaviour:
it overrides the default argument passing logic which takes copies of all
the arguments before passing them to a function. The copying of arguments
has not bothered us before, because we were only passing single values
to functions; but you can see it would be very wasteful to take a copy
of a 100 element vector. |
10.3 Vector of strings
// ex10-3.cpp - vector of strings
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
void listtable(vector<string>& table,string title)
{
cout << title << endl;
for (int i=0;i<table.size();i++)
cout << table[i] << endl;
}
int main()
{
// create an empty vector of strings
vector<string> list;
// read some lines from the keyboard
cout << "Enter some lines (end in EOF)" << endl;
string sval;
while (!getline(cin,sval).eof())
list.push_back(sval);
// list them back to the screen
listtable(list,"Entered:");
// sort them and list them
sort(list.begin(),list.end());
listtable(list,"Sorted:");
// randomise them and list them
random_shuffle(list.begin(),list.end());
listtable(list,"Randomised:");
return(0);
}
|
ex10-3
Enter some lines (end in EOF)
fred
bert
joe
^Z
Entered:
fred
bert
joe
Sorted:
bert
fred
joe
Randomised:
joe
bert
fred
|
Example ex10-3 introduces vectors of strings and demonstrates two useful
functions which can operate on vectors. The declaration 'vector<string>
list;' declares list to be a vector of strings, but since we have not
specified an initial length for the vector, it contains no elements.
An empty vector is still useful because we can always append to the end
of a vector with the push_back() method. We then input a series of
lines from the keyboard using the 'getline()' function that we have met
before. Here however, we do not want to use a sentinel value to end
the series of lines, but simply to stop when the list of lines is complete:
this is when the list has reached its EOF (End Of File marker). The
EOF is a special character that cannot be stored in a file or in lines
of text and which is used simply to signal the end of some file or some
text. On MS-DOS systems, the EOF character is 'ctrl/Z', i.e. the
character you get if you hold down the Ctrl key simultaneously with the
Z key. On Unix systems, the EOF character is 'ctrl/D'. Thus
the conditional expression '(!getline(cin,sval).eof())' both reads
in a line of text into sval and tests to see if the EOF character was typed;
you should read it as: 'not true that getline() finds EOF' with the
side effect that any text input gets put into sval. For each non-EOF
line, we append it to the list with push_back(). In the example,
we input three lines, so the vector list ends up of size 3 with list[0]="fred";
list[1]="bert"; list[2]="joe";. Once we have those list of strings
we can demonstrate the effect of two useful functions: one which can sort
the order of a vector and one that can randomise the order of a vector.
The function 'sort' (included in the standard component <algorithm>)
will sort any vector - not just strings, using the properties of the '<='
operator for comparisons: i.e. it will rearrange the contents of the vector
such that element [0] <= element[1] <= element[2], etc.
The sort() function takes two arguments indicating which part of the vector
needs sorting. For now we need to sort the whole vector, so we can
use the vector method functions begin() and end() to identify the beginning
and the end of the vector as the range of operation of sort. The
'random_shuffle()' function is clearly the opposite of sort! It changes
the ordering of a vector so that the elements are in no particular order.
You might try to think how the function achieves this without requiring
a temporary list or accidentally duplicating values. This example
has a function 'listtable' which prints the contents of the list so that
we can see the effect of sort() and random_shuffle(). Notice that
the vector is passed with the argument copying logic over-ridden with '&' as
we saw in ex10-2. |
10.4 Exercises
a. Write a program (namelist.cpp) that produces
a list of names sorted by last name from an input which is a list of full
names, as in:
namelist
Catherine Zeta Jones
Fred Bloggs
Khalid El Choukri
^Z
Bloggs, Fred
Choukri, Khalid El
Jones, Catherine Zeta
b. The prime number tester in example ex9-3 is inefficient in that it tries
to divide by all numbers less than the square root of the number to be
tested, rather than just the prime numbers less than the square root of
the number. A more efficient way to generate a list of all prime numbers is
to store the primes in a vector as they are generated then use the list
so
far to test the primality of the next number. Write a program (prime.cpp)
which generates a list of all prime numbers less than 1000 and which builds
a vector of primes to minimise the amount of calculation. |