Professor Douglas Troeger The City College of New York Spring, 2016

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 44

Lecture 1

Csc 102

Professor Douglas Troeger


The City College of New York
Spring, 2016
Administrative Info
All administrative details – including the exam schedule,
grading, attendance and academic integrity policies -- are
packaged into the syllabus, which is available on piazza.

To get access, you need only sign yourself up at


piazza.com/ccny.cuny/spring2016/
csc10200

All handouts (instructions for installing the programming


environment on your computer, lecture notes, problem sets,
etc.) will be distributed via piazza.
For now, you need just two details, since recitations start this
afternoon:
● all grades will be based on work completed during

recitation classes: recitation attendance is not optional.


● both the midterm and the final exams will be given via

workstations in the lab: this is not a paper and pencil


course!
Computer Programming

Computer programming is the art and science of


solving certain kinds of problems in certain kinds
of ways.
Certain kinds of problems ...

Problems which can be solved by sequences of


simple steps, together with rules which specify
when each step is to be executed.
What problems are these?
Example: to determine the monetary value in
cents of a collection of quarters, dimes and
nickels
● Count the quarters – call this number q
● Count the dimes – call this number d
● Count the nickels – call this number n
● Calculate 25*q + 10*d + 5*n
Are all so trivial?
It was a significant discovery that the square root of n
can be computed by this sequence of steps:
1. Make a guess at the answer – say n/2
2. Compute r = n/guess
3. Set guess = (guess + r)/2
4. Go back to step 2 for as many iterations as
necessary. The more that steps 2 and 3 are repeated,
the closer guess will become to the square root of n.
Generally: How do we know that a given sequence –
perhaps tens of millions of steps – does what we
claim? Certainly you cannot usually tell just by
looking at the steps!
Just recipes?
Well, yes and no. Consider:
● Place 2 teaspoons of sugar in a mixing bowl
● Add to the bowl: 1 egg, 1 cup of milk, 1 oz. rum
● Add vanilla extract to taste
● Beat until smooth
● Pour into a pretty glass
● Sprinkle with nutmeg

Kind of like a program – but although this is a sequence


of steps, the steps are not of the right kind.
But ...

Computer programs specify sequences of steps executed


by a computer, not a person.

At heart, computers do two things: calculate numbers and


store results.

As incredibly fast and accurate as they are, computers are


still hard-pressed to make sense of 'to taste' and 'pretty
glass'.
Ways and Problems and Algorithms

In this course, we are interested in problems which can


be solved by sequences of computer-understandable
steps, together with computer-understandable rules
which specify when each step is to be executed.

Such sequences are termed algorithms, and the


problems solvable by algorithms are termed
algorithmic.
Machine-understandable?

Does this mean sequences of 0s and 1s? Things


like
0110 1001 1010 1011
which is a possible machine-language version of
ADD X Y Z
(add the number in memory location X to the
number in memory location Y and store the result
in memory location Z)?
Fortunately, no!

In fact, even

ADD X Y Z

a typical statement in an assembly language, is


at a lower level than we need.
Programming Languages
We use high-level programming languages to communicate
our algorithmic designs to computers (and to each other).

Programs written in high-level languages are translated by


computer programs called compilers into their machine-
language equivalent, which are then executed by a computer.

Despite being much easier to use than machine or assembly


language, high-level programming languages are still
severely constrained modes of expression, trading freedom
for the precision needed by computers.
The language used in this course is
C++

C++ is an industrial-strength language exemplifying an


industrial-strength programming paradigm – object-
oriented programming – that will be immediately useful
to you when you leave this course.
C++ is very powerful, but also very complicated: we have not only to learn the art and science of
algorithmic problem solving, but also the enormous set of rules imposed by the language.

That's just the way it is.


We'll need you to do your best to master these rules.
The basic idea: practice, and then practice some more.

We'll do our best to layer the complexity, approaching


the language on an as-needed basis as we pursue the
main (at once more important and harder) goal of
learning algorithmic problem solving.
The general idea of programming in C++

One creates a C++ source program – say


start.cpp – using an editor.

One then uses the C++ compiler to translate


the source program into object code: a
machine-language program that the computer
can execute directly.
How to proceed?

Since our designs will be shaped by the means at hand for


their expression, it makes sense to make progress on two
fronts simultaneously:

algorithmic design <---------> coding in C++

We'll move back and forth between these, as the situation


demands.
Getting started: a first problem
We want to input 3 integers, and then output the sum of the
squares of the 2 larger of these.
This breaks into a number of subproblems:
● Input some integers
● Determine the two largest of three integers
● Compute the appropriate squares and a sum
● Output the result
And, of course, we also need to know how to package the
whole thing into a program!
Input and output, and a more or less generic simple
program, Start.cpp

#include<iostream>
using namespace std;
int main () {
int first, second, third;
cout << "Enter three integers, separated by spaces" << endl;
cin >> first >> second >> third;
cout << "The sum of " << first << ", " << second << ", and "
<< third << " is " << first + second + third << endl;
return 0;
}
Solving the main problem

If we knew how to compute the min of 3 numbers, we might proceed as follows:

if (min == first)
cout << "The sum of the squares of the two largest of " << first << ", " << second
<< ", and " << third << " is " << second * second + third * third << endl;
if (min == second)
cout << "The sum of the squares of the two largest of " << first << ", " << second
<< ", and " << third << " is " << first * first + third * third << endl;
if (min == third)
cout << "The sum of the squares of the two largest of " << first << ", " << second
<< ", and “ << third << " is " << first * first + second * second << endl;
How can we test this idea?

We can set min to a fixed value, and then test the


resulting program.

One says that the computation of min has been


replaced (temporarily) by a “stub”.
SumOfSquaresOfTwoLargest-Stub.cpp

// Set min to a fixed value for development purposes


#include<iostream>
using namespace std;
int main () {
int first, second, third;
cout << "Enter three integers, separated by spaces" << endl;
cin >> first >> second >> third;
int min = 10; // or some other value chosen for testing
/*
Replace this comment by the code given on the previous slide
*/
return 0;
}
Testing the stub version
Once an idea is testable, it is a good idea to test it.
Assuming the interior comment has been replaced by executable
code, we now have a simplified version of our program which allows
us to check our idea for computing the result. What constitutes a
useful test?
The program takes 3 inputs – clearly our testing would be useful
only if one of these is equal to the value we set for the variable min.
Moreover, our program executes different clauses depending on
whether min occurs as the value of first, second or third. We clearly
want our testing to exercise each clause.
Most likely errors for this example? Typos in the arithmetic
computations – eg, writing first*second when we meant first*first
Testing, continued

Three tests come to mind as immediately useful:


● 10 2 3 – should output 2^2 + 3^2 = 13
● 2 10 3 – same output
● 2 3 10 – same output
Observe that while we do not need to worry about the magnitudes
of the input for testing this part of the final program, we do need to
choose our inputs to show problems with the selection of clause,
the computation of the squares, and the computation of the sum.
Some poor tests:
● 10 10 10 // would show nothing about clause selection
● 10 1 1 // would not check that second^2 or third^2 had been
correctly computed
And so on.
Moving on: how to compute the min of 3 numbers?
int min;
if (first <= second)
if (first <= third) // so (first <= second) and (first <= third)
min = first;
else // so (first <= second) and (third < first), ie, third < first <= second
min = third;
else
if (first <= third)
// so (first > second) and (first <= third), ie, second < first <= third
min = second;
else
if (second <= third) // so (second < first) and (second <= third)
min = second;
else // so (second < first) and (third < second), ie, third < second < first
min = third;
Testing the computation of min
Again: once an idea is testable, test it.
One way to check our min idea is to embed it in a complete
program and then input 3 distinct values, in all possible orders:
● 1 2 3, 1 3 2, 2 1 3, 2 3 1, 3 1 2, 3 2 1
But what if all the values are not distinct? Shouldn't our
program work with inputs (say)
● 1 1 2, 1 2 1, 2 1 1
Or even
● 111
Nothing in the problem description (the specification) requires
the inputs to be distinct, so we must consider all possibilities.
Testing, continued
Good grief! What if there had been 10 inputs? Or 100? Let's
see: there are n! rearrangements of n inputs – for n = 10, just
checking the distinct input case would require 10! = 3628800
cases, and for n = 100 the number is

9332621544394415268169923885626670049071596826438
1621468592963895217599993229915608941463976156518
2862536979208272237582511852109168640000000000000
00000000000

It is infeasible to carry out exhaustive testing in most cases.

Testing is incredibly important, but it generally cannot be the


whole story.
Testing, continued
Judicious testing is just one way of getting insight into
the computational processes engendered by our code.

Another powerful way is to insert comments stating


clearly what relationships we expect to hold among
program variables.

Study the example carefully – we will have more to say


about this as the term progresses.
Finishing the program
Dropping our code fragments into one program body would give

#include <iostream>
using namespace std;
int main () {
int first, second, third;
cout << "Enter three integers, separated by spaces" << endl;
cin >> first >> second >> third;
int min;
// min computation as on previous slide
// sum of squares of two which are not equal to min from the stub solution
return 0;
}

For completeness, here is the entire program:


SumOfSquaresOfTwoLargest.cpp
/*
This program inputs three integers and outputs the sum of the squares of
the two largest.
*/

#include <iostream>
using namespace std;
int main () {
int first, second, third;
cout << "Enter three integers, separated by spaces" << endl;
cin >> first >> second >> third;
SumOfSquaresOfTwoLargest.cpp
page 2
int min;
if (first <= second)
if (first <= third) // so (first <= second) and (first <= third)
min = first;
else // so (first <= second) and (third < first), ie, third < first <=
second
min = third;
else
if (first <= third) // so (first > second) and (first <= third), ie, second < first <=
third
min = second;
else
if (second <= third) // so (second < first) and (second <= third)
min = second;
else // so (second < first) and (third < second), ie, third < second < first
min = third;
SumOfSquaresOfTwoLargest.cpp
page 3
if (min == first)
cout << "The sum of the squares of the two largest of " << first << ", " << second << ", and "
<< third << " is " << second * second + third * third << endl;

if (min == second)
cout << "The sum of the squares of the two largest of " << first << ", " << second << ", and "
<< third << " is " << first * first + third * third << endl;

if (min == third)
cout << "The sum of the squares of the two largest of " << first << ", " << second << ", and "
<< third << " is " << first * first + second * second << endl;

return 0;
}
Testing the complete program

In some sense, since we know that the program's constituent


parts work (we tested them independently, we commented
them, we thought them through carefully …), we are entitled
to a high degree of confidence in the complete program.

But overconfidence is a serious problem in this business:


better to be cautious and test again.

What tests would you choose?


Just when we thought we were done ...

Suppose the new program passes all its tests. Are we done?

NO. Yet another level of review is necessary!

We need to ask: is this the best way to do the job?

Our computation of min seems somewhat tortured and a bit


hard to understand. Is there a better way?
Consider
int min = first; // so min is smallest in the set {first}

if (second < min)


min = second; // min is smallest in {first,second}

if (third < min)


min = third; // min is smallest in {first,second,third}

// therefore min is the smallest of the inputs


Assuming this passes testing
Let's assume the new computation of min passes
the tests you apply, and that you have replaced
the first computation of min with this simpler one,
and that the new program passes its tests.

Which version is the keeper?


If two versions pass the tests ...
Keep the one which is easier to understand, and therefore
● Easier to check by means other than testing
● Easier for other programmers to maintain (update,
extend, use in other contexts …)

Another criterion is the efficiency of the program. We will


explain what we mean by efficiency as we go along.
Which of our two versions of min would you intuitively
judge to be the more efficient?
Seldom is there just one way to solve a
problem ...

and almost never is the first way we think of the best!

Composing a program is a lot like writing an essay, or


developing a mathematical proof, or, in fact, doing
anything which requires inspiration, insight, care and
patience.

Lots and lots of all of these.


Can you think of another way to improve our
program?
Have a look at our use of three if statements in

if (min == first)
cout << ...
if (min == second)
cout << ...
if (min == third)
cout << …

Even if it is the case that min == first, the remaining tests will be executed
unnecessarily. Can you see how to use if-else to improve this?
Reading and Homework
This lecture draws primarily on Savitch,
Chapters 1 and 2
1.1 Computer Systems 2.1 Variables and Assignments
1.2 Programming and Problem 2.2 Input and Output
Solving
2.3 Data Types and Expressions
1.3 Introduction to C++
2.4 Simple Flow of Control – pp
1.4 Testing and Debugging 74 - 84
We also used material on nested if statements from Chapter 3 – pp
120 – 123.

These chapters contain considerably more material than we have


covered here – we will come back to the rest as we need it.
Read!

Nonetheless, you are strongly encouraged to


begin reading NOW. Do the self-study exercises,
do the practice programs, and make heavy use of
the MyProgrammingLab software.

Specific homework will be assigned in your


recitation classes.
See you in recitation!

You might also like