table of contents introduction.............................................7 the idea of...
TRANSCRIPT
How To Program © Walter Milner 2013 Page 1
How To Programby Dr. Walter Milner
All contents copyright © 2013 Walter William Milner. All rights reserved.
Comments to [email protected] are welcome.
About the authorDr. Milner's first degree was in the Natural Sciences at Cambridge University. Since then he has worked in education teaching science maths and computing, and has postgraduate qualifications in teaching at secondary level and higher education. His PhD was awarded by the University of Birmingham for a thesis concerned with cognitive aspects of novice programmers learning Java.
How To Program © Walter Milner 2013 Page 2
Table of ContentsIntroduction.............................................7
The idea of Programming..........................9
Programming languages are formal languages..............9
A programming language IS a set of grammar rules......9
Java is a general purpose language..............................9
There are only 50 reserved words in Java.....................9
Program text and machine state.................................10
Java is a high level language......................................10
Starting Java..........................................12
Setting up Java..........................................................12
Hello World................................................................12
Variable declarations..................................................13
Types.........................................................................14
Literals.......................................................................14
Type casts..................................................................15
Comments.................................................................15
When it goes wrong....................................................16
Some suggestions for debugging.................................18
Algorithms.............................................20
Recipe steps...............................................................20
Exchange values........................................................20
Where the term comes from........................................22
Algorithms and code structure................23
Algorithm examples................................25
Larger of two values...................................................25
The conditional statement..........................................25
Repetition – while.......................................................27
The for loop................................................................28
break and continue....................................................29
Random numbers.......................................................30
Adding up..................................................................30
Counting....................................................................31
Extreme values..........................................................31
Square root................................................................32
Euclid's Algorithm......................................................33
Arrays....................................................35
Arrays and loops........................................................36
Sequences..................................................................37
Iteration and recursion...........................38
Iteration.....................................................................38
Linear searches..........................................................38
Recursion...................................................................39
Binary search.............................................................43
Equivalence of recursion and iteration........................44
Data structures......................................45
Linked lists................................................................45
How To Program © Walter Milner 2013 Page 3
Defining a linked list in Java......................................45
Prettyprinting a linked list.........................................48
Adding to a list...........................................................49
Searching a list..........................................................50
Deleting a node..........................................................51
Ordered Lists.............................................................52
Selforganising lists....................................................54
Object oriented programming..................55
Packages....................................................................55
Static and nonstatic..................................................56
Time and space complexity.....................58
Introduction...............................................................58
List insertion..............................................................58
Big O notation............................................................59
Insertion in an ordered list.........................................60
Array data structures.............................62
Standard implementation...........................................62
Resizable arrays........................................................63
Access control, wrapper classes and import................63
Strings...................................................67
Unicode......................................................................67
The String class.........................................................67
The Java API..............................................................68
Objects and references...............................................68
String processing.......................................................68
Comparing objects and references..............................69
Sort algorithms......................................71
The sort concept.........................................................71
Bubble sort................................................................72
Bubble sort is stable and in place...............................73
Time complexity of a bubble sort................................74
Quicksort...................................................................74
Stacks...................................................77
Linked list implementation.........................................77
OOP inheritance.........................................................77
Uses of stacks reversing things.................................78
Uses of stacks return address..................................78
Uses of stacks expression evaluation........................80
Uses of stacks programming languages....................81
Time Complexity........................................................81
Exceptions.................................................................81
More on inheritance...................................................82
What is the point of inheritance?................................84
The Object class.........................................................84
Abstract Data Types...............................86
Queues..................................................87
Operations.................................................................87
Implementation Linked List......................................87
How To Program © Walter Milner 2013 Page 4
Inner classes..............................................................89
Implementation – Array..............................................90
Priority queues...........................................................92
Namespaces...............................................................93
Generics.................................................95
A generic linked list....................................................95
The Java Collections framework.................................97
Radix sort..................................................................98
Sets......................................................102
Variadic arguments..................................................102
Iterators..............................................105
Using an index.........................................................105
Making an iterator....................................................105
Interfaces.................................................................106
The Iterator interface................................................107
The Iterable interface................................................109
Trees....................................................110
Terminology.............................................................110
Ordered Trees..........................................................111
Enums.....................................................................112
A generic Tree..........................................................113
The Comparable interface.........................................113
Tree traversals.........................................................114
Tree sort..................................................................116
Breadthfirst traversal..............................................117
Depthfirst traversal.................................................118
Pretty printing a tree................................................119
Balanced trees.........................................................120
Maps....................................................121
Hash tables..............................................................121
Collisions.................................................................123
Deletions..................................................................125
The Collections Framework HashMap.......................126
hashCode() and .equals()..........................................129
Memoization.............................................................131
Graphs.................................................133
Representations.......................................................134
Kruskal's Algorithm..................................................137
Coding Kruskal........................................................137
Graphics Algorithms.............................140
Background.............................................................140
A Swing window.......................................................140
Drawing an image....................................................142
Bitwise operators......................................................143
Bit shifts..................................................................144
A better binary.........................................................144
Bit masks.................................................................145
Pixel bit manipulation..............................................146
Using transparency..................................................148
How To Program © Walter Milner 2013 Page 5
Plotting a pixels........................................................149
Line drawing algorithms...........................................150
An integerarithmetic only line..................................153
Antialiasing.............................................................154
An antialiasing algorithm........................................155
Floodfill....................................................................156
File Structures and algorithms.............159
Reading and writing text files....................................159
Opening and closing files..........................................161
Buffers.....................................................................161
Serial and random access........................................163
A Map in a file..........................................................164
File devices..............................................................166
How Java works...................................168
How Computers work...............................................168
Machine code...........................................................169
Using an assembler and linker.................................169
Hello world on Linux................................................170
Using ASCII..............................................................172
Doing arithmetic......................................................173
High level language programming.............................174
Java bytecode...........................................................175
How bytecode works.................................................176
Abstraction..............................................................177
How To Program © Walter Milner 2013 Page 6
Introduction
The purpose of this text is to teach how to program.
Most programming books are about C or Java or Visual Basic they are about programming languages. As a result, students will often say I know the language, but I don't know how to write programs. This is about how to write programs.
Learning how to program is mostly done by writing programs, so we need to use some programming language. We use Java, because it is very 'expressive', easy to read and write, is opensource, and runs on Windows, Linux, Mac OS X, and many other platforms. But it is not about Java. It is about how to write programs. It is intended for novice programmers, not professionals.
Students often think 'I will learn language X, then have a career as a professional programmer writing X all my life'. Then they ask 'what is the best language to learn to get a job?'.
This is a mistake. Competent programmers know, and use, several languages. For web applications in 2013, that often involves PHP, SQL, JavaScript and html and css (which are markup languages not programming languages). So you
need to know several languages to get a job. The question 'what is the best language' is meaningless. And the idea that a language must be 'current' is also wrong. COBOL was designed in 1959 but Google 'COBOL jobs' to see that people are still recruiting.
But this is not so difficult. All programming languages are fundamentally the same (if they are Turing complete Google it). But they often take different approaches, and learning them deepens your understanding.
There are two key concepts to understand algorithm and data structure.
1. An algorithm is a type of recipe for solving a problem. Some are very simple, some are not. Programming means finding (or inventing) a suitable algorithm, and writing it in a programming language.
2. A data structure is a way of arranging a set of data items.Examples are lists, stacks, queues and trees. Each data structure has a set of algorithms which are appropriate for it.
This is not an encyclopeadia of data structures and algorithms. The idea is to provide an understanding of theseideas, and how they are used to write programs.
How To Program © Walter Milner 2013 Page 7
Most of the code examples here work in Java version 6, with a handful needing Java 7. The compiler will tell you if there is a version issue.
Again – this is not a book about Java, so not all aspects are covered – in particular there is nothing about concurrency. But it will teach you how to program. After that – you just write the code.
Some other good books and links
The Java Programming Language by Arnold, Gosling and Holmes. The book about Java by the people who designed it.
The Really Big Index of tutorials from Oracle
The Java Language Specification. This is a formal specification of what Java is.
Data Structures and Algorithms by Aho, Hopcroft and Ullman. Classic text on the subject. Examples in Pascal. Around $5 secondhand
The Art of Computer Programming by D. Knuth. This man basically invented Computer Science. In 4 volumes. Lots of maths.
How To Program © Walter Milner 2013 Page 8
The idea of Programming
As you work through this, you should try the exercises and write lots of code. It probably won't work, at first. Read the section 'When it goes wrong'.
Programming languages are formal languages
This is in distinction to natural languages like English or
Arabic or Cantonese. Formal languages have a set of syntax rules rules of grammar which can be used to checkif a program is valid or not. Natural languages also have grammar rules. But if you say to a Frenchman 'Bonjour, comment alleznous?' he will not reply 'Syntax error'. He will think that you are not very good at French, and will reply slowly and simply as if talking to an idiot. But in programming, any syntax error will stop your program from running.
A programming language IS a set of grammar rules
Java is not a piece of software, but a grammar. Using the grammar, anyone can create the software needed to write and run Java. There is a 'standard implementation' but youdo not have to use it.
The grammar of Java is published in the Java Language Specification. This is a link to it.
Some languages are proprietary they are owned by commercial companies. Others are developed and agreed bya community process Java is like this. C and C++ are agreed by ISO committees. Visual Basic and C# are proprietary Microsoft languages.
Java is a general purpose language
It is designed to be able to do everything which a programming language can do (technically, it is 'Turing complete'). So long as someone writes the code for it.
There are only 50 reserved words in Java
A reserved word has a special meaning in a Java program.'for', 'if' and 'while' are some reserved words. The programmer can use other words, as names for variables (and methods and classes and other stuff), and these are called identifiers. But you cannot name a variable with a reserved word.
So how can you write programs that will do anything, when you only have 50 words, when English has around 600,000 words in use? That's what this text is about. Read it and you will know.
How To Program © Walter Milner 2013 Page 9
Program text and machine state
Here is part of a program the text of a Java program:
int x,y,z;x=2;y=3;x=4;z=x+y;System.out.println(z);
It prints out 7. Think about why.
The program text has 6 lines, each of which is an instruction to the computer. The instructions are carried out in sequence, from the top down. The first line says that x y and z are ints – whole numbers. The next 4 instructions change the values of variables in other words, they changethe state of the machine. It is likely that this will mean that subsequent instructions will have a different effect.
That means we must follow down the program text, and track how that will alter variable values like this:
State
Instruction x y z
x=2 2
y=3 2 3
x=4 4 3
z=x+y 4 3 7
Java is a high level language
A low level language is about the computer and how it works. Low level language programs are written in machine code or assembler. They make references to the hardware details of the computer so they are specific to a particular processor chip and OS. There are some tutorial examples later.
High level languages focus on the the problem to be solved, and express the solution in the program as clearly as the language and the programmer can. They do not refer to detials of the particular computer being used. This has two advantages. First we do not need to learn all the details of a particular processor chip and the OS. Second, our program
How To Program © Walter Milner 2013 Page 10
will run on any machine which has the appropriate software. In our case, that is a Java runtime. So for example the Java programs in this text will run identically on a Windows machine or Linux or iOS.
High level languages, to varying degrees, can use the idea ofabstraction. See later.
How To Program © Walter Milner 2013 Page 11
Starting Java
Setting up Java
To run Java programs, you need to install some software called the Java Runtime Environment, or JRE.
To write Java, you need the JRE, and the Java Software Development Kit, or JDK.
Once you have these, you can write Java programs just using a simple text editor, such as Notepad in Windows.
But instead you can use an IDE such as NetBeans or Eclipse. Most people argue that it is best to start with a texteditor, then move on to an IDE after a little experience.
All this is free software.
The download and install instructions vary between different operating systems and their different versions. Google and follow the one for your platform.
Hello World
There is a tradition that the first program to write in a new language is one that simply outputs 'Hello world!'. Here we go:
public class Test {
public static void main(String args[]) {
System.out.println("Hello world!");
}}
1. Type (or better, copy and paste) this into a text editor, like Notepad on Windows or gedit on Ubuntu. Save it with the filename Test.java ( not test.java or anything else).
2. At the command line, in the folder where you saved it, compile it by typing
javac Test.java
3. Launch the application from the command line by:
java Test
For example, on a Linux platform:
How To Program © Walter Milner 2013 Page 12
Exercise
1. Change the letters in “Hello world” and compile and run the program again
2. As well as normal characters, we can output some 'control characters'. One of these is n, for new line. But we need to 'escape' this, as \n, to show we do not mean normal n. So change it to
“Hello\nworld\n” and see what happens.
Variable declarations
A variable is some data which can change, and which has aname. At any time, the value of the data is held in memory, and the name lets the system work out where it is held – its address in memory. We can assign a value to a variable, which stores a value in memory at the correct place.
For example
x = 3;
This is an assignment statement, which stores a 3 at
location x. The ; at the end makes it a statement.
But in Java, before we can use a variable, it must be declared. This means we tell the compiler what type the data is. For example:
int x;
This tells the compiler that x is an int – an integer, a whole number.
Here is a little program which goes on to output the value:
How To Program © Walter Milner 2013 Page 13
public class Test {
public static void main(String args[]) {int x;x = 3;System.out.println(x);
}}
Try this out.
Types
A 'type' is a kind of data.
In Java there are two sorts of types, primitives and reference types. We will look at references later.
An int is an integer – a whole number. We will use ints a lot, since they are nice and simple. An int can be positive ornegative, up to about 2 thousand million
There is also a long, which is even bigger than an int.
For numbers with decimals, we use a double. Like
double d;d=3.1415926;
An alternative to double is float. Doubles use 8 bytes of memory, but floats only use 4 bytes, with smaller range andaccuracy.
For single characters, we use char. Like
char c;c = 'A';
Note that is 'A' and not “A”. Double quotes enclose strings, which are different.
There is also a true or false type, called a boolean – such as
boolean finished;finished=false;
Literals
A literal is a value in source code. For example in
x = 2 + 3;
there are two literals, 2 and 3.
How To Program © Walter Milner 2013 Page 14
Literals have type, as well as variables. The type of 2 is an int. If you want a long, you say 2L.
Similarly 2.0 is type double, in 8 bytes. If you want a 4 byte float, say 2.0f.
'X' is a char literal.
Type casts
Sometimes we need to change data from one type to another. This is called a cast or typecast.
If you change from a smaller to a bigger type (float to doubleor int to long), this is no problem. For example
int x = 2;long v = x;
The x is held in 4 bytes. This is stored in the 8 bytes of the long, with the other 4 bytes filled with 0s. No problem. This is called an implicit typecast.
But casting to a smaller type is an issue. For example:
If we put the 8 bytes of a long into the 4 bytes of an int – weare likely to lose some bits. If we still want to do it, we need an explicit type cast, which in this case is:
long v = 2L;int x = (int) v;
In fact 2 only requires two bits ( 2 = binary 10 ) so we are not really losing any significant bits.
Comments
In program text we can put comments, which are pieces of text which the compiler ignores. Comments are used to put in date, author, version number, and notes of explanation for things which are not obvious.
A line comment starts with a // and extends to the end of the line:
// Author: Walter Milner
A block comment starts /* and ends */ and can cover several lines
How To Program © Walter Milner 2013 Page 15
/* Author: Walter Milner Date: 13 September 2013 Purpose: Illustrate block comments */
When it goes wrong
Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?
• Brian Kernighan, "The Elements of Programming Style", 2nd edition, chapter 2
This is probably the most important part of this text.
When you start to write Java, most of it will be wrong. Lateron, only some of it will be wrong.
Two points when getting started:
1. Your filename MUST be named the same as your class name, with a .java extension, exactly, including capitalisation.
So
public class SomeName{..}
must be saved as SomeName.java – exactly, and nothing else.
2. Your code structure must be (to start with)
public class Test {
public static void main(String args[]) {.. your code goes here..}}
You don't have to name the class Test – but you may as well.
Once you've checked those two – here is rule 3:
3. Read and understand the error message
The error message tells you two things – what the error was, and where it is (what line number in your code). Error messages are hard to understand at first, and there is a temptation to ignore them as meaningless. But they are full of meaning, and you must try to understand them – they correspond to your knowledge of Java.
This applies both to compile time syntax errors and runtimeexceptions – explained next.
4. Syntax errors are easy
How To Program © Walter Milner 2013 Page 16
when you have some experience. For example
This is in Eclipse, but NetBeans does something similar. There is a red wavy line under 'x' – so the problem is to do with 'x'. It's at line 8 – so this is where the problem is. The error message is 'x cannot be resolved to a variable'. The compiler tracks the variables it has seen – and it cannot match 'x' with one of them. One possibiity is that you forgot to declare x as a local variable:
int x;x=4;
Another example:
Here the red wavy line is under '4', but the '4' is not the problem. The error message gives a pretty big clue – 'insert ;to complete the statement' So you forget the trailing ';'.
Third example:
The '4.5' has the red wavy line, so the problem is about this.The error message is 'cannot convert from double to int'. So this is to do with data types, double and int. A little thoughtshould show the problem – you've declared x to be an int,
How To Program © Walter Milner 2013 Page 17
which is a whole number, then tried to assign a value whichis not a whole number. Depending on what 'x' represents, either declare it as a double, or asssign a whole number to it.
5. Runtime exceptions
Sometimes code has no syntax errors, but when it runs, a 'runtime exception is thrown'. For example
int[] data=new int[5];data[5]=7;
When this runs, we get:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5
at Test.main(Test.java:18)
We have not yet met arrays – no matter.
The exception name is ArrayIndexOutOfBoundsException. What does that mean? It must be about an array. What array? It occurs 'at Test.main(Test.java:18) That means at line 18. Line 18 is data[5]=7; so the array in question is 'data'. The problem is 'index out of bounds', and it says 5, in red. So the problem is the 5 in data[5]. It is 'out of bounds'. Why?
The array data has been declared to have 5 elements, and these are numbered from 0, so allowed index values are 0 to4 inclusive. There is no element number 5, so we cannot store something in it. We probably need to change that to data[4].
If the exception name does not make sense to you – just Google it.
6 Incorrect output
These are the only kinds of bugs which are difficult to fix, with some experience. The program runs, with no error messages or exceptions, but it does not do what it is supposed to. One example is in a graphics context, and you get a blank screen. Tricky.
Some suggestions for debugging
Write code in the smallest possible amounts between tests. In other words, do not write hundreds of lines of code, then check it works. If you do, the problem could be anywhere inthose hundreds of lines. Instead, write ten lines and check. If that does not work, you know the problem lies somewherein those ten lines.
How To Program © Walter Milner 2013 Page 18
Put System.out.println values at appropriate points. This tells you at least that code at that point is executing. Outputting values lets you check if variables have the values which you think they have. When you find one whichdoes not, you must work backwards. Where was it given that value? Why is it not what you thought?
Try commenting out code. You may have a block of code which is suspect. You might delete it. But if in fact it is OK, you have to write it again. So just temporarily put /* before and */ after. If it checks out OK, remove the comments – and look somewhere else.
Think a lot.
Work through the code on paper, tracing out variable values. Sometimes this is feasible. If you have thousands of loops, its not.
As a last resort, use a debugger. This is software which offers options like run to cursor, single step, jump over method calls, and outputting watch variables and expressions. This is a last resort since setting up a debugger often takes some time.
Software testing is a specialist area. JUnit is a formal way of doing this. assert is a useful tool. But these notes are justintended to help if you have a problem doing the exercises in this text.
How To Program © Walter Milner 2013 Page 19
Algorithms
Recipe steps
An algorithm is a recipe for solving a problem. The recipe is a sequence of steps. Each step is an instruction to the computer to do something. A program is an algorithm expressed in a programming language.
What kind of steps or instructions can we have? Only six categories are allowed:
1. Input data. Algorithms always have some input data to work on.
2. Output data the result of the algorithm. This might be a single number, or a whole set of related data.
3. Assign the value of an expression to a variable. The expression must be capable of being expressed in terms of basic arithmetic. So
x = 3+4
is OK. So is
y=sin(2) since a sine function can be calculated by basic arithmetic as accurately as we want.
4. Test if some condition is true. Like
if x>5...
The test can only be something we can work out using basicarithmetic but we can use a very large amount of arithmetic if we need to.
5. Repeat other statements. This might be to to repeat one or more steps a given number of times, or until some condition is true. This is called iteration. So for example we can say
repeat 10 times
6. Another algorithm. One algorithm can make use of another algorithm. This is a useful item of problemsolving we can split one difficult problem into several smaller problems, and solve each one with a simpler algorithm
So we can have just 6 kinds of steps input, output, assignment, iteration, conditional and other algorithms.
Exchange values
Suppose we have two variables, and we want to exchange their values:
x=1
y=2
How To Program © Walter Milner 2013 Page 20
then after the exchange, y would be 1 and x 2. How to do this?
The obvious way isx=y
y=x
but this does not work. If we track the values:
x=1 x is 1
y=2 x is 1 y is 2x=y x is 2 y is 2y=x x is 2 y is 2
The problem is that the assignment x=y stored the value of y in x, so the initial value of x was lost.
We need an additional storage location to hold the initial value of x, which we can then write into y:
public class Test {
public static void main(String args[]) {int x,y; x=1; y=2;int temp; temp=x; x=y; y=temp; System.out.println(x+" : "+y); // 2 : 1
}}
We have found an algorithm to exchange the values of two variables. The actual algorithm is the 3 assignments:
temp=x
x=y
y=temp
Exercise
1. Try this out in a Java program to check it works.
2. Suppose we have 3 variables, w, x and y, and we want to rotate their values, w to x, x to y, and y to w. So for example:
How To Program © Walter Milner 2013 Page 21
Before After
w 4 9
x 7 4
y 9 7
Devise an algorithm and write a Java program to do this. Test it works.
3. Can you rotate 4 values, w, x, y and z?
Where the term comes from
The word derives from the Latin version of Mu ammad ibn ḥMūsā alKhwārizmī, an eighth century Persian mathematician who, amongst other things, set out the beginnings of algebra in his text “AlKitāb almukhta ar fī ṣ
isāb aljabr walmuqābala” ('The Compendious Book on ḥCalculation by Completion and Balancing').
How To Program © Walter Milner 2013 Page 22
Algorithms and code structure
We often want to use an algorithm several times in a program. We can do this by just typing out the code for the algorithm, repeatedly. But this is is not a good idea. It wastes memory, since we have multiple copies of the same code. It risks bugs, if the code copies are not identical. And it hides the fact that we are doing the same thing, unless welook really closely and see that we have the same code several times.
A better solution is to put the code for the algorithm in a separate area, and just use it whenever we want to. We are talking about giving some structure to the program code – dividing it into several units, instead of just having one block of code.
Other advantages of splitting code into small units include being able to test those units separately; the fact that it enables a programming team to work together, with each person working on a separate unit; and it means it might bepossible to reuse those units in other projects.
All programming languages allow this. The names of the units vary – subroutines, modules, functions, procedures and subprograms are some of them.
In Java, one way to do this is using something called a method.
For example, we might need to find the average of three numbers, many times. We just need to add them up and divide by three:
public class Test {
static double average(double x, double y, double z) {double result;result = (x + y + z) / 3.0;return result;}
public static void main(String args[]) {double a, b, c;a = 1.0;b = 3.0;c = 4.0;double av;av = average(a, b, c);System.out.println(av);}}
Our code is now in two units. One starts 'public static void main..' and the other unit starts 'static double average..'. The purpose of 'main' is to be the point where the application starts. The purpose of 'average' is to find the average of three numbers.
The definition of the method starts as 'static double average..' and end with the 'return'.
The method is used (invoked) by av = average(a,b,c)
How To Program © Walter Milner 2013 Page 23
Exercise
Before we go through this in detail – try to modify this so it finds the average of four numbers.
The method definition header is
static double average(double x, double y, double z) {
We explain 'static' later.
The first 'double' is there because this is the return type of the method. The method calculates a value, and returns it, and we must say it this point what type is the returned value. It could have been int – but an average in general willnot be an integer, so we say double. Some methods just do something (for example, deleting a file), and do not return anything. For these, the return type is void.
After the name of the method, average, we have round brackets ( ) enclosing a list of formal parameters. The idea here is that sometimes we need to supply some data into the method, and this is done through these parameters – sometimes called arguments. We need to give the data type of each parameter, and a name for it.
The body of the method definition is in a block, enclosed { and }. Inside the block we declare a local variable, result, and use it to hold the calculated value. At the end of the method, we return this value.
The method is invoked in
av = average(a, b, c);
Here we have 3 actual parameters, a b and c. The values ofthe actual parameters are copied to the formal parameters, in order – so x gets the value of a, y gets b and z gets c. The returned value is assigned to the variable 'av'.
Exercise
Write a method called addUp which takes three integer parameters and returns their total. In main, test it.
How To Program © Walter Milner 2013 Page 24
Algorithm examples
Larger of two values
How can we find the larger of two values? We can use a conditional. If the first is larger than the second, the first is larger otherwise the second is larger.
We can use an if to write a method which finds the larger oftwo values:
public class Test {
static int larger(int a, int b) {int bigger;if (a > b)bigger = a;
elsebigger = b;
return bigger;}
public static void main(String args[]) {int x, y;x = 3;y = 4;System.out.println(larger(x, y));}}
The conditional statement
A conditional, or 'if' statement, enables a program to execute different pieces of code, depending on data values. The idea is
if (something which is true or false) {
.. things to do if it is true
}
else
{
.. thing to do if it is false
}
The 'else' part is optional – so you then have
if (something which is true or false) {
.. things to do if it is true
}
and if it is false, you simply go on to the next statement.
How To Program © Walter Milner 2013 Page 25
The curly brackets { and } form a block statement or compound statement. This is a sequence of statements enclosed by { and }, and executed in the sequence. If you only want to do one thing, you can replace the block by one statement, like
if (a > b)result = a;else result = b;
This is wrong
if (a > b);result = a;
Do NOT put a semicolon at that point.
The 'something which is true or false' is a boolean expression. Here are some examples, using constants:
Expression Meaning Value Notes
4>3 Greater than True
4 < 3 Less than False
5 == 5 Equals True NOT =
3 >= 3 Greater thanor equal to
True
2 <= 3 Less than or True
equal to
6 != (2+4) Not equal to False
!(4>3) Not greater than
False
(2>1) && (5>4)
Logical AND True Like both 2>1 and 5>4
(1>3) || (2>1)
Logical OR True Like either 1>3 or 2>1
&& and || are not the same as & and |. See later.
These can be as complicated as you like. For example:
if ( x>3 || (y==7) && !(z=4) )...
but the more complicated, the more likely it will be incorrect.
Use brackets to ensure things are done in the order you want.
The statements in a block can be any type, including an if, so you could say:
How To Program © Walter Milner 2013 Page 26
if (..) { .. if (..) { .. } .. }
The indentation of the code in an if is important – you will go wrong if you do not do it. Most IDEs can correct indentation for you.
Exercise
1. Write a method which returns the largest of three values.
2. Test it with the following
input result
1, 2, 3 3
7,9,2 9
9, 2,1 9
1,2,3 1
4,7,7 7
Repetition – while
One basic tool in an algorithm is to repeat some steps. If weneed to do something three times, we can just type out that statement three times. But if we need to do it 1000 times, that is impractical. Languages include control constructs which allow blocks of code to be repeated. These are known as loops.
One such construct in Java is the while loop:
public class Test {
public static void main(String args[]) {int x = 0;while (x < 5) {System.out.println(x);
x++;}
}}
which outputs 0 1 2 3 4. x++; means to increment x by 1.
Exercise
1. Alter this so it outputs 1 2 3 4
2. Then change it so it outputs 1 2 3 4 5
3. Now change it so it outputs 2 4 6 8 10
How To Program © Walter Milner 2013 Page 27
A while loop in Java is like this:
.. some initialisation..while (.. something true or false.. ){.. some code to repeat..}
The 'something true or false.. is an expression which gives atrue or false value, as in an 'if' statement. That can use == or < or && and so on. If it is false the first time around, the loop body is never executed. If there is just one statement torepeat, that can replace the { block }.
There is a small variation on a while loop, which is a do.. while..:
public class Test {
public static void main(String args[]) {int x = 0;do {System.out.println(x);x++;
} while (x < 5);}}
This checks the condition at the end of the loop body, rather than at the start. This means the loop body will always execute at least once.
Exercise
Write while loops to output
1. 0 to 100
2. 0 to 99
3. 20 down to 10
4. 20 down to 10 in steps of 2 ( 20 18 16.. 10 )
5. Can you output 1 to 5, 50 times?
The for loop
As well as a loop body, loops have 3 aspects:
1. Initialisation
2. When to continue, or stop
3. What to change every time around
A for loop has these three parts.
For example:
How To Program © Walter Milner 2013 Page 28
public class Test {
public static void main(String args[]) {for (int x = 0; x < 5; x++) {System.out.println(x);}
}}
After the for ( there are 3 parts, with ; between them:
1. int x=0 declares and initialises x to 0
2. x<5 means repeat so long as x<5
3. x++ means increases x by 1 after each loop
so it outputs 0 to 4.
The 3 parts of the for header can be any kind of statement you want. This makes the for statement very flexible and is very often used.
Exercise
Write for loops to output :
1. 0 to 100
2. 0 to 99
3. 20 down to 10
4. 20 down to 10 in steps of 2 ( 20 18 16.. 10 )
5. Can you output 1 to 5, 50 times?
break and continue
These two statements are useful to modify the behaviour of loops.
break; breaks out of a loop. It is usually used with an if. Forexample:
public class Test {
public static void main(String args[]) {for (int x = 0; x < 6; x++) {System.out.println(x);if (x==4) break;}
}}
Without the break, this would output 0 to 5. With the break, it stops at 4.
continue is similar, but just skips the rest of the loop body on that iteration, instead of breaking out of the loop completely. For example:
How To Program © Walter Milner 2013 Page 29
public class Test {
public static void main(String args[]) {for (int x = 0; x < 11; x++) {if (x%2==0) continue;System.out.println(x);}
}}
This loops for x=0,1,2...10. x%2 is the remainder when x is divided by 2, so x%2==0 if x is even. Because of the if, it skips the print if x is even, and only outputs 1 3 5 7 9.
Random numbers
It is often useful to be able to produce random numbers in a program.
It is theoretically impossible to generate truly random numbers by using a normal computer. The numbers generated must eventually repeat in a sequence. But it is not difficult to make that sequence very long, and we can make pseudorandom numbers.
Java has a collection of mathematical items in a class named Math. One of these is a method named random, which returns a double in the range 0 to 0.99999:
public class Test {
public static void main(String args[]) {for (int x = 0; x < 11; x++) { // do it 10 timesdouble d = Math.random(); // d is random in range 0 >= d < 1int i=(int)(d*100)+1; // d*100 is in range 0 to 99.99999..// (int).. is a type cast, changing a double to int// (int)(d*100) is in range 0 to 99// (int)(d*100) + 1 is in range 1 to 100System.out.println(i);}
}}
This will output 10 random numbers in the range 1 to 100.
Adding up
How do you add things up? You start with zero, and every time you get another value, you add it into the running total. For example, to add up 10 random numbers:
double total = 0.0;for (int x = 0; x < 11; x++) {total = total + Math.random();}System.out.println(total);
We have missed out class and main, since you've hopefully got the idea now.
total = total + Math.random();
is a very common kind of action, and can be written:
How To Program © Walter Milner 2013 Page 30
total += Math.random();
Exercise
Add up a sequence of random integers, each in the range 0 to 10. Stop and output the total as soon as it exceeds 100.
Counting
How do you count things? You start from 0, and every time you get something, you add 1. That's it.
Suppose we want to process a sequence of 100 numbers, and count how many are over 0.5.
int count = 0;for (int i = 0; i < 100; i++) {double number = Math.random();if (number > 0.5)count++;
}System.out.println(count);
Exercise
Modify this so it generates 100 random integers in the range 1 to 10, and counts the even numbers ( if x is even, x%2 == 0. Recall that % gives you the remainder ).
Extreme values
How do we find the largest value in a set of data?
One way is to use the idea of 'the biggest so far'. We go through the data, and when we find one larger than the biggest so far, we change the biggest so far to be this one. We can initialise the biggest so far to be the first one:
double biggestSoFar=Math.random();for (int i = 1; i < 100; i++) {double number = Math.random();if (number > biggestSoFar)biggestSoFar=number;
}System.out.println(biggestSoFar);
Exercise
Modify this so that it also outputs the smallest.
How To Program © Walter Milner 2013 Page 31
Square root
Suppose we want to work out the square root of a number in a program. Most languages have a builtin maths library which could do that, and Java does. But how is it programmed? How do you actually calculate a square root?
Here is one way. Suppose we want the square root of X (say,10). Start with a guess, calling it G (say 5).
Now if G is too big (which it is), then X/G (=2) is too small.
If G is too small (say 1) then X/G is too big (=10)
Now if one is too big and the other too small, then their average should be better than both.
So have a new guess G = (G+X/G) / 2
And repeat until we are close enough.
Suppose we try this out, to find √10, with a first guess of 5. See how the average becomes the new G on the next row:
G X/GAverage of G and X/G Average squared
5.000000000 2.0000000000000 3.500000000000 12.250000000000
3.5000000000 2.857142857142860 3.178571428571 10.103316326530
3.1785714285 3.146067415730340 3.162319422150 10.000264127712
3.1623194221 3.162235898737390 3.162277660444 10.000000001744
We never get exactly the correct answer. But we can get very close. When to stop? We would like G * G = X, but it isvery slightly more. We could stop when G2X is less than some very small amount, say 106.
public class Test {
static double squareRoot(double x){final double ACCURACY=1e-6;double guess = x/2.0;boolean finished=false;while (!finished){guess=(guess+x/guess)/2.0;double temp=guess*guess;if ((temp-x)<ACCURACY)finished=true;
}return guess;}
public static void main(String args[]) {double v=10.0;System.out.println("v = "+v+" root="+squareRoot(v)+ " which squared is "+squareRoot(v)*(squareRoot(v)));}}
In the squareRoot function, we declare ACCURACY to be final – that is, a constant, and we write it in capitals, as a reminder.
The output is
v = 10.0 root=3.1622776604441363 which squared is 10.00000000174404
How To Program © Walter Milner 2013 Page 32
which is pretty close.
Exercise
1. The square root function has a loop. How many times does it repeat? Modify the program to count, and output, the number of times it loops. Before the squareRoot method, declare a variable
static int counter=0;
Increment counter in the loop, and print it out in main()
2. The algorithm is slower (takes more steps) for larger numbers. Why? Fix it, while keeping the same basic algorithm.
3. Google ways of calculating square roots. Program one youlike.
Euclid's Algorithm
This is another example, which might be called algorithm number 1 – because it is so old. The purpose of this is to find the greatest common divisor (gcd) of two integers. For example, the gcd of 25 and 15 is 5 this is the largest integer which will divide both 25 and 15 without a remainder. It is named after Euclid, the ancient Greek mathematician who first described it.
To find the gcd of two positive integers a and b:
1. If a is less than b, exchange them
2. Work out the difference r = ab
3. If r is 0, we have finished, and the gcd is b
4. Otherwise replace a with b, b with r, and repeat from step 1
Here is Euclid's algorithm coded in Java:
How To Program © Walter Milner 2013 Page 33
static int gcd(int a, int b) {int temp, difference;boolean finished = false;while (!finished) {if (a < b) {temp = a;a = b;b = temp;}
difference = a - b;if (difference == 0)finished = true;
else {a = b;b = difference;}
}return b;}
Notice how we have chosen variable names to make this as clear and easy to understand as possible.
This uses it:
public static void main(String[] args) {int r = gcd(210, 45);System.out.println(r);}
Exercise
Try this out for different values of a and b
Why does it work?
• If a number divides a and b, it also divides ab ( Suppose the divisor is d, then a=n.d and b = m.d for some integers n and m, so ab = n.d m.d = d.(nm) so d divides ab ). So the gcd of a and b is also the gcd of b and ab
• When we replace a and b with b and ab, the numbers are getting smaller, and one must eventually reach zero (when a=b). When a=b, then b (and a) is the gcd
The usual form of Euclid's algorithm is, instead of replacingthe smaller by the difference, to replace it with the remainder when a is divided by b. For example, suppose a =27 and b=5. Then we make a=5 and b=2. This is simply quicker than repeatedly subtracting 275=22, 225=17, 175=12, 125=7, 75=2.
How To Program © Walter Milner 2013 Page 34
ArraysThe variables we have seen up to now have all contained a single data value. But there are many situations where we need to handle a set of values. Examples include
• The cheques issued on a bank account
• The pixels across an image
• The employees in a workforce
• The planes in an air traffic control system
We need a structure to hold a set of values. An array is one possibility. Most programming languages offer arrays. Their characteristic features are
• They have a set of values, or elements
• The elements are usually the same type
• The number of elements is fixed at compiletime (when the program is written) and cannot change at runtime
• An element can be accessed using an index. In other words, we can get (or change) the 3rd element, or the 9th, or the 2nd. This is the special defining feature of anarray.
The first element in a Java array is at index zero.
In this example, we declare an array, put some values in, change some, and output some:
int[] numbers= new int[5]; // get boxes to hold 5 intsnumbers[0]=7; // put 7 in the first boxnumbers[1]=3; // put 3 in the 2nd boxnumbers[2]=5; // put 5 in the 3rdnumbers[3]=7; // put 7 in 4thnumbers[0]=8; // put 8 in the first, over-writing the 7numbers[1]++; // increment the 2nd boxSystem.out.println("At index 0: "+numbers[0]);System.out.println("At index 1: "+numbers[1]);System.out.println("At index 4: "+numbers[4]);System.out.println("At index 0: "+numbers[5]);
which outputs
At index 0: 8
At index 1: 4
At index 4: 0
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5
at Test.main(Test.java:29)
At index 0, we put in 7, then overwrote that with a 8, so weget 8 out. At index 1, we put in 3, then incremented it, so we get 4 out. But what about indexes 4 and 5?
We never assigned a value to numbers[4]. So we get the default value of an int, which is zero.
How To Program © Walter Milner 2013 Page 35
For numbers[5] – it does not exist. The array has 5 elements, but these are indexed 0 to 4. Java checks this at runtime, and if we try to access a nonexistent element, weget an exception as shown here.
Arrays and loops
Arrays typically have a very large number of elements, and it is very common to use a loop to deal with them.
For example, this fills an array with 100 random numbers:
final int ARRAY_SIZE = 100;double[] data = new double[ARRAY_SIZE]; // get boxes to hold 100 doublesfor (int index = 0; index < ARRAY_SIZE; index++)data[index] = Math.random();
and this would print them out:
for (int index = 0; index < ARRAY_SIZE; index++)System.out.println(data[index]);
and this would add them up:
for (int index = 0; index < ARRAY_SIZE; index++)total+=data[index];
In this code, we could have used 100 in place of ARRAY_SIZE. So why use ARRAY_SIZE? It is to avoid magicnumbers. The main point is readability. Someone reading
for (int index = 0; index < 100; index++)
would wonder why the 100 was there. But if they see
for (int index = 0; index < ARRAY_SIZE; index++)
it is obvious that the end of the loop is so as to match the length of the array.
The other point is maintainability. If we want to alter this code so that it uses an array of 10000 elements, we just change 1 line:
final int ARRAY_SIZE=10000;
If we had used 100, we would have had to search through all the code and change 100 into 10000, after checking the 100 referred to the size of the array.
Why say final? So that if we go mad and say later
ARRAY_SIZE=28;
the compiler will point out the error to us.
Why say ARRAY_SIZE not arraySize? To remind ourselves that it is constant.
Exercise
Modify this code so the array is just 10 elements long. Then find
How To Program © Walter Milner 2013 Page 36
1. The largest element in the array
2. The smallest
3. The average
For each of these, print out the vales so you can check it is correct.
Sequences
Many simple algorithms are associated with iterating through an array.
For example, suppose we want to output the length of each repeating sequence in some data, and the data value. So if the data is
7,7,2,9,9,9,3,3,6
we should get 7 2, 2 1, 9 3, 3 2, 6 1
How to do that?
We can track the current data (call it val), and the sequencelength (call it length). We initialise val as the first element, and length to be 1. When we check another value, if it is thesame as val, just increment the length. Otherwise, output val and length, then reset length to 1 and val to be this new value.
Here's the code – which also shows how to initialise an array with some constants:
final int ARRAY_SIZE = 9;int data[]={7,7,2,9,9,9,3,3,6};int val=data[0];int length=1;for (int index = 1; index < ARRAY_SIZE; index++)if (data[index]==val) length++; else{ System.out.println(val+" with length "+ length);
val=data[index]; length=1;}
and the output is:
7 with length 2
2 with length 1
9 with length 3
3 with length 2
Exercise
1. Problem is, that's wrong. Debug it.
2. Alter the code so that it outputs just the longest sequence. With this data, that should be 9.
How To Program © Walter Milner 2013 Page 37
Iteration and recursion
These are two key aspects of algorithms. Iteration is repeating a loop. A recursive process calls itself.
Iteration
All nontrivial algorithms involve iteration. Some examples follow.
Find triangle numbers
The triangle numbers are 1, 3, 6, 10, 15.... The nth. trianglenumber is 1+2+3...+n. They are socalled because writing them as dots gives a triangle shape.
Title : Find the nth triangle number
Purpose : Find a triangle number
Input : A positive number n
Output : none
Return : The nth triangle number
Steps:
A. total ← 0
B. for i in 1 to n
a. total ←total + i
C. return total
In Java:
public class Test {
static int triangle(int n){int total=0;for (int index=0; index<n+1; index++){total=total+index;}
return total;}
public static void main(String args[]) {for (int x=1; x<6; x++)System.out.println(x+" : "+triangle(x));
}}
This is iteration, because of the index loop in the method definition.
Exercise
A triangle number is the sum of an arithmetic progression. Use the formula for this (Google if needed) to write a versionof this which does not use iteration.
Linear searches
Suppose we have an array:
How To Program © Walter Milner 2013 Page 38
int[] data = { 78, 24, 19, 11, 220, 38, 23, 44, 68, 4, 8 };
and our task is to search for a data item (say 23) and find its location – or possibly find the array does not contain that value.
This is an example of a general task, that of searching a data structure for a value.
The obvious solution is to start at the beginning and look through the array, checking if any value matches:
int lookFor=23;boolean found=false;int location=-1;while (!found && location!=data.length){location++;if (data[location]==lookFor) found=true;}if (found) System.out.println(location);else System.out.println("Not present");
This is an iterative method.
How fast is it? If we are lucky, its the first item in the array. If we are unlucky, it is the last. On average, it will be halfway down.
So if there are n items to search, on average we will take n/2 steps.
Can you think of a faster way?
Recursion
A recursive procedure calls itself. Somewhere in the code of a recursive method, you will find a call to that method.
Tower of Hanoi
In this game, we have three poles, with a set of rings on one. The task is to move all the rings onto another pole, with the rules that
1. You can only move one ring at a time.
2. You can only put a smaller ring on a bigger ring
This is pretty tricky. How about this algorithm sketch:
to move n rings from p1 to p2
if n is 1, just move it and finish
work out which is the third pole p3
move n1 p1 to p3
move 1 from p1 to p2
move n1 from p3 to p2
How To Program © Walter Milner 2013 Page 39
The idea is we move all but 1 onto the third pole p3, then move the single ring p1 to p2, then move the rest p3 to p2.
We can move a single ring directly.
How do we move n1? By using this method. And that will use this method again, and so on, but with decreasing n, which will get down to 1, and we can do that directly.
Suppose the poles are labelled A B and C. Here is a version in Java:
static void move(int n, char p1, char p3) {// move n rings from pole p1 to pole p3// easiest case - just 1 to moveif (n == 1) {System.out.println("Move "+p1+" to "+p3);return; // and do nothing more}
char p2='A'; // the other pole. Is it A?if (p1 != 'A' && p3 != 'A')p2 = 'A';
if (p1 != 'B' && p3 != 'B') // or Bp2 = 'B';
if (p1 != 'C' && p3 != 'C') // or Cp2 = 'C';
move(n - 1, p1, p2); // move all but 1, using this method, onto p2move(1, p1, p3); // move the bottom onemove(n - 1, p2, p3); // move the rest from p2 to p3}
}
To test it, try the simplest case:
public static void main(String args[]) {move(1, 'A', 'C');}
outputs
Move A to C
Using 2 rings
move(2, 'A', 'C');
to get
Move A to B
Move A to C
How To Program © Walter Milner 2013 Page 40
Move B to C
Other cases are very difficult to check without the game in front of you:
move(3, 'A', 'C');
Move A to C
Move A to B
Move C to B
Move A to C
Move B to A
Move B to C
Move A to C
If the method calls itself, what stops it going on forever? Because
a call with n leads to 2 calls with n1, so n reduces in the calls, and
the call with n=1 does not recurse, so it will always stop eventually.
Fibonacci sequence
The Tower of Hanoi is a standard example to show recursion. Another is the Fibonacci sequence:
0,1,1,2,3,5,8...
This starts 0,1. Then the next number is the last two added together.
Here is the algorithm:
Title : Fibonacci Purpose : Find the nth Fibonacci numberInput : A non-negative integer nOutput : noneReturn : The nth Fibonacci numberSteps:
A. if n = 0 return 0
B. if n = 1 return 1
C. return fibonacci(n1) + fibonacci(n2)
In Java:
static int fib(int n){if (n==0) return 0;if (n==1) return 1;return (fib(n-1)+fib(n-2));}
To test:
public static void main(String args[]) {for (int x=0; x<10; x++)System.out.println(x+" "+fib(x));
}
output:
0 0
How To Program © Walter Milner 2013 Page 41
1 1
2 1
3 2
4 3
5 5
6 8
7 13
8 21
9 34
We can also have an iterative version of this, with no recursion:
static int fib(int n) {if (n==0) return 0;if (n==1) return 1;int current = 0; // the nth Fib, number to be worked outint before1 = 1; // the one beforeint before2 = 0; // two beforefor (int counter = 0; counter < n-1; counter++) {current = before1 + before2;before2 = before1;before1 = current;}return current;}
Exercise
Which is faster? The recursive method has fewer lines – so is it faster? Modify the recursive version so it counts the steps:
How To Program © Walter Milner 2013 Page 42
public class Test {static int count=0;
static int fib(int n){count++;if (n==0) return 0;if (n==1) return 1;return (fib(n-1)+fib(n-2));}
public static void main(String args[]) {System.out.println(fib(5)+" "+count);
}}
and do the similar for the iterative version (inside the loop). Found out how many steps are involved to calculate the 20th Fibonacci number iteratively and recursively. Explain the difference.
Binary search
Suppose we are searching an array, and the array is sorted:
int[] data = { 4,7,9,10,13,14,18,24,32,45,67,89,98 };
One method would be
• Look at the middle item
• If it matches, finish
• If it is greater, do this again with the lower half
• else do it with the upper half
This is a recursive method.
The method must have parameters marking the lower and upper limits of the range being checked.
The outline algorithm is very simple, but getting it correct istricky:
static int binary(int[] data, int start, int end, int lookFor){if (start>end) return -1;int middle = (end+start)/2;if (data[middle]>lookFor) return binary(data, start, middle-1, lookFor);else if (data[middle]<lookFor) return binary(data, middle+1, end, lookFor);else return middle;}public static void main(String[] args) {int[] data = { 4,7,9,10,13,14,18,24,32,45,67,89, 98 };int where = binary(data, 0,data.length-1,98);if (where==-1) System.out.println("Not present");else System.out.println("Found at "+where);}
Exercise
Invent a systematic way of testing this, and do so.
How fast is a binary search?
How To Program © Walter Milner 2013 Page 43
If the item we are looking for is the middle one, we are lucky– we find it in one step. In the worst case we have to keep on halving the intervals until we get down to a range of just 1. How many steps would that be?
Suppose we have 128 items. First step reduces this to a range of 64. Then 32, 16, 8, 4, 2, and 1, when we find it.
So the answer to our question is the same as the number oftimes we can half it until the answer is 1. In other words, for n items, it is the value of d in 2d=n.
That is, log2 n
So the speed of a binary search is around log n.
Suppose we need to search 32768 items. That is 215. So it would take around 15 steps. A linear search would take around 16000 steps. So the binary search is around 1000 times faster. This shows the importance of choosing an algorithm.
Why is it so much faster? In one step on a linear search, we can ignore one item (the one we are looking at – or we've found it). On a binary search, we can ignore half of what remains. So when searching 32768, on the first step we have dealt with 16384 items.
This comes at the cost of having to have the data in order.
Equivalence of recursion and iteration
It can be proved that any recursive process can be converted into an iterative one.
Once you have the idea, recursive programs are easy to write. But sometimes, if they repeat identical calculations, they can be slow.
How To Program © Walter Milner 2013 Page 44
Data structures
This section gives the idea of what a data structure is, by looking at one example a linked list.
Linked lists
This is the idea of a linked list:
This example has 5 nodes. Each node has two parts some data, and a pointer to the next node.
The data in these nodes are integers, but they could be any type of data. They might also be a collection of fields, like name, address, date of birth and so on. Each node would hold the same set of fields.
These nodes are not in increasing order ( 6 9 3 9 4). If they were, they would be ( 3 4 6 9 9).
Each pointer would in fact be the address in memory of the next node. But we can think of it as 'pointing to' the next node.
The last node, the tail, has a next pointer which is different since there is no next node for it. The pointer there has a special value, null, meaning 'the pointer to nowhere'.
Defining a linked list in Java
To do this in Java, we need to set up a new kind of thing – aNode. This concept is the basis of Java – that of object oriented programming. A 'new kind of thing' is a class. We
will define a Node class. Then we will make some instances of the Node class – some Node objects – in other words, some Nodes.
We need to say what data members there are in a Node. There are just two, an int, and a pointer to the next node. We also need a constructor, which is used to create a new node, and a method of linking the node to another one.
Here is our Node class:
How To Program © Walter Milner 2013 Page 45
public class Node {// two data membersint data;Node next;
// the constructor : has same name as the class, no return typeNode(int val){data=val;next=null;}
// method to link this node to anothervoid link(Node another){next=another;}
}
The names of classes in Java should always start with a Capital Letter.
We put this code defining the class in its own file, with a name which exactly matches the class name : Node.java. This means we now have two source code files in our project– Test.java containing main, and Node.java. In Eclipse this
looks like this.
In main we can make 3 nodes:
How To Program © Walter Milner 2013 Page 46
public class Test {
public static void main(String args[]) {Node node1= new Node(6);Node node2 = new Node(9);Node node3 = new Node(3);}}
We say Node node1 =... because Node is the type. The keyword new invokes the constructor, to create a new
object.
That gives us:
Now lets link them:
public class Test {
public static void main(String args[]) {Node node1= new Node(6);Node node2 = new Node(9);Node node3 = new Node(3);
node1.link(node2);node2.link(node3);}}
In node1.link... we are telling the node1 object to do its link method. Check out the dot notation. We always say
<some object>.<some method>(..)
to make a method execute.
But at this stage we just have nodes, and we wanted a linked list. We need another type of thing – another class, named LinkedList:
public class LinkedList {Node head; // One data member, a pointer to the head
LinkedList(Node headNode) // the constructor{head=headNode;}
}
which we can use like this:
How To Program © Walter Milner 2013 Page 47
public class Test {
public static void main(String args[]) {Node node1= new Node(6);Node node2 = new Node(9);Node node3 = new Node(3);
node1.link(node2);node2.link(node3);
LinkedList list1 = new LinkedList(node1); }}
Exercise
Copy this code, and modify it so nodes have 2 data fields, an integer field named ID and a double field named 'value'. Keep this for the next exercise.
Pretty-printing a linked list
How could we output a linked list nicely like [ 1, 2, 3 ]?
We need to start at the head, printing each node, then follow the pointer on to the next, and repeat until we reach the end. Going through a data structure like this, visiting each node and doing something (like printing it) is called a traversal.
Here is the class with the print method added:
public class LinkedList {Node head; // One data member, a pointer to the head
LinkedList(Node headNode) // the constructor{head=headNode;}
void print(){System.out.print("[ ");Node nodePtr=head;while (nodePtr!=null){System.out.print(nodePtr.data+" ");nodePtr=nodePtr.next;}
System.out.println("]");}
}
and to use it:
How To Program © Walter Milner 2013 Page 48
public class Test {
public static void main(String args[]) {Node node1= new Node(6);Node node2 = new Node(9);Node node3 = new Node(3);
node1.link(node2);node2.link(node3);
LinkedList list1 = new LinkedList(node1);list1.print(); // get [ 6 9 3 ]}}
Exercise
Modify this so it pretty prints your list with two data fields
Adding to a list
Suppose we have a new node, and we want to put it in a
list, placing it at the head. How do we do that?
We make the next of the new node point to the current headof the list, then change the head to this new node. We need to add a method to the LinkedList class:
How To Program © Walter Milner 2013 Page 49
public class LinkedList {Node head; // One data member, a pointer to the head
LinkedList(Node headNode) // the constructor{head=headNode;}
void print(){System.out.print("[ ");Node nodePtr=head;while (nodePtr!=null){System.out.print(nodePtr.data+" ");nodePtr=nodePtr.next;}
System.out.println("]");}
void put(int value){Node newNode = new Node(value);newNode.next=head;head=newNode;}
}
tested by:
public class Test {
public static void main(String args[]) {
LinkedList list = new LinkedList(null);list.put(5);list.put(6);list.put(1);list.print(); // get [ 1 6 5 ]}}
Exercise
Write a method called append(), which adds the new node atthe end of the list. You first need to traverse the list to find the last node.
Searching a list
Suppose we want to search a list for a given data item. Usually we would look for some key field, in order to retrieveother value fields linked to that key. In this example we willjust get the index of the node containing the value, starting at 0. For example if the list is [5, 3, 9] and we search for 9, we should get 2.
If the list does not contain the value, we will return 1.
How To Program © Walter Milner 2013 Page 50
Here its is (this is just the find method, placed in LinkedList.java):
..int find(int val){Node nodePtr=head;int index=0;while (nodePtr!=null){if (nodePtr.data==val)return index;
index++; nodePtr=nodePtr.next;}
return -1;}
Exercise
Add comments to the above method.
Test it.
Deleting a node
To delete a node, we first need to find it. Then we would usually cut it out, by making the next of the previous node point to the following node:
In that example, it looks like the 3 node is 'still there'. But ithas been removed from the list.
We need to be sure the memory used by the node is released and returned to the pool of free memory or otherwise we get a memory leak, and we will eventually run out of free memory if we keep doing it. This depends on the language used. In C and C++ we would need to free the memory. In Java, there would no longer be a reference to the node, and the garbage collector will remove it for us.
If the node to remove is the first one, its slightly different, since then we have to change head to point to the second node. Here it is:
How To Program © Walter Milner 2013 Page 51
void delete(int val) {if (head.data == val) { // is it at the head?head = head.next; // change head to the second nodereturn; // and finish}
Node nodePtr = head; // start searching at the headwhile (nodePtr.next != null) { // until reach the lastif (nodePtr.next.data == val) { // if the next node has valuenodePtr.next = nodePtr.next.next; // cut it outreturn; // and finish}
nodePtr = nodePtr.next; // else go on to next}
}
Exercise
1. We should test this, making sure it can delete the first item in a list, the last, one in the middle, and an item not present. Do this testing.
2. If there are several nodes with the same value, this just removes the first one. Write a method removeAll which would delete all of them.
Ordered Lists
An ordered list has nodes in increasing order or decreasing.
The data in our example lists have just been integers, so it is obvious what 'increasing' means. But suppose the data was name, address and date of birth? They could be in alphabetical order of name, or time order of date of birth, orwhatever. We just need to bear that in mind.
How do we get an ordered list? We start with an empty list, and every time we insert a new node, we have to put it in the correct place. Like this:
How To Program © Walter Milner 2013 Page 52
So, we traverse the list from the head, until we find a node which has data larger than the new node to insert. Then we do the insertion, by making the new node 'next' point to the larger node, and making the previous one point to the new node. This is 'normal' case.
But there are three other situations we must code for:
1. Inserting into an empty list. For that, the head of the list is null. We just change it to point to our new node.
2. The new value is less than everything in the list less than the first node. If so, it should be the new head of the list. So, we make the new node 'next' point to the current head, then change the head to point to the new node.
3. The new value is greater than everything in the list, so it should go after the current last one. We just make the 'next'of the last one point to the new node.
Exercise
Draw diagrams illustrating these 3 cases.
Turning this into Java, we can define a class called OrderedList, which is identical to the LinkedList class, except the put method is altered:
public class OrderedList {Node head; // One data member, a pointer to the head
OrderedList(Node headNode) // the constructor{head = headNode;}
..
void put(int value) {Node newNode = new Node(value);// insertion in empty listif (head==null){head=newNode;return;}
// insertion before headif (head.data>value){newNode.next=head;head=newNode;return;}
// search where to put itNode where=head; // start at the headwhile (true){// if we reach the lastif (where.next==null){where.next=newNode; // put it after the lastreturn;}
if (where.next.data>value) // if next one is bigger{// insert it here
How To Program © Walter Milner 2013 Page 53
newNode.next=where.next;where.next=newNode;return;}
// else go on to nextwhere=where.next;}
}
..}
Code to test this:
OrderedList list = new OrderedList(null);list.put(5);list.put(6);list.put(1);list.delete(7);list.print(); // get [ 1 5 6 ]
Exercise
Modify this so it keeps the list in decreasing order.
Self-organising lists
Searching a linked list for an item is fast if it is near the head, and slow if it is towards the end.
That means the overall performance will be better if more frequently accessed items are towards the front. One simpleway to do that is to move a node forward one place every time it is accessed. That way the list organisies itself.
The first person to have this idea was McCabe in 1965
Exercise
Implement this in Java. Start with the unordered list, and modify the find method so that a located node is moved forward one place.
Create a list with say 1000 nodes of random integers in the range up to 1000.
Then many times, search for random integers in the range up to 100. This should move smaller numbers towards the front of the list.
Can you show the list gets faster as it is used?
How To Program © Walter Milner 2013 Page 54
Object oriented programmingThe previous section used OOP to model linked lists and thenodes they contain. This section provides more features.
A class defines a new type.
The definition is stored in a .java source code file, which is compiled into a .class file. For example, class Node is defined in Node.java, which compiles to Node.class.
By convention, class names should start with a CapitalLetter.
A class has several members – data members (sometimes called fields or attributes), methods (blocks of code which do something) and constructors, which create the instancesof the class.
The order of the definitions of constructors, fields and methods in a class file do not matter. A class cannot be 'run'and its not an executable program. It is a definition. Only inside a method or constructor does the order of the statements matter.
Packages
A Java application usually contains several classes, which work together to do what is required. In the last section, we saw a LinkedList class using the Node class, since a linked list contains nodes.
Grouping classes together into related groups is useful. A group of related classes is called a package. To create a package called, for example, dataStructures, we just need to:1. say
package dataStructures;
at the start of each class in the package, and
2. Put each file in a folder with the same name.
In Eclipse that looks like:
How To Program © Walter Milner 2013 Page 55
Another advantage of using packages is that we do not needto worry if the name of our class clashes with the name of a class somewhere else. The 'fullyqualified' name of our Nodeclass is dataStructures.Node, and this way it has a unique name.
Static and non-static
Class members default to being nonstatic. For example:
class SomeClass{int x;double y;char z;}
then x y and z are nonstatic. Every instance of SomeClass (each SomeClass object) has their own x y and z fields, and they will typically be different values for each different instance. Nonstatic members are per object. We might say
SomeClass obj1=new SomeClass();obj1.x=3;SomeClass obj2 = new SomeClass();obj2.x = 9;
But if we had
class SomeClass{static int w;int x;double y;char z;}
then the field w is static, which means there is just one value for the whole class. A static member is per class.
In this example we might say
SomeClass.w = 5;
This is true for methods as well as data fields.
How To Program © Walter Milner 2013 Page 56
The starting point for an application, the main method, is static:
public static void main(String args[]) {..
The reason is that execution starts here, and so at this point there are no objects created yet, so we cannot invoke the method on an object. We fix the problem by making the method static, then we do not need to make an object first.
How To Program © Walter Milner 2013 Page 57
Time and space complexity
Introduction
This section is about comparing different data structures and algorithms, to decide which is the best to use.
It introduces 'bigO' notation used when analysing algorithms
List insertion
We wrote a linked list data structure, and a method to insert a new item at the head of the list. How fast is it? The following code measures how long it takes to insert 1,000,000 new nodes
long start = System.nanoTime();LinkedList list = new LinkedList();for (int count = 0; count < 1000000; count++)list.put(3);
long time = System.nanoTime() - start;System.out.println(time);
System.nanoTime returns the system time in nanoseconds. In fact it took 100092500 ns. Trying that again with numbers of nodes up to ten million, we get the times shownin the table.
Nodes Time taken (ns)
1000000 10009250
2000000 334951206
3000000 249423751
4000000 553908832
5000000 634907728
6000000 1330103003
7000000 462940693
8000000 1378609484
9000000 1872583335
10000000 1926329984
If we graph this, we get
How To Program © Walter Milner 2013 Page 58
These time measurements have limited accuracy. One issueis that this was run on Ubuntu Linux, which like all modern OS is multitasking, so that while this program wasrunning, lots of other things were also running, and they may have changed over different runs. But we will soon see this does not matter.
Suppose we time insertion into an ordered list. This will depend on how big the value to insert is. One way to do it would be to insert random values:
for (int limit = 1000; limit < 11000; limit += 1000) {long start = System.nanoTime();OrderedList list = new OrderedList();for (int count = 0; count < limit; count++){int val = (int)(Math.random()*10000);list.put(val);}
long time = System.nanoTime() - start;System.out.println(time);}
}
Trying this out for up to 10000 nodes we get the graph.
Insertion in an unordered list gives a straight line, but an ordered list gives a curve. Why?
Big O notation
This is the code to insert a node at the start of an unordered list:
public void put(int value) {Node newNode = new Node(value);newNode.next = head;head = newNode;}
How To Program © Walter Milner 2013 Page 59
So there are 3 steps involved. If we insert n nodes, that will require 3n steps. If each step takes time t seconds, then
time taken = 3 t n
Now if we ran this on a different computer, with a different clock speed, or a different OS, the time for a single step would be different.
But we are not interested in the speed of a program on one computer. We want the speed of the algorithm with this datastructure.
In any language, on any computer,
time taken = some number X n
The value of the number depends on the speed of the computer, which is not what we are interested in. In BigO notation we say
time = O[n]
meaning the time is proportional to the number of nodes ( and not n2 or log(n) or √n and so on).
Insertion in an ordered list
When a node is inserted in an ordered list, we have to search along the list to find the correct point to insert it.
This might be at the front, or we might be unlucky and it might be at the back, so we have to go all the way through. If there are k nodes,
the number of nodes could be anything from 1 to k.
If we are inserting random data, there is an equal chance it will be inserted anywhere down the list. On average it will be inserted half way down, or k/2 nodes to check.
That is to insert 1 node. How many to check to insert n nodes? The first is 1/2. The second is 2/2 .The third is 3/2,and so on, with the last being n/2.
So the total nodes to check is
1/2 + 2/2 + 3/2...n/2
=(1+2+3..n)/2
That is the sum of an arithmetic progression, which is n(n+1)/2 (Google if you have not met aritmetic progressions). So the nodes to check is
n(n+1)/4
In bigO, we can ignore the 1/4 factor, since it is a constant, and does not alter how the time depends on n. So the time is
O[n(n+1)] = O[n2+n]
How To Program © Walter Milner 2013 Page 60
but in algorithmic analysis, we are only interested in the time for a large number of data items. We assume that for asmall number of items, all algorithms are fast. The test is how good they are for a large number of items.
This shows n and n2 for different values:
n n2 n2+n
1 1 2
10 100 110
100 10000 10100
1000 1000000 1001000
so for large n, n2 is much bigger than n. So n2+n is almost equal to n2, for large n. So the time of our insertion is
O[n2]
This makes sense. As the list gets longer, it takes on average longer and longer to find where to put it. This is why our graph curves upwards.
In summary, inserting data into an ordered list is slow.
Exercise
This is what happens for random data
1. Suppose the data is inserted in reverse order, with the largest number inserted first, and the smallest last. Where do all the insertions take place? How long to insert n items?
2. Suppose the data is in order, so the smallest is inserted first. Where do all the insertions occur? How long for n?
This is called bestcase and worstcase analysis.
How To Program © Walter Milner 2013 Page 61
Array data structures
An array is a data structure where the elements can be accessed through an index. That means we can get the third item, or the ninth, or the second.
We show an array of 12 integers. The first element is 7. The second is 9, the third is 1, and so on. We have shown the
first element as index 0. It is like this in C and Java and Java. Fortran starts at 1.
The following Java code creates an array as above, and outputs the first two elements:
int[] array={7,9,1,2,4,7,8,8,2,4,5,1};System.out.println(array[0]); // 7System.out.println(array[1]); // 9
There are only two operations on an array get the value of an element at an index, or change the value at an index.
Standard implementation
In many languages, an array is stored in one contiguous block of memory (not several blocks with gaps between).
An item is then accessed by an address calculation. Suppose the array starts at address 100, and is made of integers each needing 4 bytes. Where is the third element? There are 2 elements in front of it, in 2 X 4 = 8 bytes. So thethird element is at address 108.
This calculation can be done in constant time, not affected by the size of the array. So the time complexity of array element access is O(1).
Usually then, arrays are fixed in size, and are homogeneous all the elements are the same type.
How To Program © Walter Milner 2013 Page 62
Re-sizable arrays
Often we do not know when we create the array how many elements it will have. One solution is to create it with a lot, and hope you do not need more. But that wastes memory if in fact you only use a few elements.
What some languages do is to have a resizable array. In Java 'normal' arrays are fixed size, but we can get resizablearray as well). When it is created, it has a block larger than the number of elements, to accommodate extra elements.
When sufficient new elements are added, the block becomesfull. A new, larger block of memory is obtained, the data copied to it, and the old block released. Again the new block is larger than needed, to accomodate additions. Copying the data is slow, so we want to avoid it if possible without using excessive memory.
We show an implementation of this idea in Java, also introducing some more OOP ideas.
Access control, wrapper classes and import
We will define a resizable array class. The idea is that the data is held in a fixed size array with an initial size (of 10). Ifwe try to put something into it beyond the array size, we make a new array twice the size, copy the data to it, and use the bigger array instead. The class will be named ArrayList, and will be part of the dataStructures package.
The first issue is that if we try out the class in Test.java, it has no idea what ArrayList is:
How To Program © Walter Milner 2013 Page 63
In fact there is another class called ArrayList, in the package java.util. We can tell Test.java where to look for theArrayList we want, by starting with an import statement:
import dataStructures.ArrayList;
public class Test {
public static void main(String args[]) { ...
We could have done the same by always referring to the fullyqualified class name, dataStructures.ArrayList, but theimport statement saves a lot of typing.
The code for ArrayList is:
package dataStructures;
public class ArrayList {private int capacity;private Integer[] data;
public ArrayList() {capacity = 10;data = new Integer[capacity];for (int i = 0; i < capacity; i++)data[i] = null;
}
private void enLarge() {// create array twice as bigInteger[] newOne = new Integer[2 * capacity];// copy data to itfor (int index = 0; index < capacity; index++)newOne[index] = data[index];
// null the restfor (int index = capacity; index < 2 * capacity; index++)newOne[index] = null;
data = newOne; // switch to the new arraycapacity *= 2;}
How To Program © Walter Milner 2013 Page 64
public int size() {return capacity;}
public void set(int index, Integer n) {if (index > capacity)enLarge();
data[index] = n;}
public Integer get(int index) {if (index < 0 || index >= capacity)return null;
elsereturn data[index];
}}
This uses the keywords public and private a lot. Why?
public and private are access modifiers, which are used to achieve encapsulation
A class member marked as public can be used in any other class, including in a different package. We want other classes to be able to make an ArrayList, so the constructor is public. We also want other classes to be able to put data into an ArrayList, and get it out, so the set and get methodsare public. But we want to stop other classes having direct access to the underlying data, and the capacity field, for
fear they might accidentally corrupt the values. So we makethem private. This is not a security measure, but an attempt to stop programmers writing bugs. If this data cannot be accessed directly, a bug cannot corrupt it.
The capacity field is private, but we want to allow other classes to know how big the arraylist is, so we have a publicmethod which returns capacity. This makes it in effect a readonly value. A method which reads a private field is called a getter, and one that writes a private field, after validation, is a setter. Getters and setters in general are
called accessor methods.
If we have neither private nor public, then the default (nothing) setting is packageprivate. That means the member is visible from within the package, but not outside the package. This expects classes to be packaged so that they will work together, and can directly access each others members, but not from outside, unless they are made public.
Another aspect of this code is that it refers to Integer not int. Integer starts with a Capital Letter, so it is a class. It is an example of a wrapper class, which encloses a primitive (here an int) and which also has some useful methods. Other wrapper classes include Double and Character.
How To Program © Walter Milner 2013 Page 65
We don't we have to import the Integer class? Because it is in the java.lang package, and the compiler looks in there anyway.
We can test our ArrayList by:
import dataStructures.ArrayList;
public class Test {
public static void main(String args[]) {
ArrayList myArray=new ArrayList();myArray.set(5, new Integer(9));myArray.set(2, new Integer(6));myArray.set(12, new Integer(7));for (int i=0; i<myArray.size(); i++){System.out.println(i+" : "+myArray.get(i));}}}
The standard ArrayList in java.util works rather differently, and has a lot more source code.
How To Program © Walter Milner 2013 Page 66
Strings
A string is a string of characters – a piece of text. Since humans do so much with writing, strings get special attention in programming.
Java deals with strings in four ways. There is a String class,or you can have an array of char primitives, or you can use the classes StringBuffer and StringBuilder. We will look at each, and pick up some more OOP concepts.
Unicode
All chars and strings in Java use the Unicode character set.This has a very large number of characters, covering most of the world's written languages, including historic and ancient scripts not used for a thousand years, and many symbol sets. Data can use these, and so can source code. So you can say for example
double π = 3.1415926;double radius=3;double area = π * radius * radius;
The charmap utility on Windows and Linux is useful for dealing with Unicode. Note that whether you can see funny characters depends on whether a font is being used which can handle them.
The String class
Java has a String class, with many useful methods. For example:
String one="Hello";String two = one.concat(" starts with a SPACE");System.out.println(two);String three=two.toUpperCase();System.out.println(three);String[] words=three.split(" ");for (String s : words)System.out.println(s);
which outputs
Hello starts with a SPACE
HELLO STARTS WITH A SPACE
HELLO
STARTS
WITH
A
SPACE
How To Program © Walter Milner 2013 Page 67
The Java API
How do we know all the methods of String? In fact how do we know what classes are available?
There is a standard format documentation available, as the Java API, or application programming interface.
The documentation of String is here.
The documentation of all the Oracle Java classes (version 7)is here
For each class there is an outline, and a listing of all its constructors and methods.
Objects and references
The String class is immutable. That means you cannot change a String object.
What about:
String one="Hello";one = "Goodbye";
That looks like we've changed a String. But we have not.
In the code above, 'one' is not a String object. It is a reference to a String object.
The String object “Hello” is held in memory, and 'one' is a reference to it – in other words a pointer to where in memory it is held – probably its address.
When we say
one = "Goodbye";
we do not change any object. We change the pointer to pointto a new object:
String processing
The immutability of strings influences how you should process them. For example, here is a static method to reverse a string:
How To Program © Walter Milner 2013 Page 68
static String reverse(String s){String result="";for (int pos=0; pos<s.length(); pos++){result=s.charAt(pos)+result;}
return result;}
This goes through the string, from start to end, get each character using charAt, and puts the new character at the front of the result. The last one, which will appear at the front, is the last character in the argument.
So it works. But the result = .. creates lots of intermediate String objects – one for each character. A more efficient alternative is to use StringBuilder:
static String reverse(String s){StringBuilder result=new StringBuilder("");for (int pos=0; pos<s.length(); pos++){result.insert(0, s.charAt(pos));}
return new String(result);}
A StringBuilder is like a String, but mutable. It is common to make a StringBulder object, modify it, then return a String constructed from the StringBuilder instance.
StringBuffer is similar but is threadsafe and slower.
Comparing objects and references
A palindrome is the same when reversed. So 'aoxomoxoa' is a palindrome.
One way to tell if a string is a palindrome would be to reverse it, and to see if it is the same. So we might say:
static boolean isPalindrome(String s){String r = reverse(s);return s == r;}
but if you test it, this does not work – it always gives false. Why?
In this code, s is not a String object. It is a reference to a String object. Same for r. So s == r checks if these two references are the same – in other words, if they point to thesame object – which they do not.
Clearly r and s do not point to the same object. What we want to know is whether those two objects contain the samecharacters. The .equals() method does that:
How To Program © Walter Milner 2013 Page 69
static boolean isPalindrome(String s){String r = reverse(s);return s.equals(r);}
This works as expected. .equals is a method of String, which returns true if the other String contains the same sequence of characters.
All classes, not just String, have an equals method.
How To Program © Walter Milner 2013 Page 70
Sort algorithms
The sort concept
To 'sort' data means to arrange it into order.
For example, if we sort
[ 7 4 8 3 5 ]
into increasing order we get
[ 3 4 5 7 8 ]
We could also sort into decreasing order. If the data is not numeric, then 'order' has some other meaning. If it is text, we might use dictionary order.
The reason why it is called 'sorting' is interesting. Why is it not called 'ordering'? To 'sort' something means to separate it into different 'kinds'. Why is that connected with putting things in order?
In 1890 Hollerith (who went on to found IBM) processed thedata from the US Census, using punched cards. The cards were passed through a machine and separated using an algorithm called a radix sort. This works as follows:
Separate the cards into 10 piles, onthe basis of the first digit. So the firstpile is all 0something, the second is all1something, the third is 2something
Take each of these, and separate into10 piles on the second digit. So one ofthese set of ten would be 10.., 11..,12.... 19
Separate these on the third digit, for100.., 101.., 102...
Continue to the last digit.
Put all piles back together. They arenow in order.
This is why 'sorting' came to mean 'ordering'.
In base 10 numbers, each stage generates 10 piles. But on a computer the data is binary, and we just have 2 piles for0 and 1. Processors also have single machine code instructions to test for a single bit. This means a radix sort is extremely fast quicker than a quicksort, which we will meet shortly and is the most common algorithm in use now.It is strange that the first algorithm is faster than usual onein current use. We will code radix sort in a later section.
How To Program © Walter Milner 2013 Page 71
Bubble sort
There are many different sort algorithms, and they vary greatly in speed. It is usual to start with a bubble sort.
Suppose we go through a list of numbers and swap each number and the next if they are in the wrong order. For example:
9 8 10 6 7 1 7 swap
8 9 10 6 7 1 7 no swap
8 9 10 6 7 1 7 swap
8 9 6 10 7 1 7 swap
8 9 6 7 10 1 7 swap
8 9 6 7 1 10 7 swap
8 9 6 7 1 7 10 final state
At the end the largest number, 10, has been swept to the end. But the rest are not yet in order. The second largest, 9,has only been moved up one place.
If we did this a second time, the second largest number, 9, would move to its final place, one from the end.
If we did it a third time, the third largest, would move to its correct position.
If there are n numbers, we must therefore do this n times, to get them all in the correct position:
1. repeat n times:
2. go through the list from start to end, and swap adjacent values if they are in the wrong order
Here it is in Java:
final int SIZE = 10;double[] data = new double[SIZE];// fill array with random numbersfor (int index = 0; index < SIZE; index++)data[index] = Math.random();
// do bubble sortint end = SIZE - 1;for (int time = 0; time < SIZE; time++)for (int index = 0; index < end; index++) {if (data[index] > data[index + 1]) {double temp = data[index];data[index] = data[index + 1];data[index + 1] = temp;}
}// display array contentsfor (int index = 0; index < SIZE; index++)System.out.println(index + " : " + data[index]);
which outputs something like:
0 : 0.3411649943934846
1 : 0.4123942855959868
2 : 0.4207890477857321
3 : 0.6273373150343701
4 : 0.6918709783394186
How To Program © Walter Milner 2013 Page 72
5 : 0.7042990335581902
6 : 0.7152050462793944
7 : 0.8710305428710455
8 : 0.9235170851394418
9 : 0.9589815770978095
We can improve bubble sort, to make it faster, in several ways. To put n items in their correct place, we only need to do it n1 times, since if n1 values are in the right place, thelast one must be also.
Exercise
Alter the code to do this (time only needs to go up to SIZE1)
After the first pass, the largest number has been moved to the end.
So on the second pass, we do not need to go so far. We can finish the second pass one from the end.
We can finish the third pass two from the end. Finish the fourth pass three from the end.
So in our Java version, we can decrease 'end' after each pass.
Exercise
Alter the code to do this ( end; at the correct point )
Suppose we use this method on
[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
in other words on data which is already sorted. It will still go through the same process and take just as long, even though there is nothing to do.
We can fix this by having a boolean flag which represents whether any pair was switched in one pass. We make it false before each pass, and change it to true if we do a swap. If it is still false after a pass, the data is already sorted and we can stop.
Exercise
Alter the code to do this. Use break to break out of the outerloop.
Bubble sort is stable and in place
Suppose we have 2 equal numbers in the list. What will happen to them? We say
How To Program © Walter Milner 2013 Page 73
if (data[index] > data[index + 1]) {
We swap them if one is bigger. If they are equal, we do not. That means two equal values remain in the same order. That means the sort is stable.
It also uses no extra storage, apart from the temporary variable and the counters. The numbers in the list are rearranging still in the list. We do not move them to anotherdata structure. That means bubble sort is in place. For large data sets this is an issue if we are sorting megabytes of data, we would like to avoid copying it into additional megabytes if possible.
Time complexity of a bubble sort
The worst case for a bubble sort is if the data is initially in reverse order. In that case, for n items, n1 passes are required. The length of each pass is n1, then n2, then n3, down to 1. This averages n/2, and the total number of comparisons is
(n1) n/2 =O(n2n) = O(n2)
For random data the number of swaps will be less, but it will still be O(n2)
The best case is if the data is already sorted, and we use theimprovement given we only need to go through it once and the time is O(n)
For small numbers of items to sort (say 100 or less) a bubble sort is OK but then for that number, any method isOK. For more than a 1000, it takes more than a million steps, and it is usually too slow.
Quicksort
There are many other sortalgorithms. We will look at justone other the commonly usedquicksort, invented by TonyHoare in 1960.
The idea is as follows:
Choose one element say theone on the right to be the'pivot value'. Go through thearray left to right. If we meet anitem less then the pivot, swap it with a growing section of small items at the left:
How To Program © Walter Milner 2013 Page 74
left right = pivot
60 40 31 70 10 50
60 40 31 70 10 50 swap
40 60 31 70 10 50 swap
40 31 60 70 10 50
40 31 60 70 10 50 swap
40 31 10 70 60 50
less than pivot equal or more than pivot
When we reach the right, everything less than the pivot has been moved to the left. This means we have partioned the array into two parts. The left part is all less than something,and the right part is all equal or more than something. So we recursively do this again on the two parts, until the parts get down to length 1.
Here is a version in Java. First one partition stage:
static int partition(int[] array, int left, int right, int pivotIndex) {int pivotValue = array[pivotIndex];int temp = array[right];array[right] = array[pivotIndex];array[pivotIndex] = temp;int storeIndex = left;for (int i = left; i < right; i++) {if (array[i] <= pivotValue) {temp = array[i];array[i] = array[storeIndex];array[storeIndex] = temp;storeIndex++;}
}temp = array[storeIndex];array[storeIndex] = array[right];array[right] = temp;return storeIndex;}
used in the sort:
static void quicksort(int[] array, int left, int right) {if (left < right) {int pivotIndex = (left + right) / 2;int pivotNewIndex = partition(array, left, right, pivotIndex);quicksort(array, left, pivotNewIndex - 1);quicksort(array, pivotNewIndex + 1, right);}
}
started off by
How To Program © Walter Milner 2013 Page 75
final int SIZE = 30;int[] data = new int[SIZE];for (int i = 0; i < SIZE; i++)data[i] = (int) (Math.random() * 1000);
quicksort(data, 0, SIZE - 1);for (int i = 0; i < SIZE; i++)System.out.println(data[i]);
How fast is it? Firstly, let's think about initially random data. How many levels of recursion will there be? Each one halves the array (on average). So how many times can you divide n by 2 until you get to 1? For example if n is 64, you get partitions of size 32, 16, 8, 4, 2, 1. That's 6. In general, it is log2n.
Now for each level of partition how many elements in the array do we scan through? All n of them. And we do that log2n times. So for random data quicksort is O(n log n). Comparing this with bubblesort's O(n2):
n n log n n2
10 33.21928095 100
100 664.385619 10000
1000 9965.784285 1000000
10000 132877.1238 100000000
100000 1660964.047 10000000000
1000000 19931568.57 1E+12
So for large n, n2 is much bigger.
The sort function provided in the libraries of many languages is quicksort.
Suppose the data is already sorted? What will quicksort do?On a partition, the item on the right is the only one which isequal or greater than it. So the split will be between the rightmost one, and the rest. The number of partitions will be n, and the time will be n2 a disaster. An obvious improvement is an initial check for ordered data.
Exercise
In a selection sort, we find the smallest element in the array, and swap it to the front. Then we find the next smallest, and swap it to position two, and so on.
Write a selection sort in Java. Test it. Work out the best andworst case time complexity. Measure it.
Exercise
If you have a sense of humour, Google bogosort and program it.
How To Program © Walter Milner 2013 Page 76
Stacks
A stack is a linear data structure, and we can do two thingswith it push a value onto the stack, and pop a value off it:
We can only push a value on the 'top', and only pop off the top. That means the value we get when we pop a value will be the last one we pushed on. For this reason a stack is often called a LIFO structure last in, first out.
Linked list implementation
How can we actually implement a structure which works like this? One way is to use a linked list, as coded in Java ina previous section. Our stack will contain a linked list, and we just need to write push and pop methods.
package dataStructures;
public class Stack extends LinkedList {
public void push(int val){put(val);}
public int pop(){int val=head.data;head=head.next;return val;}}
which would be used as
Stack stack = new Stack();stack.push(1);stack.push(2);stack.push(3);System.out.println(stack.pop()); //3System.out.println(stack.pop()); //2System.out.println(stack.pop()); //1
OOP inheritance
Our Stack is like a LinkedList, except that it has two extra methods – push and pop. We could have copied the code of LinkedList, pasted it into Stack.java, and added the two extra methods. Instead we said:
public class Stack extends LinkedList {
This uses the OOP idea of inheritance.
How To Program © Walter Milner 2013 Page 77
The idea is that one class can inherit from another class. In this example, LinkedList is the base class, and Stack is the subclass. We would say we have 'subclassed' LinkedList. When one class subclasses another:
1. All data members of the base class and all methods are inherited by the subclass. In other words, the subclass has all the data members are methods of the base class.
2. The subclass can define extra members. This happened in our example – Stack has the extra pop and push methods.
3. A subclass can override methods of the base class. That means, the subclass can define methods with the same name and return type as in the base class, and these will be used by the subclass, instead of the base class version.
4. Constructors are not inherited.
We will discuss inheritance more later.
Exercise
Some stack implementations have a peek() method, which returns the top of stack, but leaves the item on the stack and does not remove it. Write the peek method. Consider what to do when peeking at an empty stack.
Uses of stacks - reversing things
If we push 7 and 3, we pop 3 and 7. So we can use a stack to reverse things:
Stack stack = new Stack();int[] data = { 1, 2, 3, 4, 5 };for (int i : data)stack.push(i);
for (int index = 0; index < data.length; index++)data[index] = stack.pop();
for (int i : data)System.out.println(i); // 5 4 3 2 1
Uses of stacks - return address
Here is some Java code:
How To Program © Walter Milner 2013 Page 78
The path of execution is these line numbers:
16, 8,10, 3, 5, 11, 17
When fun1 returns, it goes back to line 17.
When fun2 returns, it goes back to line 11
How does return 'remember' where to return to? By using a stack. When a function is called, the next line number is pushed on a stack. At a return, the stack is popped, giving the line number to return to. Like this
Line number
Action Stack top to the right
16 call fun1 17
10 call fun2 11 17
5 return pop11 17
11 return pop 17
17 Output and end
This means we can have an unlimited depth of function calls, and it all works.
Unless you ask for an infinite stack:
static void fun1(){fun1();return;}
which produces
Exception in thread "main" java.lang.StackOverflowError
at Test.fun1(Test.java:10)
at Test.fun1(Test.java:10)
at Test.fun1(Test.java:10)
at Test.fun1(Test.java:10)
at Test.fun1(Test.java:10)
at Test.fun1(Test.java:10)
How To Program © Walter Milner 2013 Page 79
at Test.fun1(Test.java:10)
In fact the stack is usually (in most languages) used to passparameters as well as to track return addresses. When a function is called, the return address is pushed, then the parameters. At the start of function execution, the parameters are popped off the stack. Then at the end, the return address is popped.
Uses of stacks - expression evaluation
Suppose you want to write program code which will evaluate a string. So "1 + 2" evaluates to 3.
The obvious way is to traverse the string left to right, and work it out as you go. This won't work, since when you get to +, you do not know what to add the 1 to. Further, if the string is "1+2*3" then the multiplication must be done before the addition.
One way to do it is using an operator precedence parser, as follows. Have two stacks opStack for operators +and * and valStack for values. And two actions shift pushes ontoa stack, while reduce pops two values off valStack, applies the operand to them, and pushes the result back onto valStack.
The input is scanned left to right. Values are shifted onto the valStack. For operators, use the following table to decidewhat to do:
Scanned input symbol
+ *
Top of opStack
+ reduce shift
* reduce reduce
empty shift shift
At the end, the value is the single value on the valStack.
Example with 2*3+4*5:
valStack (top on right)
opStack input action
empty empty 2 shift
2 empty * shift
2 * 3 shift
2 3 * + reduce
6 empty + shift
6 + 4 shift
6 4 + * shift
6 4 + * 5 shift
6 4 5 + * empty reduce
6 20 + empty reduce
26 empty empty end
How To Program © Walter Milner 2013 Page 80
Exercise
Code this in Java
Uses of stacks - programming languages
Some languages use a stack as a primary basis for holding data. One example is the language Forth. Another is Java bytecode. In Java, source code is compiled into bytecode, and the bytecode is executed by the Java Virtual Machine. Here is some Java source code:
x = 5;
y = 3;
z = x + y;
and this is the bytecode it compiles into:
0: iconst_5 // push 5 on the stack 1: istore_1 // pop value off stack into variable 1 2: iconst_3 // push 3 on stack 3: istore_2 //pop stack into variable 2 4: iload_1 // get variable 1 (x=5) 5: iload_2 // get variable 2 (y=3) 6: iadd //add them 7: istore_3 // store result in variable 3 (z)
Exercise
Google Charles H. Moore
Time Complexity
As normally implemented with a linked list, a push or a popis a single step, if we do it at the head of the list. So they areconstant O(1). Pushing or popping a value off a stack takes a fixed amount of time, irrespective of the number of items on the stack.
Exceptions
What would happen if we popped a value off an empty stack? What do we want to happen?
The normal situation is that we pop a value off. The exceptional situation is when the stack is empty. Most languages including Java have a way to deal with exceptions. Two common examples of exceptions are trying to open a file which cannot be found, and a user enters an invalid number, say with two decimal points.
There is a class named Exception, and instances of this model the occurrence of an exception. When an exception occurs, the code has two choices – to 'deal with' or 'handle' the exception, or to 'throw' it, which means some other codewill need to handle it. If no other code does, the application will end. But this should only be allowed to happen in thoserare cases when it is logically impossible to handle the exception.
How To Program © Walter Milner 2013 Page 81
In our case, we first need to define an appropriate exceptionclass, which we can do simply by subclassing RuntimeException:
package dataStructures;
public class StackUnderflowException extends RuntimeException {
public String getMessage() {return "Stack empty";}
}
Then we modify our stack pop method:
public int pop() throws StackUnderflowException{if (head==null) throw new StackUnderflowException();int val=head.data;head=head.next;return val;}
which we could use like this:
Stack stack = new Stack();stack.push(1);stack.push(2);stack.push(3);try{while (true)System.out.println(stack.pop());}
catch (StackUnderflowException su){System.out.println("The end");}
The try.. catch control structure means:
try{.. some code which might throw an exception..}catch (SomeException e){.. code to execute if the exception happens..}
More on inheritance
Suppose we have a base class and a subclass:
How To Program © Walter Milner 2013 Page 82
class BaseClass{int x;BaseClass(int val){x=val;}void baseMethod1(){System.out.println("In base method1");}void baseMethod2(){System.out.println("In base method2");}}class Sub extends BaseClass{int y;Sub(int val){x=val+1;}void baseMethod2(){System.out.println("In sub method2");}void subMethod1(){System.out.println("In sub method1");}
}
Then
1. Sub has a data field called x, and a method called baseMethod1. It has inherited these from BaseClass.
2. Sub has an additional field, y, and an additional method subMethod1
3. Sub has a method, baseMethod2, with the same name asin BaseClass. This method overrides the base class version.
So we can say
Sub s = new Sub(3);s.x=7; // inherited data members.baseMethod1(); // inherited methods.baseMethod2(); // use new over-ridden versions.subMethod1(); // use new method
or, we could if it would compile. It won't because of constructors.
When you write a constructor, you can call super(); as the first statement:
Sub(int val){ super(); x=val+1;}
super(); calls the constructor of the base class, and it creates the stuff the subclass will inherit.
Super() calls the 'noarg constructor' – the constructor with no arguments. If you said super(3) it would have called the constructor which takes an int argument.
If you do not say super(); the compiler puts it in for you (in the bytecode it generates).
How To Program © Walter Milner 2013 Page 83
That is what is happening in our example.
And that is the syntax error – BaseClass does not have a noarg constructor.
There are two solutions
1. Write a noarg constructor for BaseClass, or
2. Put super(val); at the strat of the Sub constructor.
What is the point of inheritance?
It is not essential. C is an excellent programming language, and has no inheritance ( because it does not have classes ). But it is useful in two ways
1. It corresponds to the way we think about categories. A Car is a type of Vehicle. A Ford is a kind of car. A Focus is atype of Ford. Since Aristotle wrote his Categories, people have thought this way.
2. It allows for code reuse. We often want a kind of thing, and we already have a similar class, but we want to change it a bit. So we subclass it. This is much more efficient than starting again from scratch.
The Object class
What about
class BaseClass
Does it extend any other base class in turn?
The answer is, yes. It does not say extends Anything, but in fact it extends a class called Object.
Object is the ultimate base class of all other classes. All classes descend ultimately from Object.
Object has few methods – like .equals and .toString and .hashCode. The idea is to ensure that all classes inherit these methods. Often they are overridden with new versions in sub classes.
If you look at the API of a class you see the inheritance hierarchy:
How To Program © Walter Milner 2013 Page 84
A JFrame models a GUI window, and is in the package javax.swing. This inherits from java.awt.Frame, then from Window, and so on back to Object, in java.lang. A JFrame inherits all the methods of those preceding classes.
How To Program © Walter Milner 2013 Page 85
Abstract Data Types
An abstract data type (ADT) defines a type of data purely interms of how it behaves.
As an example, a stack is an ADT with just two operations:
• Put a data item into a stack (normally called push)
• Get a data item out (called pop) in such a way that the item is the last item which was put in
To implement an ADT in some actual programming language, we also need to find
• Some data structure to represent the ADT
• Algorithms to carry out the operations
In the case of a stack, we can use an array, or a linked list. The algorithms to carry out the operations will depend on the choice of data structure, but in the case of a stack, theyare trivially simple.
A formal approach to this subject uses these ideas an ADT, a representation and a set of algorithms.
How To Program © Walter Milner 2013 Page 86
Queues
A stack is a linear structure where data is added and removed at the same end (LIFO). By contrast, a queue is a linear structure where data is added at one end, and
removed from the other. That means it is first in first out, orFIFO.
Operations
We can just do two things with a queue:
A. enqueue a new item (add data to the queue)
B. dequeue an item (remove data from the queue)
Implementation - Linked List
How to make such a thing?
One way is to use a linked list. We need access to both the head and the tail of the list, so it is faster to maintain pointers to both (rather than traversing the list every time tofind the tail). Also when dequeueing from the tail, we need to 'go back' so it is convenient to use a doublelinked list. Enqueueing is like this:
and dequeing is:
How To Program © Walter Milner 2013 Page 87
An implementation in Java is:
package dataStructures;
public class Queue {private class Node{int data;Node previous;Node next;Node(int val){data=val;next=null;previous=null;}
}
Node head;Node tail;
public Queue(){head=null;tail=null;}
public void enQueue(int val){Node newNode= new Node(val);if (head==null){head=newNode;tail=newNode;return;}
newNode.next=head;head.previous=newNode;head=newNode;}public int deQue(){int val=tail.data;
How To Program © Walter Milner 2013 Page 88
tail=tail.previous;return val;}}
which we can try out as
Queue q = new Queue();q.enQueue(1);q.enQueue(2);q.enQueue(3);System.out.println(q.deQue());System.out.println(q.deQue());System.out.println(q.deQue());
which outputs 1 2 3 as expected
Inner classes
In this implementation, the Queue class uses instances of the Node class as its doublylinked nodes. However the Node class is not defined in a separate Node.java file. It is defined inside the Queue class. This makes it an inner class.
It is also private. As for other members, this means it is only visible from Queue.
The idea behind this is that we are supposing that the doublylinked Node will not be used by any class other thanQueue. Then making it a private inner class means we maintain the encapsulation of Queue, and do not clutter thedatastructures package with an additional class/file. If Node had not been inner (had been a top level class) this would still have worked, but not been so tidy.
Exercise
There would be a problem if you tried to deque an empty queue. An obvious tactic would be to have it throw a QueueUnderflowException. Implement this (see the stack version).
How To Program © Walter Milner 2013 Page 89
Implementation – Array
How To Program © Walter Milner 2013 Page 90
In languages with fixed length arrays, we can use one to implement a queue. Since the elements are next to each other, we do not need pointers. We need indexes to track
the head and tail of the queue, which may need to wrap around.
It is important to be clear what 'head' and 'tail' denote here. Head is the index of the array element in which the next valuewill be enqueued. If that element has a value in it, it will be overwritten. Head is not in the queue. It is the first item not in the queue. Tail is the index of the element which we get if we deque. Tail is the last item in the queue.
Exercise
Implement and test a queue in Java using an array. It starts:
package dataStructures;
public class Queue {final int SIZE=100;int[] data = new int[SIZE];int head=0;int tail=0;
You need to write enqueue and dequeue methods, and workout how you tell if the queue is empty, and full..
How To Program © Walter Milner 2013 Page 91
Priority queues
In a priority queue, the data items include a 'priority' field, and nodes are kept in order of decreasing priority. As a result, when a value is dequeued, it will be the one from thehead, with the highest priority. (or we could keep it in increasing order, and deque from the tail).
To implement that as a linked list, we need an additional field in the nodes for priority.
We have an ordered singly linked list, ordered on the priority field. This starts:
package dataStructures;
public class PriorityQueue {
private class Node{int data;int priority;Node next;Node(int val, int p){data=val;next=null;priority=p;}}
Node head;
public PriorityQueue(){head=null;
}
The enqueue method has most code:
How To Program © Walter Milner 2013 Page 92
public void enQueue(int val, int priority){Node newNode= new Node(val, priority);if (head==null) // insertion in empty q{head=newNode;
return;}
if (head.priority<=priority){ // insertion before current headnewNode.next=head;head=newNode;return;}
Node where=head;while (true) // move along the list{ // if we reach the end..if (where.next==null){where.next=newNode;
return;}
// if we meet a smaller one..if (where.next.priority<=priority){newNode.next=where.next;where.next=newNode;return;}
}}
and dequeue is simpler:
public int deQue(){Node where=head;if (head.next==null) // last one{head=null;}
else{head=head.next;}
return where.data;}
Exercise
1. This ignores dequeing an empty queue. Fix this.
2. Use this to insert 100 items with random priority, then dequeue them.
3. They will be retrieved in order of priority, so this is a sort.What is its time complexity?
Namespaces
The code for the PriorityQueue refers to the Node class. So does the code for the Queue class. How come they refer to the correct Node class? Because they are in different namespaces.
How To Program © Walter Milner 2013 Page 93
In Computer Science a namespace is a way of limiting 'where' a name has a meaning. Without namespaces, we could only have one variable named 'x' in an entire application. In practice we can have lots of 'x's, so long as they are in different namespaces. The way that namespaces are implemented varies between languages.
In Java, namespaces are maintained through packages, classes, inner classes, methods and blocks.
So for example, we can distinguish our PriorityQueue from others (like the one in java.util) because its fully qualified name is dataStructures.PriorityQueue. The import statement simply avoids having to type that every time.
The class further divides a namespace. So Maths.PI is the name of the static data member PI in the class Maths. We could have a class named MyMaths, with a value MyMaths.PI – although it is hard to think why.
Namespaces control the names of things. Access modifiers control their visibility. So for example the inner classes Queue.Node and PriorityQueue.Node have different names. But they are also declared as private, so they are not visible outside their classes anyway.
How To Program © Walter Milner 2013 Page 94
Generics
Two ideas work in opposite directions – strong typing and algorithms. Generics fixes the problem.
Java is a strongly typed language. That means whenever you declare a variable, you must say what type it is. This is a good thing.
But as we have seen, algorithms are typeless. The quicksortalgorithm is the same whatever the type of data. The algorithm to delete an item from a linkedlist is the same no matter what type of data is in the list. However whenever wewrite Java code, we must say what the type is.
That would mean we have to write a linked list for integers, another one for strings, another for double, and so on. There is no way to reuse the same code for different data types. Except for generics.
It generic programming, you have type parameters in the code. To use the code, you supply a type as a value for the type parameter. And you get a version of the code appropriate for the type you want.
Writing generic code is sometimes tricky. But much of the time we can use the Collections framework, which provides generic code for the standard data structures, and is very easy to use.
A generic linked list
We can modify the code given earlier, to define a linked list which will hold any reference type, not just ints. We start with a node which can hold any type:
How To Program © Walter Milner 2013 Page 95
package dataStructures;public class GenNode <T>{T data;GenNode<T> next;
// the constructorGenNode(T val){data=val;next=null;}
// method to link this node to anothervoid link(GenNode<T> another){next=another;}
}
The <T> is a type parameter. It does not need to be T, but this is the convention. Whenever we actually make a GenNode, we have to specify what T is. We use this in a generic linked list:
package dataStructures;
public class GenLinkedList <T>{GenNode<T> head; // One data member, a pointer to the head
public GenLinkedList() {head = null;}
public GenLinkedList(GenNode<T> headNode) {head = headNode;}
public void print() {System.out.print("[ ");GenNode<T> nodePtr = head;while (nodePtr != null) {System.out.print(nodePtr.data + " ");nodePtr = nodePtr.next;}
System.out.println("]");}
public void put(T value) {GenNode<T> newNode = new GenNode<T>(value);newNode.next = head;head = newNode;}
public int find(T val) {GenNode<T> nodePtr = head;int index = 0;while (nodePtr != null) {if (nodePtr.data.equals(val))return index;
index++;nodePtr = nodePtr.next;}
How To Program © Walter Milner 2013 Page 96
return -1;}
public void delete(T val) {if (head.data.equals(val)) { // is it at the head?head = head.next; // change head to the second nodereturn; // and finish}
GenNode<T> nodePtr = head; // start searching at the headwhile (nodePtr.next != null) { // until reach the lastif (nodePtr.next.data.equals(val)) { // if the next node has
valuenodePtr.next = nodePtr.next.next; // cut it outreturn; // and finish}
nodePtr = nodePtr.next; // else go on to next}
}
}
This is the same as the nongeneric linked list, except that there we used int instead of T, and == in place of .equals.
We can use this then for any reference type:
GenLinkedList<Integer> numbers = new GenLinkedList<Integer>();numbers.put(3);numbers.put(2);numbers.put(5);numbers.print();GenLinkedList<String> words = new GenLinkedList<String>(); // [ 5 2 3 ]words.put("go");words.put("we");words.put("Here");
words.print(); // [ Here we go ]
T must be a reference type, not a primitive, so we must say GenLinkedList<Integer> not GenLinkedList<int>, and this code uses 'autoboxing', since
numbers.put(3);
is actually
numbers.put(new Integer(3));
The Java Collections framework
This is a set of interfaces and classes which provide generic implementations of all the standard data structures and associated algorithms. Check the Oracle documentation for full details.
How To Program © Walter Milner 2013 Page 97
For example, we might need to use a set of Strings, but we do not know how many, so we cannot use a String array. We could define our own linked list or resizable array, but we do not have to since we can use java.util.ArrayList instead:
ArrayList<String> words = new ArrayList<String>();words.add("The");words.add("small");words.add("black");words.add("cat");System.out.println(words.contains("Cat")); // falsewords.remove("small");for (String s: words)System.out.print(s); // Theblackcat
Another useful collection is a HashMap. This is a set of keyvalue pairs. You can put keyvalue pairs into it, retrievea value with a given key, get all the keys and so on:
HashMap<Integer, String> map = new HashMap<Integer, String>();map.put(5, "Green");map.put(1, "Blue");map.put(7, "Red");map.put(2, "Orange");System.out.println(map.get(7)); // RedSystem.out.println(map.get(3)); // nullSet<Integer> keys = map.keySet();for (Integer i : keys)System.out.println(i); // 1 2 5 7
The Collections are good illustrations of the idea of abstraction. How does HashMap work? We don't know, other than it is based on a hash table. Does it matter how itworks? No. If Oracle changes the way it works, does it matter? No. If you want to know, look at HashMap.java.
Radix sort
We can illustrate the use of an ArrayList to implement a radix sort.
Suppose we want to sort an array of words, each containing10 random letters.
Using a radix sort, as used by Hollerith in 1890, we
1. Separate them into 26 piles, according to the last letter
2. Put the piles together (keeping the sequence unchanged), then separate them again on the basis of the second to last letter
3. Repeat 1 and 2 until we put them together on the basis ofthe first letter
We will do this in Java.
We need to make the array – which means we need to be able to generate random letters of the alphabet. This generates random letters with character codes in the range 'from' up to 'to' inclusive:
How To Program © Walter Milner 2013 Page 98
static char rand(int from, int to){return (char) ((int)(Math.random()*(to-from+1))+from);}
Exercise
1. Test this
2. Work out how it works
Once we can get random letters, we need to make an array of strings of random letters:
static String[] makeRandomWords(){final int wordCount=10;final int letterCount=10;String[] words = new String[wordCount];for (int index=0; index<wordCount; index++){words[index]="";for (int letter=0; letter<letterCount; letter++){words[index]=words[index].concat(""+new
Character(rand('A','Z')));}
}return words;}
In this, the char literals 'A' and 'Z' are cast implicitly to theirint values (65 and 90).
To display the words, we can use:
static void print(String[] words){for (String s : words)System.out.println(s);
}
Now we need the actual sort:
static String[] radixSort(String[] data) {ArrayList<ArrayList<String>> buckets = new ArrayList<ArrayList<String>>();for (int i = 0; i < 26; i++) //make 26 bucketsbuckets.add(new ArrayList<String>());
// repeat sort on different characters, from right to leftfor (int charPos = 9; charPos > -1; charPos--) {for (String word : data) { // for each input wordchar c = word.charAt(charPos); // get charint bucketNumber = c - 'A'; // A = bucket 0, B = bucket 1 etcbuckets.get(bucketNumber).add(word); // add to that bucket}
int counter = 0;// go through each bucketfor (int index = 0; index < 26; index++) {for (String s : buckets.get(index)) { // for each worddata[counter] = s; // put back into the arraycounter++;}
buckets.get(index).clear(); // and empty the bucket, for next pos
}}
return data;}
What about
How To Program © Walter Milner 2013 Page 99
ArrayList<ArrayList<String>> buckets = new ArrayList<ArrayList<String>>();
We want to have 26 things, which we are calling buckets. The As go into the first, the Bs go in the second and so on. How many will there be in each bucket? We don't know, so each bucket must be an ArrayList of Strings. We could try and have an array of lists, but you cannot instantiate an array of generic type (because of the way Java does generics). So we have an ArrayList of ArrayLists. The line is
ArrayList< something> buckets = new ArrayList< something >();
where 'something' is an ArrayList<String>.
We can test this by
String[] data =makeRandomWords();print(data);data=radixSort(data);System.out.println();print(data);
This outputs
SDQGTUBYXF
UFPVFSYMYR
XYDYEYBHLE
DSSZBWJSHY
LMFXNSKJOH
RWSHCILAGA
PACJGFVLOP
AGUDGLNHBB
UOMPWYQSJS
XGDIMUKIIH
AGUDGLNHBB
DSSZBWJSHY
LMFXNSKJOH
PACJGFVLOP
RWSHCILAGA
SDQGTUBYXF
UFPVFSYMYR
UOMPWYQSJS
XGDIMUKIIH
XYDYEYBHLE
Exercise
Modify this so that instead of sorting a list of strings of letters like XYDYEYBHLE its sorts lists of 10 digit numbers like 0123853479
How To Program © Walter Milner 2013 Page 100
How fast is radix sort? One pass of n items requires n steps.How many passes? However many characters there are in the key (10 in our example. Is the time is O(kn) where k is the length of the key. That means it is quicker then quicksort.
How To Program © Walter Milner 2013 Page 101
Sets
A set is an abstract data type which models (more or less) the mathematical idea of a set a collection of items in no order, and with no duplicates. If you do not know about basic maths sets, it will make more sense if you read about them.
The modelling is limited, because there is usually no Universal Set (sometimes called a Universe of Discourse). Also mathematical sets are immutable, but a set data structure would usually have methods to add and remove items.
The usual operations are
1. contains is a given item an element of a set
2. union return the union of a set with another
3. intersection return the intersection of a set with another
Variadic arguments
The Collections framework has a builtin data structure of a set, but we will create our own. It will contain the elements in a linked list, but we have to check there are no duplicates. We will have a generic list, built from generic nodes with just a data value and a pointer to the next, as used earlier. It starts:
package dataStructures;
public class GenSet<T> {GenNode<T> head; // One data member, a pointer to the head
public GenSet() {head = null;}
..
public boolean contains(T val) {GenNode<T> nodePtr = head;while (nodePtr != null) {if (nodePtr.data == val)return true;
nodePtr = nodePtr.next;}
return false;}
Now we have a constructor with a new feature – variadic arguments:
How To Program © Walter Milner 2013 Page 102
public GenSet(T... values) {head = null;for (T value : values) {put(value);}
}
public void put(T value) {if (!contains(value)) {GenNode<T> newNode = new GenNode<T>(value);newNode.next = head;head = newNode;}
}
The triple dots … in the constructor header mean we can have a variable number of arguments, of type T. In the code,this appears like an array, so we just iterate through it, andadd each element to the list. The put() method checks that they are not already in it, to avoid duplicates. Then we can construct a set like:
GenSet<Integer> set1 = new GenSet<Integer>(1, 2, 3, 4, 5);
For union, we make a new set, and iterate through all our elements and add them. Then we iterate through the other set and add them. The put method will ignore duplicates:
public GenSet<T> union(GenSet<T> other) {GenSet<T> newSet = new GenSet<T>();for (GenNode<T> node = head; node != null; node = node.next) {newSet.put(node.data);}
for (GenNode<T> node = other.head; node != null; node = node.next){newSet.put(node.data);}
return newSet;}
For intersection, we go through all elements, and check they are also in the other set, in which case we add them:
public GenSet<T> intersection(GenSet<T> other) {GenSet<T> newSet = new GenSet<T>();for (GenNode<T> node = head; node != null; node = node.next) {if (other.contains(node.data))newSet.put(node.data);
}return newSet;}
We could use this like:GenSet<Integer> set1 = new GenSet<Integer>(1, 2, 3, 4, 5);GenSet<Integer> set2 = new GenSet<Integer>(3, 4, 5, 6);GenSet<Integer> set3 = set1.union(set2);set3.print(); // 6 1 2 3 4 5GenSet<Integer> set4 = set1.intersection(set2);set4.print(); // 3 4 5
How To Program © Walter Milner 2013 Page 103
Exercise
1. We have not given the code of the print method. Write it.
2. Write a difference method. The difference of two sets A B are the elements in A with the elements in B removed.
How To Program © Walter Milner 2013 Page 104
IteratorsWe have seen that iteration is a basic algorithmic tool, and simply means repeating something.
In the context of data structures, we are often concerned with a particular type of iteration, namely accessing the elements of the structure, and moving on to the next. The idea is to have an iterator object, which does these two things – accessing an element, and moving on to the next.
Using an index
The nonOO way to do this is to use an index, like
int[] data = {3,4,5,6,7};for (int index=0; index<data.length; index++){.. do something with data[index]..}
but this does not work with data structures without an index, such as a linked list.
Making an iterator
Suppose we want to create and use an iterator on our linked list class which holds integers. We would like to be able to say:
LinkedList list = new LinkedList();list.put(3);list.put(4);list.put(5);ListIterator iter = list.makeIterator();while (iter.hasNext())System.out.println(iter.next());}
That means we need to define a ListIterator class:
package dataStructures;
public class ListIterator {Node where;ListIterator(Node n){where=n;}public boolean hasNext() {return where!=null;}public int next() {int val=where.data;where=where.next;return val;}}
and we need to add a method to LinkedList to get an iterator:
How To Program © Walter Milner 2013 Page 105
public ListIterator makeIterator(){return new ListIterator(head);}
So our iterator has two methods – get the next element, andtest whether there is a next element.
In our code, next returns the value at node 'where', and then moves on. That means that at any time, 'where' actually points to the next node.
Interfaces
A Java class is a type of thing.
An interface is a set of abilities ( or a set of capabilities, or a set of processes, or some things something can do).
We define an interface by giving a set of methods, without bodies – no definitions of the methods, no saying how they are done..
For example, cars, bicycles, helicopters, trains, are all typesof things. They would all be classes.
Something they all have in common is moving. Being able tomove is a capability, so this is an interface:
public interface Mover {public void start();public void stop();}
When we define a vehicle, we can indicate it can move by declaring it as implementing the Mover interface. When we do that, we must also actually define the methods in the interface (or the compiler will flag an error):
public class Car implements Mover {double speed;public void start() {speed=30;}public void stop() {speed=0;}}
and
public class Bicycle implements Mover{double speed;public void start() {speed=8;}public void stop() {speed=0;}}
What have we gained by setting up the Mover interface?
How To Program © Walter Milner 2013 Page 106
In OOP we try to create computer models. That might be thephysical realities of buildings and streets and cars and so on, or financial situations, or computer situations like files and windows and buttons and users with passwords, or imaginary worlds in games, and so on.
A class models a type of thing. But another (slightly more subtle) aspect is to model abilities. Our Mover interface doesthat. It says anything that moves must be able to start and stop. It does not say how, since they are no method definitions (they are not allowed). But it defines the Mover abilities. The Mover interface might be implemented by Carsand Bicycles and Trains, but also People and Rabbits and RoadRunners.
We could just have defined start and stop methods for Car and Bicycle with no Mover interface. But defining and implementing the interface adds another aspect to our modelling ability and makes it more formal.
It also helps because if we say a class implements an interface, the compiler checks that it actually does define those methods, and tells us if it does not.
We cannot instantiate an interface – because the methods have no definitions. But we can have code that looks as if we do. We can say
Mover mover =.....
which makes it look like mover is an instance of Mover. In fact it means that mover is an instance of a class which implements Mover – not the same thing. In turn the compiler then knows that mover will be able to execute the Mover methods.
The Iterator interface
There is (in java.util) an Iterator interface, with methods next(), hasNext() and remove(). We can implement this interface so as to make a data structure create its own iterator. For example, to modify the generic linked list class defined earlier, we can say:
How To Program © Walter Milner 2013 Page 107
public class GenLinkedList <T> implements Iterator<T> {GenNode<T> head;
..previous methods..
GenNode<T> where;
public Iterator<T> iterator(){where=head;return this;}
@Overridepublic boolean hasNext() {return where!=null;}
@Overridepublic T next() {T val=where.data;where=where.next;return val;}
@Overridepublic void remove() {throw new UnsupportedOperationException();}}
and we can then use this as:
GenLinkedList<String> s=new GenLinkedList<String>();s.put("one");s.put("two");s.put("three");Iterator<String> iter=s.iterator();while (iter.hasNext())System.out.println(iter.next());
Remember we cannot instantiate an interface. In
Iterator<String> iter=...
it looks like iter is an Iterator object. Its not. It is an instance of a class which implements the Iterator interface. The iterator method of GenLinkedList says:
public Iterator<T> iterator(){where=head;return this;}
So it returns this, a reference to the executing object, and that is class GenLinkedList, which implements Iterator<T>. The compiler has checked this otherwise it would have flagged a syntax error.
Exercise
Code the remove() method. You will need another node pointer, to the one before the one pointed to by 'where'.
How To Program © Walter Milner 2013 Page 108
The Iterable interface
This has just one method – to create an Iterator. Our generic linked list already has that:
public Iterator<T> iterator(){..}
so we can already say:
public class GenLinkedList <T> implements Iterator<T>, Iterable<T>{...
but what's the point? Because if a class is Iterable, we can use the enhanced for loop on it:
GenLinkedList<String> s=new GenLinkedList<String>();s.put("one");s.put("two");s.put("three");for (String w:s) System.out.println(w);
This is just syntactic sugar. That is, a syntax feature which does not yield any extra functionality beyond what we couldalready do, but it makes the code look a lot cleaner and clearer.
But this is another example of how interfaces can make code more robust. The enhanced for only works on classes which implement Iterable, and this mechanism enables the compiler to check whether it will work
How To Program © Walter Milner 2013 Page 109
Trees
I THINK that I shall never see
A poem lovely as a tree.
Joyce Kilmer
A tree is a data structure like this:
That's it. More or less...
Terminology
The root node is 'at the top'.
A node has 0 or more descendant nodes or child nodes
A node which is not the root is a branch node if it has 1 or more descendants
A node with 0 descendants is a terminal node or a leaf node.
The set of nodes descending from a descendant of the root is itself a tree a subtree.
How To Program © Walter Milner 2013 Page 110
The depth of a tree is the number of nodes from the root to the lowest leaf.
In a binary tree, nodes have 0 1 or 2 nodes.
In a balanced tree , the number of nodes in all subtrees are equal.
Ordered Trees
A tree may be ordered in several ways (or not at all). The following shows the successive states of a tree as 5, 3, 8, 6 and 10 are inserted into it:
This tree is leftright ordered. This is often called a binary search tree. Everything to the left of a node is less than thedata at the node, and everything on the right is greater. A sketch of an algorithm to insert a new node to produce this ordering is:
A. Create a new node with the data
B. If the tree is empty, set the root to be this new node, and finish.
C. Start at the root
D. If data at the node is greater than that to be inserted, go left, else go right. Remember which way.
How To Program © Walter Milner 2013 Page 111
E. Repeat the last step until reaching the end (a null pointer).
F. Link the new node as a leaf node there (on the left or right, depending on which way we tried to go)
Exercise
On paper, draw what tree you get if you insert the following into an empty tree : 10, 5, 15, 3, 12, 8, 1, 20 using this algorithm.
Enums
In the insert algorithm, we have to remember if we go left orright. We could do this using an int flag, say using 1 as a code for left and 2 as a code for right:
int way;way=1; // if we went left orway=2; // meaning right
There are two problems with this. First, it involves magic number programming. We would have code like
if (way==1)...
and a maintenance programmer reading the code might be confused as to what 1 signified. The second problem is if there were some bug, we might have way becoming 3. That is a valid value for an int, so no problem would be shown, but does not make sense – it must be a bug. We want a better way which is more meaningful, and with only allowedvalues possible.
An enum, a special type of class, solves the problem:
enum Dir{LEFT, RIGHT}
Then we can declare a variable of this type (since it is a class) and assign values by symbolic name:
Dir way=Dir.LEFT;
then we get a syntax error if we try to assign an invalid value:
How To Program © Walter Milner 2013 Page 112
A generic Tree
A Java implementation might start as follows. We need a simple node:
class TreeNode<T> {T data;TreeNode<T> left;TreeNode<T> right;
TreeNode(T value) {data = value;left = null;right = null;}}
Then the Tree class starts:
package dataStructures;
public class Tree<T extends Comparable<T>> {TreeNode<T> root = null;enum Dir{LEFT, RIGHT}
What's the Comparable<T>?'
The Comparable interface
When we insert a value into the tree, we need to see if it more or less than that at a node:
if (where.data > val) {
but the data may not be a number, and we cannot compare them using >.
For example, we might be inserting Employee records into atree. What does it mean for one employee to be 'more than' another? Older? Higher salary? Alphabetical order of surname? Order of payroll number?
How To Program © Walter Milner 2013 Page 113
This is fixed by the Comparable interface. It only has one method – compareTo(). This should be coded to return a negative number if the instance is 'less than' another, a positive value if more than, and zero if equal. We write the code to reflect how we want instances of the class to be ordered. String implements Comparable, for example, and puts string objects in alphabetical order.
So in our tree insertion method, we compare values by
if (where.data.compareTo(val) > 0) {
We can only make the tree out of instances of a class which implements Comparable, which is why we have:
public class Tree<T extends Comparable<T>> {
The tree insertion could be :
public void insert(T val) {TreeNode<T> newNode = new TreeNode<T>(val);Dir way=Dir.LEFT;if (root == null) { // insertion in empty treeroot = newNode;return;}
TreeNode<T> where = root; // start at the rootboolean found = false;while (!found)if (where.data.compareTo(val) > 0) {way=Dir.LEFT;// we should go leftif (where.left == null)found = true; // we've reached the bottom
else // go leftwhere = where.left;
} else {way=Dir.RIGHT; // obviousif (where.right == null)found = true;
elsewhere = where.right;
}if (way == Dir.LEFT) // link new node on the leftwhere.left = newNode;
else // or rightwhere.right = newNode;
}
Tree traversals
Traversing a linked list is obvious we start at the head andgo through each next node until we reach the end. How to traverse a tree?
How To Program © Walter Milner 2013 Page 114
Trees are intrinsically recursive. We have a root node, then on the left and right, 2 things which are also trees. This suggests a recursive traversal (where we just output each node we come to)
public void visit(TreeNode<T> where){if (where.left!=null)visit(where.left);
System.out.println(where.data);if (where.right!=null)visit(where.right);
}
To traverse the whole tree, we start by visiting the root.
This is called an inorder traversal:
a. if there is a left subtree, visit it
b. deal with the data at this node
c. if there is a right subtree, visit it
The nodes in this tree are labelled with the order they wouldbe dealt with in:
We need to visit the root, so we need to make the root readable from outside the package, but we do not want to make it public. Instead we supply a getter method:
public TreeNode<T> getRoot(){return root;}
We could use this like
How To Program © Walter Milner 2013 Page 115
Tree<String> words=new Tree<String>();words.insert("One");words.insert("Two");words.insert("Three");words.insert("Four");words.insert("Five");words.visit(words.getRoot());
which outputs:
Five
Four
One
Three
Two
Is this correct?
Tree sort
This means we have a new sorting method. Insert the data into a leftright ordered tree, and then do an inorder traversal on it.
Is this a good method? What is it's time complexity?
When a node is inserted, we start at the root, and work our way down to the insertion point. The time is determined by the number of comparisons, which is the depth of a tree. Sohow deep is a complete tree with n elements?
depth tree Number ofnodes n
1 • 1
2 •
• •
3
3 •
• •
• • • •
7
4 •
• •
• • • •
• • • • • • • •
15
Each time we add another row, we add double the numbers in the row above.
So these number are 1, 1+2, 1 + 2 + 4, 1+2+4+8.. 1+... 2d1
This is the sum of a geometric progression (Google if you arenot familiar with the term) with first term 1, common ratio 2and d terms, and this is
(12d)/(12) = 2d1
so n = 2d1
How To Program © Walter Milner 2013 Page 116
For large n and d, n 2≈ d
Take logs to base 2 of both sides, and we have d = log(n)
To insert 1 node, we need log(n) comparisons. So to insert nnodes is O( n log (n) )
The traversal is also O( n log n ) so this is the time complexity of a treesort.
To check this out, inserting varying numbers of random
integers into a tree and counting the number of steps gives the following. Note that n log n is close to a straight line
Exercise
Modify the above code and check this yourself.
Breadth-first traversal
This is an alternative to an inorder traversal. The idea of this is to go to the root, then each node one down from the root, then each node one down from those, and so on:
This is easy to implement, using a FIFO queue:
enqueue the root
while queue is not empty
dequeue a node
enqueue its descendant nodes
How To Program © Walter Milner 2013 Page 117
and in Java this is:
public void breadthFirst() {// q is a queue of TreeNodes.GenQueue<TreeNode<T>> q = new GenQueue<TreeNode<T>>();q.enQueue(root);while (!q.isEmpty()) {TreeNode<T> val = q.deQue();System.out.println(val.data);if (val.left != null)q.enQueue(val.left);
if (val.right != null)q.enQueue(val.right);
}}}
used by
Tree<String> words=new Tree<String>();words.insert("D");words.insert("B");words.insert("A");words.insert("C");words.insert("Q");words.insert("M");words.insert("N");words.breadthFirst();
Exercise
1. For the last example, draw that tree on paper. What would the breadthfirst traversal sequence be? Check what the code produces.
2. This traverses each level left to right. Change it so it goes right to left.
Depth-first traversal
Suppose we use a stack rather than a queue? And push theright node before the left?
public void depthFirst() {GenStack<TreeNode<T>> stack = new GenStack<TreeNode<T>>();stack.push(root);while (!stack.isEmpty()) {TreeNode<T> val = stack.pop();System.out.println(val.data);if (val.right != null)stack.push(val.right);
if (val.left != null)stack.push(val.left);
}}
For example:
Tree<String> words=new Tree<String>();words.insert("4");words.insert("2");words.insert("6");words.insert("1");words.insert("3");words.insert("5");words.insert("7");
words.depthFirst();
outputs
4
2
How To Program © Walter Milner 2013 Page 118
1
3
6
5
7
Exercise
1. Is that correct?
2. Copy this tree and label the nodes with the sequence they would be visited on a depthfirst search:
Pretty printing a tree
This means to output a tree in a way which displays and reflects its structure, as in the diagrams we have been drawing.
This is not easy. The output will be done in one of two possible ways. We can use Java 'print', or in another language some characteroutput equivalent. Or in a graphical environment, there is usually a function to print text at an x,y position.
In the first case, we can only print text across and down. We must print the root node first. But how far 'across' the root should be will depend on the width of its left subtree.
In the second case, the print position can move up and down in any sequence. We still have the problem that the horizontal position of any node will depend on the width of the left subtree beneath it.
You might to try to solve this yourself first.
There are different ways to do this. Here is one way:
1. We include in a node a field for its depth so the root node has depth zero. The depth value is calculated when the node is inserted (and must be recalculated if the tree isaltered)
How To Program © Walter Milner 2013 Page 119
2. We do a breadth first traversal of the tree, and create an auxiliary data structure. This consists of a list of lists. Eachlist contains the nodes at the same depth a row across thetree. See the diagram. Whenever we find a node with a different depth, we start a new list.
3. Unless the tree is complete, there will be 'gaps' beneath where a node has a null left or right pointer. To handle this,we have a 'dummy' node which replaces a gap. Process (2) continues until we have a list containing nothing except dummy nodes.
The nth. element of the list of lists will have 2n elements like the first has 20 = 1 element, the root node. This is easilyprinted, just ensuring the width of each item is correct. Try to code this.
Balanced trees
Here are two trees, which differ only in the order in which the nodes are inserted:
The first tree is balanced. Such a tree with n nodes has depth log n, which is the average search time. The second tree is completely unbalanced. Such a tree has depth and search time n a disaster.
There is therefore a big advantage in ensuring our trees are pretty balanced. The two most common ways of doing that are AVL trees and redblack trees. But we will leave those foranother day.
How To Program © Walter Milner 2013 Page 120
Maps
A map is an abstract data type with just two operations:
put(key, value) : store a key, value pair in it
get(key): fetch the value associated with given key
The data we are dealing with consists of pairs of data. The first item in the pair is a key and the second item is a
value. This corresponds to a database table, with rows containing a key, and the value being the other fields. Keys must uniquely identify the pair. Two different pairs with thesame key are impossible. A key is chosen as something unique – a car license plate, a social security number, a passport number, a payroll ID and so on.
We put keyvalue pairs into the map. We use the key to retrieve it later.
Maps are sometimes called associative arrays. Normal arrays are accessed through a index. Associative arrays are accessed through a key, and you get back the value associated with that key.
Hash tables
A common way to implement a map is as a hash table. A hash table is a data structure designed to make it easy to find an item.
The idea is
1. When we insert an item, we calculate somehow where to place it. The calculation is based on the value of the key field.
2. To find an item, given its key, we simply repeat the calculation, and it will tell us where it is.
The calculation is called is called the hash function.
We can see the issues if we start to implement this in Java. For simplicity we will code a hash table where the keys are Integers and the values are Strings. Using generics we coulddeal with any types. We start by defining a data Pair:
How To Program © Walter Milner 2013 Page 121
class Pair{Integer key;String value;
Pair(){key=null;value=null;}Pair(int k, String v){key=k;value=v;}}
Here is a first version of our HashTable class:
package dataStructures;
public class HashTable {private static final int SIZE=100;private Pair[] data=new Pair[SIZE];
public HashTable(){for (int i=0; i<SIZE; i++){data[i]=new Pair();}
}
static int hash(int key){return (key*3)%SIZE;}
public void put(int key, String value){int location = hash(key);data[location]=new Pair(key, value);}
}
So our data will be stored in an array, with 100 elements, When we put key value pairs into the table, we use the hash function to calculate, from the key, a place to put it. And we put it there.
So that we can see what is happening, we'll have this to look at the start of the data
public void peek()
How To Program © Walter Milner 2013 Page 122
{for (int i=0; i<15; i++)System.out.println(i+" : "+data[i].key+" : "+data[i].value);
}
Let's try it out:
HashTable t = new HashTable();t.put(1,"First");t.put(2,"Second");t.put(3,"Third");t.peek();
which outputs:
0 : null : null
1 : null : null
2 : null : null
3 : 1 : First
4 : null : null
5 : null : null
6 : 2 : Second
7 : null : null
8 : null : null
9 : 3 : Third
10 : null : null
11 : null : null
12 : null : null
13 : null : null
14 : null : null
So key 1 goes in at 3. 2 goes at 6, and 3 goes at 9. In the hash function we have (key*3)%SIZE. That % is modulo, so that means we will always be in the range 0 to SIZE1, and it will fit in the array. For example for key 70 , key *3 is 210, and 210%100 is 10.
We can retrieve a value with a given key by:
public String get(int key) {int location = hash(key);return data[location].value;}
For example:
HashTable t = new HashTable();t.put(1,"First");t.put(2,"Second");t.put(3,"Third");System.out.println(t.get(2)); // Second
Collisions
A structure like this would be very fast, both for insertion and retrieval. Unfortunately there are a few issues.
Key 1 hashes to 3
Key 101 hashes to 303 % 100, which is also 3
Key 201 hashes to 603 % 100, which is also 3.
How To Program © Walter Milner 2013 Page 123
Many keys hash to the same location. This is called a collision. It is inevitable, with only a limited number of storage locations available, and the key being any integer value.
If we chose a different hash function, and used more storage, we would get fewer collisions. But unless we have infinite storage, we will always get some collions. How to deal with them?
There are many different techniques. One is simply put the data in the next available location.
public void put(int key, String value){int location = hash(key);while (data[location].key!=null)location++;
data[location]=new Pair(key, value);}
See what happens:
HashTable t = new HashTable();t.put(1,"A");t.put(2,"B");t.put(101,"C");t.put(201,"D");t.put(301,"E");t.peek();
0 : null : null
1 : null : null
2 : null : null
3 : 1 : A
4 : 101 : C
5 : 201 : D
6 : 2 : B
7 : 301 : E
8 : null : null
9 : null : null
10 : null : null
11 : null : null
12 : null : null
13 : null : null
14 : null : null
Key 1 went in at 3 and 2 at 6. For 101, it hashed to 3, but that was not empty, so it went in at 4, and 201 went in at 5.Similarly 201 hashed to 3, but it was not until 7 that it found an empty slot.
This means we need to change get slightly, since the data may not be at the hash location:
public String get(int key) {
How To Program © Walter Milner 2013 Page 124
int location = hash(key);while (data[location].key != null) {if (data[location].key == key)return data[location].value;
location++;}
return null;}
So we calculate the hash location, then start a linear searchfrom that location, until we find it, or we reach an empty location, in which case it is not present, and we return null:
HashTable t = new HashTable();t.put(1,"A");t.put(2,"B");t.put(101,"C");t.put(201,"D");t.put(301,"E");System.out.println(t.get(301)); // ESystem.out.println(t.get(3)); // null
Collisions mean insertions and retrievals will involve a calculation, and then a linear search. The longer the search,the slower the process.
Exercise
1. Try this out.
2. Insert from 10 to 90 random data values.
3. For each, measure the average retrieval search length
When the data gets to be somewhere near to being full (a large 'load factor'), there will be a lot of long and slow searches. One algorithm to deal with this is that when this happens a new larger storage area is obtained, a new hashing function is used, and existed data is copied into new storage.
For example if the data storage size is n, when the load factor exceeds 0.5, double n. The hash function could still be (3 * key )%n
Exercise
Try this out. SIZE will no longer be final.
Deletions
The obvious way to do this is
1. find the record
2. write a null record in its place at that location.
But suppose we have
How To Program © Walter Milner 2013 Page 125
0 : None
1 : None
2 : None
3 : (1, 'A')
4 : (101, 'C')
5 : (201, 'D')
6 : (2, 'B')
7 : (301, 'E')
8 : None
9 : None
10 : None
and we delete 201:
0 : None
1 : None
2 : None
3 : (1, 'A')
4 : (101, 'C')
5 : None
6 : (2, 'B')
7 : (301, 'E')
8 : None
9 : None
10 : None
Then we look for 301. This will start at 3, do a linear search, find the None at 5, and decide 301 was not in there.How to fix this?One way would be to have a dummy value of 'Deleted'. When we delete a record, we write Deleted, not None, over it.
Then on a retrieval, we know we must continue searching past a Deleted. But on an insertion, it is OK to write a new record over a Deleted.
Exercise
1. Code this
2. Google hash table, especially hash table wiki. See that hash functions are an active area of research.
The Collections Framework HashMap
In real code you would not normally write your own HashMap, since the Collections framework offers you one already written and tested in java.util:
import java.util.HashMap;
How To Program © Walter Milner 2013 Page 126
public class Test {public static void main(String[] args) {HashMap<Integer, String> map = new HashMap<Integer, String>();map.put(1, "One");map.put(2, "Two");map.put(3, "Three");map.remove(1);System.out.println(map.get(2)); // TwoSystem.out.println(map.get(1)); // null}}
We can also iterate through the map:
HashMap<Integer, String> map = new HashMap<Integer, String>();map.put(1, "One");map.put(2, "Two");map.put(3, "Three");for (Integer i: map.keySet())System.out.println(i+" : "+map.get(i));
How does it actually work? You can view the source code online. The code is here.
This is over 1000 lines with four authors, so is a major piece of work. Where we used a class Pair, the basic element there is Entry, which is an inner class:
687 static class Entry<K,V> implements Map.Entry<K,V> {
688 final K key;
689 V value;
690 Entry<K,V> next;
691 final int hash;
692 …...
The data is held in an array named 'table':
146 /**
147 * The table, resized as necessary. Length MUST Always be a power of two.
148 */
149 transient Entry[] table;
Some parts are very simple:
/**
280 * Returns the number of key-value mappings in this map.
281 *
282 * @return the number of key-value mappings in this map
283 */
284 public int size() {
285 return size;
286 }
287
288 /**
How To Program © Walter Milner 2013 Page 127
291 * @return <tt>true</tt> if this map contains no key-value mappings
292 */
293 public boolean isEmpty() {
294 return size == 0;
295 }
Others not so:
374 /**
375 * Associates the specified value with the specified key in this map.
376 * If the map previously contained a mapping for the key, the old
377 * value is replaced.
378 *
379 * @param key key with which the specified value is to be associated
380 * @param value value to be associated with the specified key
381 * @return the previous value associated with <tt>key</tt>, or
382 * <tt>null</tt> if there was no mapping for <tt>key</tt>.
383 * (A <tt>null</tt> return can also indicate that the map
384 * previously associated <tt>null</tt> with <tt>key</tt>.)
385 */
386 public V put(K key, V value) {
387 if (key == null)
388 return putForNullKey(value);
389 int hash = hash(key.hashCode());
390 int i = indexFor(hash, table.length);
391 for (Entry<K,V> e = table[i]; e != null; e = e.next) {
392 Object k;
393 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
394 V oldValue = e.value;
395 e.value = value;
396 e.recordAccess(this);
397 return oldValue;
398 }
399 }
400
401 modCount++;
402 addEntry(hash, key, value, i);
403 return null;
404 }
which uses the hash function:
257 /**
258 * Applies a supplemental hash function to a given hashCode,
259 * defends against poor quality hash functions. This is critical
260 * because HashMap uses power-of-two length hash tables, that
How To Program © Walter Milner 2013 Page 128
261 * otherwise encounter collisions for hashCodes that do not differ
262 * in lower bits. Note: Null keys always map to hash 0, thus index 0.
263 */
264 static int hash(int h) {
265 // This function ensures that hashCodes that differ only by
266 // constant multiples at each bit position have a bounded
267 // number of collisions (approximately 8 at default load factor).
268 h ^= (h >>> 20) ^ (h >>> 12);
269 return h ^ (h >>> 7) ^ (h >>> 4);
270 }
which relates to hashCode:
hashCode() and .equals()
All classes descend from the class Object, and Object has a method named hashCode. Consequently all classes have a hashCode method, which they may override (or not):
Object obj1 = new Object();
System.out.println(obj1.hashCode()); // gets 1084284121
This relates to the use of hash tables. It also relates to the use of .equals(). How are they related?
Firstly, what are their purposes? The .equals method is intended to tell if two instances are 'equal'. We usually needto code that. For example, suppose we have an employee class, with fields for payrollNumber and name. The payrollNumber identifies the employee:
class Employee{int payRollNumber;String name;Employee(int id, String n){payRollNumber =id;name=n;}}
but
Employee emp1 = new Employee(27,"John");Employee emp2 = new Employee(27,"John");
System.out.println(emp1.equals(emp2));
gives false – using .equals inherited from Object We need to override it:
public boolean equals(Object other){if (!(other instanceof Employee)) return false;return (payrollNumber==((Employee)other).payrollNumber);}
then two employees are equal if they have the same payroll number.
How To Program © Walter Milner 2013 Page 129
Now, hashCode is used in hash tables. But part of the contract of the method is:
If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
This make sense. We use the hashCode to decide where to store an object in the hash table, and where to get it from. So equal objects should give the same hashCode.
But if we override equals, and do not override hashCode, this will not work:
Employee emp1 = new Employee(27,"John");Employee emp2 = new Employee(27,"John");System.out.println(emp1.equals(emp2)); // trueSystem.out.println(emp1.hashCode()); // 1084284121System.out.println(emp2.hashCode()); // 16993205
So how should we override hashCode? Our equals method depends on payrollNumber, and nothing else. So long as our hashcode does the same, it will work:
public int hashCode(){return payrollNumber*31;}
now:
Employee emp1 = new Employee(27,"John");Employee emp2 = new Employee(27,"John");System.out.println(emp1.equals(emp2)); // trueSystem.out.println(emp1.hashCode()); // 837System.out.println(emp2.hashCode()); // 837
This is to ensure we can use Employee in a hashmap. Does it work?
Employee emp1 = new Employee(27,"John");HashMap<Integer, Employee> map = new HashMap<Integer, Employee>();map.put(emp1.payrollNumber, emp1);Employee emp2 = map.get(27);System.out.println(emp1.equals(emp2)); // true
It works – but is it good? How do we choose a good hashing function? If you Google that phrase, you will get many lectures on the question. It needs to be fast to calculate, and it should minimise the number of collisions we get in the hash table – since that will slow access down. We cannot avoid collisions but we can try to minimise them. This depends on the statistical distribution of the key field. For example the English surname 'Smith' is very common, and so a hashing function depending only on surname will produce many collisions and be slow.
But it does not matter much.
The line in HashMap.java:
389 int hash = hash(key.hashCode());
How To Program © Walter Milner 2013 Page 130
shows it does not simply use the hashCode as the hashing function. It uses it and then applies its internal hash to that, with the result that you never get more than 8 collisions.
Memoization
Suppose we have a method which needs to do a lot of computation, and which we need to call often. We can speedthis up if after the result is found, it is stored somewhere in a local cache. Then if the method is called again with the same argument, we look up the result, rather than repeating the calculation.
In Java we can use a map as the cache structure. In the following example we have a sine function, and use the Math.sin() to calculate it if we do not already have it. We treat the argument as the angle in degrees.
C can make variables local to functions static, which means they retain their values between function calls. Java has no equivalent, so we make the cache to be a static data member of a class (the keyword static is very confusing, since it has several different meanings in C, and yet another(per class not per object) in Java).
class Funcs{static TreeMap<Integer, Double> sine = new TreeMap<Integer, Double>();public static double sin(int angle){// if we have it already..if (sine.keySet().contains(angle))return sine.get(angle);
// otherwise calculate itdouble rad= angle*Math.PI/180;double result=Math.sin(rad);// cache the resultsine.put(angle,result);// and return itreturn result;}}
which we can use as:
System.out.println(Funcs.sin(0));System.out.println(Funcs.sin(0));System.out.println(Funcs.sin(30));System.out.println(Funcs.sin(30));System.out.println(Funcs.sin(90));System.out.println(Funcs.sin(90));
calling each twice, once calculated and the second looked up.
How To Program © Walter Milner 2013 Page 131
Our sine function has limited resolution – we can find sin(5)or sin(6) (degrees), but not sin(5.04). But this implementation would be OK if speed and limited accuracy was required. This would be the case in a video game, where we would need sine for rotations, it would need to be very fast, but 1 degree accuracy would be sufficient.
Memoization gains speed at the cost of increasing memory use, which is a common situation.
Exercise
1. The cache eventually holds entries for degree values 0 to 360 (and may be more). In fact we only need 0 to 90, since the sine of any other angle is equal to a value in this range. For examples – sin(95)=sin(85), sin(365)=sin(5), sin(5)=sin(5).
Include this in the function so we only cache 090
2. This function should become faster with use, as the percentage of cache hits increases. Test whether this is actually the case.
How To Program © Walter Milner 2013 Page 132
Graphs
This might be a road map, with the letters marking towns, and the numbers might be the travel times between locations.
We might ask is C next to D? Whattowns are next to
G? What is the fastest route from A to G? Or what quickest journey would visit every town?
But this might also be a computer network, and a router might need to answer such questions about locations on a network.
Or it might be a project management diagram, and we might want to know what is the critical path for the project.
Something like this is a graph. (Different use of the word from a Cartesian xy graph of a function).
A graph is an abstract data type, with nodes or vertices, andedges. Here are some terms used with graphs:
simple Only one (or zero) edges between two nodes
directed The edges have arrows. A graph is undirected if they don't
connected Two nodes are connected if there is a path between them. A graph is connectedif there is a path between every pair of nodes. Otherwise the graph is cut into two or more unconnected parts
cycle A set of edges which start and end at the same node
cyclic graph
Contains at least one cycle. An acyclic graph has no cycles.
In these terms, a tree is a type of graph – a connected acyclic graph.
How To Program © Walter Milner 2013 Page 133
Basic graph operations include testing whether two vertices are adjacent, listing all the neighbors of a vertex, and adding and deleting edges.
Representations
There are several ways of representing a graph. One way is an adjacency list. Each vertex has a list of its neighbors, and the adjacency list is a list of these lists.
We will show a slightly different different method. A Graph object contains Edge objects, and an Edge links Node objects. So an OOP model of a graph needs Node and Edge
classes. A definition of Graph, using code folding to hide details, is shown here.
How To Program © Walter Milner 2013 Page 134
Node is an inner class, so we do not need to worry about a clash with Node in any other namespace:
class Node {String label;Node(String s) {label = s;}
public boolean equals(Object obj) {if (!(obj instanceof Node))return false;
return label.equals(((Node) obj).label);}
}
and our Edge uses Node:
class Edge implements Comparable<Edge> {Node start;Node end;int weight;
Edge(Node s, Node e, int w) {start = s;end = e;weight = w;}
public String toString() {return start.label + "-" + end.label + " ";}
We will need to compare edges, in terms of their weight, so we need to implement Comparable:
@Overridepublic int compareTo(Edge other) {int otherWeight = ((Edge) other).weight;if (weight < otherWeight)return -1;
if (weight > otherWeight)return 1;
return 0;}
}
A Graph starts off:
public class Graph {ArrayList<Node> nodes = new ArrayList<Node>();;ArrayList<Edge> edges = new ArrayList<Edge>();;
public Graph(Node n) {nodes.add(n);}..
so a Graph has a list of vertices, and a list of edges, and we provide a constructor to make a graph from just one node. Here is a simple test graph:
How To Program © Walter Milner 2013 Page 135
We will use the class like:
Graph g = new Graph("AC3AB2BC1CD4BD8BE9ED7");System.out.println(g);
So the constructor takes a string containing triples <label><label><weight>:
public Graph(String s) {int charPos = 0;while (true) {char c = s.charAt(charPos);String s1 = "" + c;Node n1 = new Node(s1);if (!nodes.contains(n1))nodes.add(n1);
charPos++;c = s.charAt(charPos);String s2 = "" + c;Node n2 = new Node(s2);if (!nodes.contains(n2))nodes.add(n2);
charPos++;c = s.charAt(charPos);int w = c - '0';Edge e = new Edge(n1, n2, w);if (!edges.contains(e))edges.add(e);
charPos++;if (charPos == s.length())break;
}}
This is restrictive ie labels only 1 character, and weights only 0 to 9. But it is convenient. toString is
public String toString() {String s="Graph - Nodes ";for (Node n:nodes)s+=n.label+" ";
if (!edges.isEmpty()){s+=" Edges: ";
for (Edge e : edges)s+=e + " ";}
return s;}
so the output for our test graph is:
Graph - Nodes A C B D E Edges: A-C A-B B-C C-D B-D B-E E-D
This representation is potentially inconsistent – the list of edges could involve a node not in the list of nodes. But it does cater for disconnected graphs.
Exercise
1. With this representation, write a method to display a list of all the neighbors of a vertex with a given label,
2. and whether two vertices are adjacent.
How To Program © Walter Milner 2013 Page 136
Kruskal's Algorithm
The task is to find a minimal spanning tree (mst). That is, a set of edges which connect all vertices in a tree, and which has minimal total weight.
Exercise
Try to work out the mst for our test graph
The algorithm is simply:
1. Put the edges into increasing order by weight
2. Create a set of graphs, each consisting of a single vertex from the graphs
3. Then for each edge in turn, if it links two graphs in the set,combine those graphs.
Suppose we do this for our test graph above. The edges in order of increasing weight are:
BC BA AC CD DE DB BE
Our initial set of graphs, with just single nodes, is
A B C D E
Then we do step 3. BC obviously links B and C, so our set of graphs becomes
A BC D E
BA links A with BC, so we get
ABC D E
AC does not link any of these 3 graphs
CD links ABC and D
ABCD E
then DE
ABCDE
DB and BE do not link anything.
So ABCDE is our mst.
Coding Kruskal
Kruskal produces a mst, which is a graph, so the outline is
How To Program © Walter Milner 2013 Page 137
public Graph kruskal() {..return .. the mst graph;}
For step one, the Collections class offers a static method to sort a collection, so long as it implements Comparable:
Collections.sort(edges);
For step two, we need a collection of Graphs, with just one node:
ArrayList<Graph> graphs = new ArrayList<Graph>();for (Node n : nodes)graphs.add(new Graph(n));
For step three, we must iterate through the sorted edges. For each edge, we must check if it links any pair of graphs –which means a nested loop to check each pair. If we find a pair it does link, we join them, and go on to the next edge.
When we join two graphs g1 and g2, we merge g2 into g1, and we need to remove g2 from the set. However removing an item from a collection as you iterate through it produces an Exception. So, we copy the set, remove the g2 from the copy, then copy the set back:
for (Edge e : edges) {ArrayList<Graph> copy = (ArrayList<Graph>) graphs.clone();for (Graph g1 : graphs) {boolean flag = false;for (Graph g2 : graphs) {if (Graph.links(e, g1, g2)) {System.out.println("Join on "+e+" from "+g1+"to "+g2 );g1.join(g2, e);copy.remove(g2);flag=true;break;}
}if (flag) break;}
graphs = (ArrayList<Graph>) copy.clone();}
The println is there mostly for debugging. The .clone method makes a shallow copy. That means it creates a newArrayList, and for each object refernced in 'graphs', it creates a copy reference in the 'copy' list. But it does not create copies of the Graph objects in the list. In this case, that's fine.
After this, 'graphs' should contain just one graph, which is what we return:
return graphs.get(0);}
How To Program © Walter Milner 2013 Page 138
A sample run is:
Graph g = new Graph("AC3AB2BC1CD4BD8BE9ED7");Graph k = g.kruskal();System.out.println(k);
which outputs:
Join on B-C from Graph - Nodes B to Graph - Nodes C
Join on A-B from Graph - Nodes A to Graph - Nodes B C Edges: B-C
Join on C-D from Graph - Nodes A B C Edges: B-C A-B to Graph - Nodes D
Join on E-D from Graph - Nodes E to Graph - Nodes A B C D Edges: B-C A-B C-D
Graph - Nodes E A B C D Edges: B-C A-B C-D E-D
How To Program © Walter Milner 2013 Page 139
Graphics AlgorithmsThis section is about some algorithms relating graphics (notgraphs).
Background
Code to output to a graphics device is very dependent upon the hardware and system software. Consequently it varies alot across platforms, and so general purpose languages do not have primitives to handle graphics they are not 'builtin' to the language itself. Instead, the usual approach is to use a graphics library to provide this facilities. The library might be device specific, or it might be possible to use the same commands on a range of different devices by using library versions for these different devices.
For Java, there are several graphics libraries available. A commonly used one is Swing. It offers a range of 'widgets' like windows, text input boxes, labels, buttons and so on. We will only show the minimum needed to program some graphics.
Graphics output is usually done in 1 of 2 ways. The most common is as a rectangular array of dots, which are pictureelements or pixels. Each pixel has a colour. The colour is often specified by giving red, green and blue components, each in the range 0 to 255 the values which will fit into
one byte. Pixels have x,y coordinates, and x is 'across' as usual, but y is often measured down from the top of the screen or a window. So we make a graphics image by colouring the dots.
The second way is by using vector graphics. This uses lines rather than dots. However most output devices lcd, led or plasma physically use pixels, and vector graphics are usually converted to dots.
A Swing window
The Swing class JFrame models a standard GUI window object, with borders, moveable, resizable and so on. It is common to subclass a JFrame to customize it to what we want. We will call ours MyWindow:
How To Program © Walter Milner 2013 Page 140
package graphics;
import javax.swing.JFrame;
public class MyWindow extends JFrame {
public MyWindow(){setSize(500,500);setTitle("This is a title");setVisible(true);}
}
Starting a Swing application must be done correctly. This isbecause Swing uses a separate 'eventdespatching thread' to handle events like resizing a window or clicking a button. To work properly, code to control a Swing application must run in that thread. This does that:
import graphics.MyWindow;
import java.awt.EventQueue;
public class Test {
public static void startGUI(){MyWindow win = new MyWindow();
}
public static void main(String[] args) {EventQueue.invokeLater(new Runnable() { public void run() { startGUI(); } });}}
This produces the following on Ubuntu 12.04 Linux. The exact appearance will depend on the platform.
How To Program © Walter Milner 2013 Page 141
Drawing an image
The Swing class BufferedImage represents an image. It is buffered in the sense that it is held in a rectangular array ofpixels, which can be manipulated, which is why we will use it.
We modify the constructor:
public class MyWindow extends JFrame {BufferedImage img;
public MyWindow() {setSize(500, 500);setTitle("This is a title");
img = null;ClassLoader classLoader = Thread.currentThread().getContextClassLoader();InputStream input = classLoader.getResourceAsStream("t.jpg");
try { img = ImageIO.read(input) ;} catch (IOException e) {e.printStackTrace();}
setVisible(true);}
This creates the BufferedImage by reading the graphics file named 't.jpg', which should be in the same folder as the MyWindow class file.
We also add a paint method to the MyWindow class:
How To Program © Walter Milner 2013 Page 142
public void paint(Graphics g) {Graphics2D g2 = (Graphics2D) g;g2.drawImage(img, 0,0, null);}
paint is a callback method. When the window is initially drawn, or when it needs to be redrawn after a move or resize or whatever, the system calls 'paint' to draw it. We do not call 'paint'. In this case, paint justs draws the image. The result is:
We will now manipulate this image – but to do so we must manipulate the bits in the pixels, for which we need the bitwise operators.
Bitwise operators
Everything in digital systems is represented by patterns of binary digits – 0s or 1s – bits. This includes program code and all data, which means numbers, text, sound, graphics and the rest. An int, for example, is encoded as a sequence of 32 bits. Usually we treat the data as an int, and need not worry about the bits. But sometimes we do need to do things with the bits, and most languages, including Java, have bitoriented operators to do this.
Full details of number bases and digital data representationin Java is available here.
The bitwise operators take 1 or 2 bit patterns, and produce a new bit pattern as the result. The result is formed by applying the operation to each pair of bits. & is AND, | is OR. For example:
int b1=0B11111010; int b2=0B11111100; int b3 = (b1 & b2); String r=Integer.toBinaryString(b3); System.out.println(r); // 1111 1000
This uses 'binary literals' introduced in Java 7. You might need to update.
Why 1111 1000? We are ANDing pairs of bits:
How To Program © Walter Milner 2013 Page 143
b1 1 1 1 1 1 0 1 0
b2 1 1 1 1 1 1 0 0
b3 1 1 1 1 1 0 0 01 A
ND
1 =
1
0 A
ND
1 =
0
1 A
ND
0 =
0
0 A
ND
0 =
0
Bitwise OR is:
int b1=0B11111010; int b2=0B11111100; int b3 = (b1 | b2); String r=Integer.toBinaryString(b3); System.out.println(r); // 1111 1110
The bitwise NOT is ~
int b1=0B11111111_11111111_11111111_11111010; int b3 = ~b1; String r=Integer.toBinaryString(b3); System.out.println(r); // 101
We need to explain this. The Java 7 binary literal is of type int, and so has 4 bytes = 32 bits. When we say
int b1=0B11111010;
the leading bits are taken to be 0s, so this is actually 0000 0000 1111 1010.
We want the leading bits to be 1's, so we say
int b1=0B11111111_11111111_11111111_11111010;
This also uses the feature of being able to put in _ to track the bit positions.
When we ~ this, all those 1s turn to 0s. The 3 least significant bits 010 are inverted to 101, which is output. toBinaryString omits leading 0s.
The XOR bitwise operator is ^.
Bit shifts
The bit shift operators produce a new bit pattern by moving the bits in an input pattern left or right a number of places. >> shifts to the right, and << to the left. For example:
int a = b << 4;
means a has the same bits as b, but shifted 4 places to the left.
We can use a bit shift to write a better routine to convert anint to a displayable binary pattern.
A better binary
Integer.toBinaryString suppresses leading 0s, and does not insert spaces every 4 bits. Here is a better version:
How To Program © Walter Milner 2013 Page 144
static String binary(int i) { StringBuilder s = new StringBuilder(); for (int bitPlace = 0; bitPlace < 32; bitPlace++) { int bit = i & 1; i >>= 1; if (bitPlace % 4 == 0) { s.append(" "); } s.append(bit); } s = s.reverse(); return new String(s); }
This forms the result in 's'. A StringBuilder is like a String, but is mutable so a lot more efficient here.
We have a loop going round 32 places, since we know ints have 32 bits.
int bit = i & 1;
This uses the idea of a bit mask (explained more later). We are ANDing each bit in i with 1, which in binary will be 00000..001. If you AND a bit with 0, you get 0. If you AND itwith 1, you get the bit unchanged ( ) AND 1 = 0, 1 AND 1 = 1). So i & 1 makes every bit 0, except for the rightmost one,which is unchanged. So i & 1 gets us the rightmost bit.
i >>= 1;
is the same as
i = i >> 1;
so it shifts the bits in i right one place.
s.append(bit);
puts our bit on the end of s.
if (bitPlace % 4 == 0) { s.append(" "); }
puts a space in the result every four places to make it more readable.
The append builds up the bits left to right, so we need
s = s.reverse();
Finally
return new String(s);
returns a new String constructed from the StringBuilder s.
For example
int b1 = 0B1010; System.out.println(binary(b1)); // get 0000 0000 0000 0000 0000 0000 0000 1010 int b2 = b1 << 4; System.out.println(binary(b2)); // get 0000 0000 0000 0000 0000 0000 1010 0000
Bit masks
A bit mask is a bit pattern which we AND or OR with a given pattern, to isolate or change specified bits.
How To Program © Walter Milner 2013 Page 145
If you AND something with a bit mask, you force to zero those bits where you have a 0, and leave the rest unchanged. For example:
int b1 = 0B1010; System.out.println(binary(b1)); // 0000 0000 0000 0000 0000 0000 0000 1010 int b2 = b1 & 0B0011; System.out.println(binary(b2)); // 0000 0000 0000 0000 0000 0000 0000 0010
If you OR something with a bit mask, you force bits to 1 where the mask has a 1:
int b1 = 0B1010; System.out.println(binary(b1)); // 0000 0000 0000 0000 0000 0000 0000 1010 int b2 = b1 | 0B11110000; System.out.println(binary(b2)); // 0000 0000 0000 0000 0000 0000 1111 1010
If you XOR a bit mask, you invert the 1 bits. If you do it twice, you get back to where you started from
int b1 = 0B1010; System.out.println(binary(b1)); \\ 0000 0000 0000 0000 0000 0000 0000 1010 int b2 = b1 ^ 0B11111111; System.out.println(binary(b2)); \\ 0000 0000 0000 0000 0000 0000 1111 0101 b2 = b2 ^ 0B11111111;
System.out.println(binary(b2)); \\ 0000 0000 0000 0000 0000 0000 0000 1010
Pixel bit manipulation
BufferedImage can handle the colour of the pixel as an int, withthe red green and blue components as 8 bit fields. So yellow, forexample, is coded in the 32 bits of an int as 00 FF FF 00 – the red component is ff = decimal 255, green is ff = 255, and blue iszero.
So if we AND a pixel with 0x0000ff, we will force red and green components to zero, and be left with just the blue:
public void process() {for (int row = 0; row < img.getHeight(); row++) {for (int col = 0; col < img.getWidth(); col++) {int pixel = img.getRGB(col, row);pixel = pixel & 0x0000ff;img.setRGB(col, row, pixel);}
}}
which is used like:
How To Program © Walter Milner 2013 Page 146
MyWindow win = new MyWindow();win.process();
to produce
Exercise
1. What would
int pixel = img.getRGB(col, row);pixel &= 0x00ff00;img.setRGB(col, row, pixel);
do in the above loop?
2. Alter this to obtain just the red component.
This code rotates the red green and blue components:
public void process() {for (int row = 0; row < img.getHeight(); row++) {for (int col = 0; col < img.getWidth(); col++) {int pixel = img.getRGB(col, row);int red = (pixel & 0xff0000) >> 16;int green = (pixel & 0x00ff00) >> 8;int blue = pixel & 0x0000ff;pixel = (blue << 16) + (red << 8) + green;img.setRGB(col, row, pixel);}
}}
How To Program © Walter Milner 2013 Page 147
Using transparency
An alternative pixel format for a BufferedImage is RGBA. As wellas the red green and blue components, each pixel also has an alpha component for transparency, with 0 being fully transparent and ff fully opaque. The alpha component is in bits 24 to 31.
Our image is RGB because we constructed it from a loaded jpg. We can write that into an RGBA image:
public class MyWindow extends JFrame {BufferedImage img;BufferedImage rgbaImage;
public MyWindow() {setSize(500, 500);.. as before to read img..rgbaImage = new BufferedImage(img.getWidth(null), img.getHeight(null),
BufferedImage.TYPE_INT_ARGB);Graphics2D g2 = rgbaImage.createGraphics();g2.drawImage(img, 0, 0, null);g2.dispose();setVisible(true);}
then in paint we draw rgbaImage not img.
We can then manipulate the alpha:
public void process() {for (int row = 0; row < rgbaImage.getHeight(); row++) {for (int col = 0; col < rgbaImage.getWidth(); col++) {int pixel = rgbaImage.getRGB(col, row);int transparency = (int) (col * 255.0 / rgbaImage.getWidth());pixel = (transparency << 24) + (pixel & 0x00ffffff);rgbaImage.setRGB(col, row, pixel);}
}}
this means the transparency varies from 0 at the left to ff at theright:
How To Program © Walter Milner 2013 Page 148
Exercise
Alter this so the image is opaque at the centre and transparent at the edge.
Plotting a pixels
Swing widgets like text fields are not drawn one pixel at a time. But plotting single pixels is the foundation of graphics, so we want to be able to do that. We modify the window class:
package graphics;
import ..
public class MyWindow extends JFrame {BufferedImage img;public MyWindow(){setSize(500,500);setTitle("This is a title");setVisible(true);img = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB );}
public void paint(Graphics g) { Graphics2D g2 = (Graphics2D) g; g2.drawImage(img, null, null); }
public void plotPixel(int x,int y,int col){img.setRGB(x, y, col);}}
A BufferedImage is a 2D image, usually used to display an image file. We use it because we can set pixels on a BufferedImage. We create one 500X500 pixels in size.
plotPixel justs calls setRGB to plot a pixel on the image.
We can use this like:
How To Program © Walter Milner 2013 Page 149
MyWindow win = new MyWindow();win.plotPixel(50,50,0x00ff00);win.plotPixel(54,50,0xffff00);win.plotPixel(58,50,0xff0000);
which outputs:
The color parameter to plotPixel encodes red green and bluecomponents with the blue as the lower 8 bits, green as the next, and red as the next. It is then easiest to write this in hex. So
0x0000ff is pure blue
0x00ff00 is pure green
0xffff00 is red+green = yellow and so on.
Line drawingalgorithms
Once we can plot asingle pixel how canwe draw a line?
We will look at twoalgorithms to do this,firstly a basic simpleone, then an improvedone.
We want to draw the line from point (x0,y0) to (x1,y1). The slope of that line is
(y1y0)/(x1x0),
and the equation of the line through those two points is
y = y0+ slope * x.
(you need a little coordinate geometry to follow this).
We will iterate x values from x0 to x1, in steps of 1, so our xvalues are integer coordinates. At each step, the change in y is dy = slope X dx. But dx is 1, so dy=slope, and y changeslike y = y + slope.
How To Program © Walter Milner 2013 Page 150
When we move from one x to the next, the y coordinate of the plotted pixel is the same as the last, or maybe one more.(Why cannot it be 2 or more? See below). In the diagram, the first pixel plotted is at (x0,y0), shown in red. The next pixel to the right is at x=x0+1, But should we plot this at the same y0 (the yellow one), or at y0+1 (the green one)? If yis closer to y0+1 than y0, we should plot the green one.
We decide by working out the error, between the exact y value, and the current pixel y. If this is less than 0.5, the lower pixel is closer otherwise we plot the upper one.
public void line(int x0, int y0, int x1, int y1, int col){double slope=(double)(y1-y0)/(x1-x0);double exacty = y0;int y = y0;for (int x = x0+1;x<x1+1; x++ ){exacty=exacty+slope;double diff=y-exacty;if (diff<0.5)y=y+1;
plotPixel(x,y,col);}
}
We can use this for example
MyWindow win = new MyWindow();
win.line(0,0,100,50, 0xff0000);
which produces this:
How To Program © Walter Milner 2013 Page 151
(we are measuring y down from the top of the window).
We make two developments from this.
Suppose we say
win.line(0,100,100,50, 0xff0000);
then we get:
This is because y either stays as it is, or we increase it by 1.There is no option for y to decrease, so if y0>y1 it will not work.
We can fix this by checking if y0<y1 or not, and setting a flag showing if we are going up or down. Then in the loop weincrease or decrease y according to the flag.
Exercise
Modify the code according to this plan
Secondly suppose we draw a line from 0,0 to 50,150 in other words, a steep line. In fact we get this a line with slope=1:
We only increase y by 1 each step, so we cannot get a slope more than 1.
How To Program © Walter Milner 2013 Page 152
See the diagram at the left. If we iterate through x, we only have 3 x values, so only 3 pixels. If we iterate through y values, we will get 7 points, and 7 pixels.
So if the line is steeper than 1 (if y1y0 > x1x0), it is better to iterate through y, and calculate x,than to iterate on x and calculate
y.
Exercise
Modify the code again to achieve this.
An integer-arithmetic only line
The problem with this is that y and slope are floating point numbers, so y=y+slope involves floating point arithmetic, and this is relatively slow. It would be much faster if we could only use integer arithmetic and since pixel coordinates must be integer, this must be possible. The
time to draw 1 line is negligible, but in a game with 3D graphics we must draw millions of lines every second, so speed is very important. So we look for an algorithm with only integer arithmetic in the loop.
We can do this if we scale up the variables involved, by a factor of (x1x0), except for the x and y pixel coordinates plotted:
public void line(int x0, int y0, int x1, int y1, int col) {
int scale = x1 - x0;int slope = y1 - y0; // real slope = this slope/scaleint exacty = y0 * scale; // real exacty = y0int limit = scale / 2; // real limit is 0.5int y = y0;for (int x = x0 + 1; x < x1 + 1; x++) {exacty = exacty + slope; // real exacty = this / slopeint diff = y * scale - exacty;if (diff < limit)y = y + 1;
plotPixel(x, y, col);}
}
The version with floatingpoint arithmetic is sometimes called DDA, and the integerarithmetic version is Bresenham's algorithm. But on the web you will find integerDDA, and floatingpoint Bresenham.
How To Program © Walter Milner 2013 Page 153
In practice, this is usually done not in the core processor, but in a graphics processor. It will usually use Bresenham'sinteger algorithm, but implemented in hardware rather thansoftware, so that it is extremely fast and will do this in 3D millions of times per second.
Anti-aliasing
Drawing a line as a set of pixel dots works perfectly for vertical and horizontal lines. But lines at any other angle produce the jaggies. For example
win.line(0,50,300,55, 0xffff00);
is almost horizontal, and produces when magnified:
Antialiasing tries to reduce the problem of the jaggies. There are many approaches. We will show an approach where instead of plotting one pixel at one x coordinate, we plot 3 – one above and one beneath the one closest to the exact y position. And we weight the three pixels with a
colour intensity reflecting how far they are from the exact position. We mulitply them by a scale factor which is smaller if they are further away. But to do that we need to use some bit operations.
Let's see what happens if we scale yellow by a factor of 0.12decimal. ffff00 in hex = decimal 16 776 960. If we mulitply by .12 we get 2 013 252.2, and if we turn this back to hex we get 1e b8 33
But suppose we multiply the separate components by 0.12. These are 255, 255 and 0. Multiplied by 0.12 gives 30.6 30.6 0, which in hex is 1e 1e 00 which is completely different green and blue components.
What we need to do is to separate the red green and blue components, multiply them separately by a scale factor, then put them back together again. How do we do that?
public int scaledColor(int col, double scale) {int blue = col & 0xff; // get lower 8 bits// get bits 8 to 15, shifted to the rightint green = (col & 0xff00) >> 8;// get bits 16 to 23, shifted to the right
int red = (col & 0xff0000) >> 16;// mulitply components by scale factorblue = (int) (blue * scale);green = (int) (green * scale);red = (int) (red * scale);// re-assemblereturn (red << 16) + (green << 8) + blue;}
How To Program © Walter Milner 2013 Page 154
An anti-aliasing algorithm
We draw a line iterating across x, calculating an exact y value each time (named yx). We then find the nearest integer y value ( y ) and plot that pixel, and y1 and y+1, thepixels above and below that one. We modify the three pixels'color depending on how far away they are from yx.
The diagram shows the 3 pixels, at y, y1 and y+1, and the exact position yx. Each pixel has size 1. d1 d2 and 3 are thedistances of the centres of the pixels from yx.
We have:
y = (int) yx
d1 = yx+y+1/2
d2=yxy1/2
d3=y+1.5yx
We scale each pixel inverselyproportional to d1, d2 and d3, scaled so the total weighting is 1. In other words the weights are k/d1, k/d2 and k/d3, where
1 = k/d1 + k/d2 + y/d3
so
y = 1 /( 1/d1 + 1/d2 + 1/d3)
So our code is as follows:
public void lineAnti(int x0, int y0, int x1, int y1, int col) {double slope = (double) (y1 - y0) / (x1 - x0);double yx = y0;for (int x = x0 + 1; x < x1 + 1; x++) {yx = yx + slope;int y = (int) yx;double d1 = (yx - y + 0.5);double d2 = Math.abs(y + 0.5 - yx);double d3 = (y + 1.5 - yx);double k = 1.0 / (1 / d1 + 1 / d2 + 1 / d3);int c1 = scaledColor(col, k / d1);int c2 = scaledColor(col, k / d2);int c3 = scaledColor(col, k / d3);plotPixel(x, y - 1, c1);plotPixel(x, y, c2);plotPixel(x, y + 1, c3);}
}
If we run this:
MyWindow win = new MyWindow();win.line(0,50,300,55, 0xffff00);win.lineAnti(0,60,300,65, 0xffff00);
we get:
How To Program © Walter Milner 2013 Page 155
Floodfill
Most graphics programs have a 'floodfill' tool. You click a point in an image, and it 'pours paint' there, spreading out until it reaches another color. How to do that?
One way is using recursion. In pseudocode:
floodfill(x,y, targetColor, fillColor):
get pixel colour at x,y
if colour is not targetColour, return
set pixel at x,y fillColour
floodfill(x+1,y, targetColor, fillColor)
floodfill(x1,y, targetColor, fillColor)
floodfill(x,y+1, targetColor, fillColor)
floodfill(x,y1, targetColor, fillColor)
so we check the starting pixel, and if it is not target colour, we do nothing, and stop. Otherwise we plot that point, and recurse on the 4 pixels around it, north south east and west.
How to do this in Java? We need an area to fill:
public MyWindow() {.. img = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB); Graphics2D g=img.createGraphics(); g.setColor(Color.red); g.fillRect(50,50,100,100); g.fillRect(75,75,200,300);}
public void paint(Graphics g) {Graphics2D g2 = (Graphics2D) g;g2.drawImage(img, null, null);}
which outputs:
How To Program © Walter Milner 2013 Page 156
Then floodfill is:
public void floodfill(int x, int y, int target, int fill){int pixelColor=img.getRGB(x,y);if (pixelColor!=target) return;img.setRGB(x, y, fill);floodfill(x+1,y, target, fill);floodfill(x-1,y, target, fill);floodfill(x,y+1, target, fill);floodfill(x,y-1, target, fill);}
which we can use by
MyWindow win = new MyWindow(); win.floodfill(60,60,0xffff0000, 0xff0000ff);
which outputs this,
and the error message:
Exception in thread "AWT-EventQueue-0" java.lang.StackOverflowError
For each recursive call, the return address is pushed on thesystem stack and not popped off until the call ends. We have a lot of pixels, so we use a lot of stack space more than the JRE allows. So we get some pixels filled, but not
How To Program © Walter Milner 2013 Page 157
all. We could try to fix this by allocating more stack space, or we can use our own stack – as an instance of java.util.LinkedList. We first define an inner class in MyWindow:
class Pair {int x;int y;Pair(int a, int b) {x = a;y = b;}
}
and use it like:
public void floodfill(int x, int y, int target, int fill) {LinkedList<Pair> stack = new LinkedList<Pair>();stack.push(new Pair(x, y));while (!stack.isEmpty()) {Pair pair = stack.pop();int pixelColor = img.getRGB(pair.x, pair.y);if (pixelColor == target) {img.setRGB(pair.x, pair.y, fill);stack.push(new Pair(pair.x + 1, pair.y));
stack.push(new Pair(pair.x - 1, pair.y));stack.push(new Pair(pair.x, pair.y + 1));stack.push(new Pair(pair.x, pair.y - 1));}
}}
How To Program © Walter Milner 2013 Page 158
File Structures and algorithms
In all the other sections, we have manipulated data held in RAM. We also need to use files, mostly because RAM is volatile, so the data is lost when the device is switched off – while files are usually held on a nonvolatile medium. A second reason is capacity – some applications handle hundreds of millions of data items, too much to hold in RAM at the same type.
Reading and writing text files
Java and related languages (C and C++) treat file use as a part of input and output (I/O). So input from the keyboard is treated as a standard input stream, and character output to the screen is a standard output stream.
Java has a wide set of classes relating to I/O, and since this text is about programming, not Java, we will not go through them all. We will just point out a few basic concepts.
package filing;
import ...
public class TextFile {public static void write(String filename, String... lines) { BufferedWriter writer;try {writer = new BufferedWriter(new FileWriter(filename));for (String s : lines) {for (int i = 0; i < s.length(); i++) {char c = s.charAt(i);writer.write(c);}writer.newLine();}writer.close();
} catch (Exception e) {e.printStackTrace();}}
The key class here is BufferedWriter, and the key method write(). Java writer classes in general write characters to a file, and this is what write() does. We pass a filename to our method, folowed by variadic set of strings to write to the file. Once the BufferedWriter is constructed, we loop through the strings, and for each one, loop through its characters, writing each one out to the file. After that we write a newline character to the file.
How To Program © Walter Milner 2013 Page 159
Opening and writing to the file is in a try..catch.., since several things might go wrong. If the file does not already exist, it will be created – and we might not have rights to create files there, or it might be a readonly device, and so on.When we are writing to the file we might get a hardware error.In this simple example we just print the stack trace of the exception. In a real application we should do something more constructive.
We can call this method for example as:
TextFile.write("data.txt","ABC", "01", "012");
We could check the contents of the file by looking at it directlywith a hex editor, which displays the bytes in a file – such asmidnight commander = mc on Linux:
This shows there are 10 bytes (=hex a) bytes in the file. The first three are hex 41 42 43, which is decimal 65, 66, 67, the Unicode of A Band C. Then the 0a is a control code – the new line. 30 31 are decimal 48 49, codes for 1 and 2. Note we are not storing 1 as 2 as ints, but as chars.
Using a hex editor is a good way to test if filewriting code is working.
Similarly we can read the text file back in:
How To Program © Walter Milner 2013 Page 160
public static String[] read(String filename) { BufferedReader reader; ArrayList<String> strings = new ArrayList<String>();try {reader = new BufferedReader(new FileReader(filename));String s;while ((s=reader.readLine())!=null) strings.add(s);reader.close();} catch (Exception e) {e.printStackTrace();}String[] result = new String[strings.size()]; strings.toArray(result);return result;}
The method readLine reads a sequence of characters until it reaches a newline, when it stops and returns the characters as a String. When we reach the end of the file, it returns null, and hence the loop. We do not know how many strings we willread in, so we cannot input them into an array. Instead we add them to an ArrayList, and convert that to an array at the end.
Opening and closing files
The OS will use a file handle through which to read and writeto the file. A file handle is a data item which will include the name of the file, the folder path to where it is on the device, which device that is, and so on. The file handle is set up whenthe file is opened. At any one time an OS will have many file
handles in existence. When we create our BufferedReader in the last section, that included the OS setting up an appropriate file handle. When we have finsihed with the file, we must close it. This will flush any buffers (explained next) and return the file handle to free memory.
If we do not close the file, the handle remains in use – and this is a memory leak. The OS will probably have some limit to the number of file handles which can be open at the same time, and if we keep opening files without closing them, we will eventually reach this limit. So in file use we must always code as a sandwich:
open the file
use it
close it
Buffers
A buffer is a section of memory used to hold data being transferred. For an output buffer, as here, what happens is asfollows – data is written into the buffer, and when the buffer is full, it is written out to the file.
The write method of BufferedWriter apparently writes a single character into the file. In fact, it only writes it into the buffer in memory, and the buffer is written out when it is full, or when the file is closed.
How To Program © Walter Milner 2013 Page 161
Why? Because it takes almost as much time to write one byte out as it does to write a block of say one thousand bytes. So itis much faster to use a buffer and write blocks of data rather than single bytes.
A corresponding process happens on input – even if a single character is apparently read, in fact an entire block is read into an input buffer. Input of further characters than happens(at speed) from that buffer, until it is exhausted and another bufferfull is read.
Binary data files
Binary data files contain bit patterns which correspond to theinternal representation of the data.
Java offers the ObjectOutoutStream as a class which can write objects, and primitives, out to a file, and a corresponding ObjectInputStream to read them. For example:
public static void write(String fileName, int[] data) {FileOutputStream fos;try {fos = new FileOutputStream(fileName);ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeInt(data.length);for (int i : data)oos.writeInt(i);
oos.close();} catch (Exception e) {e.printStackTrace();}}
If we try this out:
int[] data = { 1, 2, 3 };BinaryInts.write("numbers.dat", data);
the file contains:
This is interpreted as
ac ed : a 'magic number' meaning 'this is a Java object stream file'.
How To Program © Walter Milner 2013 Page 162
00 05 : version number
77 10 : more meta data
00 00 00 03 : this is the first int we wrote to the file. Ints are 4 bytes using 2's complement format. This is where we wrote the number of ints in the array
00 00 00 01
00 00 00 02
00 00 00 03
The 3 actual ints. These are easy to recognise. A primitive double, and objects, are much harder to see in binary or hex.
So here we store in the file the size of the array, followed by the array contents. That makes it easy to read back:
public static int[] read(String fileName) {FileInputStream fis;int[] result=null;try {fis = new FileInputStream(fileName);ObjectInputStream ois = new ObjectInputStream(fis);int len = ois.readInt();result=new int[len];for (int i=0; i<3; i++){int val = ois.readInt();result[i]=val;}ois.close();
} catch (Exception e) {e.printStackTrace();}return result;}
Serial and random access
The example so far have involvedsimply going through the file, writingdata out from the start, and reading itback in the same way, from thebeginning of the file, in sequence. Thisis serial access. In the early days of
How To Program © Walter Milner 2013 Page 163
computing, file storage was on magnetic tape. On this type of storage, serial access was the only possibility – the only thing which could be done was to read through the file from the start.
But with hard discs and similar devices, it is possible to movea head directly to any point in the file, without first reading through all preceding data. This is known as random access. This offers 3 basic operations – reading a number of bytes from the current head position (and advancing the position ofthe head), writing a number of bytes, or seeking – moving the head to a new position at a number of bytes from the start of the file.
As an example, we can read the last file backwards. The file starts with a six byte magic number sequence, then there are 4 ints, each 4 bytes long. So the ints start 6, 10,14 and 18 bytes in from the file start:
public static void showRandAccess(String fileName){try {RandomAccessFile raFile =new RandomAccessFile(fileName,"r");for (int n=3; n>-1; n--){
raFile.seek(6+4*n);int i = raFile.readInt();System.out.println(i);}
} catch (Exception e) {e.printStackTrace();}
}
If we use this like:
int[] data = { 1, 2, 3 };BinaryInts.write("numbers.dat", data);BinaryInts.showRandAccess("numbers.dat");
it outputs 3 2 1 3
A Map in a file
Random access filing is equivalent to memory, which is also random access, and in memory we can use pointers, which enables many data structures. Random access filing enables pointer equivalents in files, so we can implement many data structures and algorithms on file as in memory (with a gain incapacity and a reduction in speed).
As an example, we will show a file with an index, which we will use as a map. The map will contain keyvalue pairs, with key being an int and the value a double.
How To Program © Walter Milner 2013 Page 164
The file will be structured in two sections – an index and a main data section. The index will be a series of pairs – the keyvalue, followed by an int which says where the corresponding value is in the main block (which is just a sequence of doubles).
Index Main data
Key Pointer Values
3 1.0
2 2.0
1 3.0
The class definition starts:
public class IndexedMap {String fileName;int size;RandomAccessFile file;int entryCount = 0;
public IndexedMap(String fn, int s) {fileName = fn;size = s;// each index entry is a key and an index into the main block// which is 2 ints or 8 bytes// so index size is 8*s// main block is size doubles = another 8*s bytes// a key value of -1 flags an 'empty' index entrytry {file = new RandomAccessFile(fileName, "rw");file.setLength(16 * size);// initialise indexfor (int i = 0; i < size; i++) {file.seek(i * 8);file.writeInt(-1);}
} catch (Exception e) {e.printStackTrace();}}
If we use this like:
IndexedMap map =new IndexedMap("map",10);map.put(3, 1.0);map.put(2, 2.0);map.put(1, 3.0);
Then the file 'map' contains:
How To Program © Walter Milner 2013 Page 165
The index runs from offset 00 to 4f. The data block starts at offset 50. The first index entry is ..03, with the pointer to 50, where the first data value is. The data is 3F F0 00 00 00 00 00, which is 1.0 as an IEE 754 format double. The second index entry is 02, with the pointer to 58, which is where 2.0 isstored. Third index entry is 01, with the pointer to 60, where 3.0 is stored.
The class also needs a get method, like
double d=map.get(2);System.out.println(d);
Exercise
1. Write and test the get method. What about if the key isnot present in the map?
2. This is just a sketch outline of how this might work. The most obvious problem is that we cannot open an existing map – whenever we construct a map from a file, itinitialises the map to be empty. We need an overloaded constructor, so that we can open an existing map with datain it. But how to tell how much data is in it? In other words, the values of size and entryCount? Solve this problem.
3. Write a delete(keyValue) method. Make sure get still works.
File devices
None of this Java file code makes any reference to what kind of file hardware is being used. It might be magnetic hard disk, flash memory, CD or DVD, or even in RAM. It also might be local, or on another computer over a LAN, orit might be cloud storage and the user does not know whereit is. How can the same code work different devices?
Because layers of software are running here, with specialisation towards devices occuring at the end of the chain. So in the case of Java, we have:
• Source code compiled into bytecode
• which runs on the JVM
How To Program © Walter Milner 2013 Page 166
• The JVM maps file I/O to OS file I/O operations
• which delegates I/O to the appropriate drivers
• which work with the electronics on the device's electrical interface
So differences in device characteristics are handled by device drivers, which are specific to the electronic device. Issues like error detection and correction are dealt with there. Clearly devices have limitations, such as the impossibility of writing to a read-only CD. But these appear at the Java level as an IOException.
This situation is an example of abstraction. We abstract from real devices the simple abilty to read and write data, and delegate to other layers the task of actually doing that, which will vary from one device to another. The next section expands on the idea of abstraction.
How To Program © Walter Milner 2013 Page 167
How Java works
This section comes last, in order to show that it does not matter. We can use Java to create algorithmic solutions to problems, without knowing how it works. An important aspect of programming is abstraction. We cannot make sense of that unless we know something about what is the opposite of abstraction which is, how computers work.
How Computers work
This is the same – in principle for all 'computers' – desktops, smartphones, laptops, tablets or high performance computing clusters, smartTVs, smart watches or any digital programmable device.
These all have a processor (CPU) and memory, connected
by a bus to transfer data. How that is organised in terms of chips varies – it might be a single chip or lots. There are also usually peripheral devices like keyboard and screen. A separate graphics processor which does graphicsrelated computation in hardware is common. Also there may be some file storage devices like hard discs. The capacity of
memory and file storage are both measured in bytes, but they are different types of things, in that memory is thousands of times faster than memory. Executing programs are in memory.
Memory might be ROM – read only – or RAM. The RAM holds binary data. It is organised as bytes of storage, each holding 8 bits 1s or 0s, and each byte having a different address. Each address is itself a string of bits. In fact these are all electronic switches, and we interpret open or closed as 1 and 0. We can do just two things with RAM write something into an address (and overwrite what is currentlythere), or read something out (and leave it unchanged).
The binary values held in RAM can represent two things data, and program instructions. Both are just binary strings, and you cannot tell which is data and what are instructions by looking at them.
The CPU can do just two things. It can recognise a program instruction (in other words, decode which one it is), and it can execute it.
The CPU contains a set of registers – a small number, maybe 10 or 20. A register is like a memory location, exceptthat there aren't many of them, access to them is faster, and some have special roles in the execution of instructions.
How To Program © Walter Milner 2013 Page 168
One is the program counter. It holds an address the address in RAM of the next instruction. This is fetched fromRAM to the processor, the instruction is executed, the program counter is moved on to the next instruction, and the cycle repeats. This is the fetchexecute cycle.
To get maximum speed, the actual process is more sophisticated then this. 32 or 64 bits are fetched, but this might be 1, 2 or 4 instructions at one time. Execution is pipelined. This means the cycle is split into 8 stages, and insome of these, 2 or 3 actions are performed at the same time.
The program instructions types are add, subtract, multiply, compare, logical 'and' and so on, branch to a new instruction address, load and store data to and from memory, and move between registers, and so on.
Machine code
A program instruction in machine code is for example:
1011 1100 1100 0000 1111 1101 1011 1110
Writing programs in this manner is pretty much impossible,since the bit strings are meaningless to humans.
Instead assembly language is used. This looks like for example
mov %r7, $4
which means move 4 into register r7. The $ is there to showwe mean the actual number 4, not the value stored at address 4 in RAM.
Each assembly language instruction corresponds to exactly one machine code instruction. But they make a lot more sense than a string of 1s and 0s.
Using an assembler and linker
When a program is executing, it must be in binary form as shown above. This is always true. For all 'programs'. Always. In Java or C or Visual Basic. An executing program is machine code.
So we need a way of creating programs looking like
1011 1100 1100 0000 1111 1101 1011 1110
without going crazy.
So we do as follows:
1. Use a text editor to write the program in assembly language, like 'mov %r7, $4'. This is saved in a file, andthis sort of thing is called source code.
How To Program © Walter Milner 2013 Page 169
2. A piece of software called an assembler reads the source code, and converts it to an intermediate form known as object code. This is mostly carrying out the onetoone translation from assembler to machine code.
3. Another piece of software, called a linker, is used. This links one or more units of object code, together usually with files from a library of code which can be usefully reused, and puts it into the form of an executable file. The way this is structured will depend on the operating system.
Hello world on Linux
This tutorial shows how to write and run an assembly language program on Linux (Debian) running on a Pentium.To do it on Windows, you need an assembler and linker ( like masm ) and the assembler format and OS calls will be different. Traditionally, a first program outputs the message'Hello world', so this is what our program will do.
The program does two things – output the message, and end. It outputs a message by making a call to the operating system kernel, rather than doing it itself. The OS can do lots of things like this.
To output the message, the kernel needs to know four things, and these are passed to it by putting values in someregisters. It needs to know:
1. What to do. In this case, it is to write out a message. The code for this is 4, and must be in the eax register
2. Where the message starts, in memory. This must be in the ecx register
3. How long the message is, in register edx
4. Where to write it to. In this case, stdout, coded by 1, in register ebx
We actually call the kernel with an int instruction, a 'software interrupt'.
Then we end the program by another system call, with a return code in register ebx (0 means OK) and 1 in eax, meaning end this program and return to the OS.
1. Go into a shell terminal. Use a text editor like gedit to write this:
How To Program © Walter Milner 2013 Page 170
.text # this section is program .global _start # entry point
_start: # write string to stdout movl $len,%edx # third argument: message length movl $msg,%ecx # second argument: pointer to message movl $1,%ebx # first argument: file handle (stdout) movl $4,%eax # system call number (sys_write) int $0x80 # call kernel
# and exit movl $0,%ebx # first argument: exit code movl $1,%eax # system call number (sys_exit) int $0x80 # call kernel
.data # this section is data
msg: .ascii "Hello, world!\n" # the string len = . - msg # length of it
Programs use memory in segments to hold code, and also data. This program has 2 segments. The first is a data segment, starting .data. The second is a text segment, .text, where the program is. _start marks where execution should begin. There are only 8 actual code instructions everythingelse is information for the assembler. The instructions start after _start
msg is a label. When assembled, it will turn into an addressin memory a binary string. A label is a symbolic address something more meaningful for a human than a string of bits. So msg is the address where "Hello world." will be placed, as ASCII code. The \n is a new line control code.
In .msg, the dot is the current address, and msg is addressof the start of the message. So len is the number of bytes from the end of the message to the start the length of the message.
2. Save this into a file named hello.s
Go back to the shell.
3. Assemble it with the command
as o hello.o hellos.s
This invoke the GCC assembler. The o option means to generate object code file named hello.o, from source code hello.s
4. Link it by
ld s o hello hello.o
This is to produce an executable file named hello, from hello.o. The s means remove all symbolic information (labels and so on).
5. Load and run the program by
./hello
How To Program © Walter Milner 2013 Page 171
This program will only work on an Intel Pentium processor (or AMD equivalent), since it contains machine code instructions which are specific to that machine, relating to the registers eax, ebx and so on. It will also only work on something running Linux as the OS, since Windows, for example, has a different set of functions available.
Using ASCII
Everything in a computer is strings of bits. So how do we store 'characters' letters of the alphabet, punctuation marks and so on? This is done using a character set, like ASCII or Unicode. This is a set of characters, each one having a corresponding binary pattern.
ASCII stores each character as 8 bits, with a total of 256 characters possible.
For example:
Character Binary Decimal equivalent
A 0100 0001 65
B 0100 0010 66
C 0100 0011 67
..
Z 0101 1010 90
The first ASCII characters are not 'printable' instead, they are control characters, menaing they control printing. ASCIIcode 10 means 'line feed', or go on to the next line.
Here is an Intel Linux program to output the letter A:
How To Program © Walter Milner 2013 Page 172
.text # this section is program .global _start # entry point
_start: # write our string to stdout movl $2,%edx # third argument: message length movl $msg,%ecx # second: pointer to message movl $1,%ebx # first: file handle (stdout) movl $4,%eax # system call number (sys_write) int $0x80 # call kernel
# and exit movl $0,%ebx # first argument: exit code movl $1,%eax # system call number (sys_exit) int $0x80 # call kernel
.data # this section is data
msg: .byte65,10 len = . - msg
This is similar to the first, except we just output 2 characters, and we use the .byte directive. This tells the assembler to place a sequence of bytes in memory at the current location. In this example, we want 65, ASCII code for A, then 10, ASCII control code for a new line.
Exercise
Try out this program. As in the first example, you will need to1. Type it into gedit or another text editor, and save it in a file named for example ascii.s
2. Assemble it by
as o ascii.o ascii.s
3. Link it by
ld s o ascii ascii.o
4. Run it by
./ascii
Exercise
What is ASCII 66, 65, 68?
Write a program to output BAD followed by a new line
Doing arithmetic
Here is a program to add 2 and 3, and display the result:
How To Program © Walter Milner 2013 Page 173
.text # this section is program .global _start # entry point
_start:
movl $2, %eax # put 2 into eax addl $3, %eax # add 3 into eax addl $48, %eax # convert digit to ASCII movl $msg, %ecx # put address of msg into ecx movb %al, (%ecx) # store byte in al in location ecx # points to
# write our string to stdout movl $2,%edx # third argument: message length movl $msg,%ecx # second argument: pointer to message movl $1,%ebx # first argument: file handle (stdout) movl $4,%eax # system call number (sys_write) int $0x80 # call kernel
# and exit movl $0,%ebx # first argument: exit code movl $1,%eax # system call number (sys_exit) int $0x80 # call kernel
.data # this section is data
msg: .byte65,10 len = . - msg
This puts 2 into register eax, then adds 3,
To store the result into memory, we have to use 'indirect addressing'. That is we put the address in ecx, then store itinto the address pointed to be ecx.
We also wantto store just one byte. Most of the instructions end with l, meaning long or 32 bits. We use movb for move byte, and use register al. Thsi is the lowest 8 bits of eax.
It then outputs the result using the code as in the previous example.
But first we have to add 48. This is because Linux expects to output a sequence of ASCII characters. And the ASCII code of '5' is 53, not 5, since the code of '0' is 48.
Exercise
1. Try this out
2. Change it so it adds 2 and 7
3. The mnemonic to add numbers is addl. The mnemonic tosubtract is subl. Write a program to subtract 2 from 7 and display the result.
4. Why would adding 5 and 5 be more complicated?
High level language programming
Assembler and machine code are low level. That means they refer to processor registers, memory registers and so on. The logic of the problem solution is obscured by references to the actual machine.
How To Program © Walter Milner 2013 Page 174
Another problem with low level code is that it is platformdependent. The program has to be rewritten completely to run on a machine with a different processor, since it will have different registers and different instructions. And it must be rewritten for a different OS, like Mac OS X instead of Linux, since the system calls will differ.
That means that a high level language is usually preferable. In a language such as C, we then have a different step in the chain:
1 Write the source code in C
2 Compile it into object code
3 Link it to produce an executable file
The compiler is an equivalent to the assembler, except that it inputs code in C rather than assembler.
An IDE may well have a single 'Run' button. In fact this saves the source code, compiles it, links it, and loads and runs the result.
To run the program on a different platform, we do not have to rewrite it (if it is correctly written). The same source codeis recompiled for a different target.
As well as being partly platformindependent, a high level language allows us to focus on the logic of the problem solution, instead of the details of the machine we are using.
Java bytecode
Java uses a different approach from C. The reason is that Java was required to run on a wide range of platforms, without being recompiled. That is, it should be possible to compile a Java program, and run it unchanged, on differentprocessors and different OS. How to do this? As follows:
1. A Java compiler converts source code into an intermediate form, called byte code.
2. Byte code executes on a Java Virtual Machine – a JVM.
3. A JVM is a piece of software. There are lots of JVMs – around 70. A different JVM is needed for every different platform.
4. A JVM is a 'virtual machine'. It appears to byte code running on it that it has a screen(maybe) and a mouse and keyboard and so on. In fact these are virtual.
5. The JVM maps the actual hardware and OS calls to thesevirtual resources. So when Java bytecode draws a window on the screen, it actually draws it on a virtual screen, whichthe JVM maps to the actual hardware screen.
How To Program © Walter Milner 2013 Page 175
This means the same byte code will run the same (more or less) on any platform which has a JVM
How bytecode works
The utility javap, part of the JDK, lets us look at bytecode ina .class file. We start with a simple piece of source code:
public class Test {
public static void main(String[] args) { int t=0;for (int x=5; x<10; x++)t++;
}}
If this is compiled, then we go to the folder where Test.class is, then 'javap c Test' will display the byte code, like this: ( comments are mine )
walter@walter-s5-1030uk:~/eclipseworkspace/testJava/bin$ javap -c Test
Compiled from "Test.java"
public class Test extends java.lang.Object{
public Test(); // compiler generated no-arg constructor
Code:
0: aload_0
1: invokespecial#8; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_0 // push 0 onto stack
1: istore_1 // pop value into variable 1. so t=0
2: iconst_5 // push 5 on stack
3: istore_2 // pop into variable 2. So x=5
4: goto 13 // go to 13
7: iinc 1, 1 // increment t
10: iinc 2, 1 // increment x
13: iload_2 // get variable 2 (which is x)
14: bipush 10 // push 10 onto stack
16: if_icmplt 7 // compare with value popped off stack,
// and go to 7 if not equal. IOW goto 7
// if x!=10
19: return
}
So bytecode makes extensive use of a stack. Here it is usingvariable 1 as t, and does t=0 by pushing 0 on the stack, then popping it off into variable 1. Similarly for initialising xat 5 in variable 2. The body of the loop, t++;, is line 7, and lines 13, 14 and 16 do the check x<10 to continue the loop.
How To Program © Walter Milner 2013 Page 176
This is in effect assembly language, corresponding to the machine code, which is the actual byte code. But the machine that the byte code runs on is the virtual machine ofthe JVM, not directly on the processor with its registers.
Why would you learn to program in Java bytecode? If you needed to write a JVM, for a new platform. Otherwise there is little point. Clearly, writing code in Java not bytecode is simpler, easier, faster and makes more sense than using bytecode. In other words, we can ignore how bytecode works, because it does not matter. We can abstract away the implementation mechanism for Java, and focus on writing the source code to formulate algorithmic solutions to problems.
Abstraction
You could learn more about writing assembler. But that is not what this text is about. Nor is it about bytecode.
It is about things called abstract data types, data structuresand algorithms. These are the same across programming languages, let alone platforms. In other words a tree, for example, is the same in assembler, C, Java and Java it is languageagnostic.
There are two advantages to this approach. First is it makesthe ideas very powerful and useful, since we can use them in any language. It also makes it clearer, since we can focuson the idea, not how it is implemented on one processor.
This section has looked at how a Linux Intel/AMD computer works, as a machine. We can now park that idea, and look instead at data structures and algorithms in themselves, with an implementation in the high level language of Java, which will work the same on any machine.
This is the idea of abstraction. We ignore how the machine does it, in terms of registers and memory locations and OS calls. Instead we focus on a solution in terms of the problemand an algorithm to solve it.
How To Program © Walter Milner 2013 Page 177