Anda di halaman 1dari 31

# What Is Programming?

## Programming is the process of taking an algorithm and encoding it into a

notation, a programming language, so that it can be executed by a computer. Although
many programming languages and many different types of computers exist, the
important first step is the need to have the solution. Without an algorithm there can be
no program.

Algorithm
Algorithms describe the solution to a problem in terms of the data needed to
represent the problem instance and the set of steps necessary to produce the
intended result. Programming languages must provide a notational way to represent
both the process and the data. To this end, languages provide control constructs and
data types.
Control constructs allow algorithmic steps to be represented in a convenient
yet unambiguous way. At a minimum, algorithms require constructs that perform
sequential processing, selection for decision-making, and iteration for repetitive
control. As long as the language provides these basic statements, it can be used for
algorithm representation.

## Building Blocks Of Algorithms (Statements, State, Control Flow, Functions)

Simple statements
A simple statement is comprised within a single logical line. Several simple statements may occur on
a single line separated by semicolons. The syntax for simple statements is:

## simple_stmt ::= expression_stmt

| assert_stmt
| assignment_stmt
| augmented_assignment_stmt
| annotated_assignment_stmt
| pass_stmt
| del_stmt
| return_stmt
| yield_stmt
| raise_stmt
| break_stmt
| continue_stmt
| import_stmt
| global_stmt
| nonlocal_stmt
Expression statements
Expression statements are used (mostly interactively) to compute and write a value, or (usually) to
call a procedure (a function that returns no meaningful result; in Python, procedures return the
value None). Other uses of expression statements are allowed and occasionally useful. The syntax
for an expression statement is:

## expression_stmt ::= starred_expression

An expression statement evaluates the expression list (which may be a single expression).
Assignment statements
Assignment statements are used to (re)bind names to values and to modify attributes
or items of mutable objects:

## assignment_stmt ::= (target_list “=”)+ (starred_expression |

yield_expression)
target_list ::= target (“,” target)* [“,”]
target ::= identifier
| “(” [target_list] “)”
| “[” [target_list] “]”
| attributeref
| subscription
| slicing
| “*” target

class Cls:
x = 3 # class variable
inst = Cls()
inst.x = inst.x + 1 # writes inst.x as 4 leaving Cls.x as 3

## Augmented assignment statements

Augmented assignment is the combination, in a single statement, of a binary operation and
an assignment statement:
augmented_assignment_stmt ::= augtarget augop (expression_list |
yield_expression)
augtarget ::= identifier | attributeref |
subscription | slicing
augop ::= “+=” | “-=” | “*=” | “@=” | “/=” |
“//=” | “%=” | “**=”
| “>>=” | “<<=” | “&=” | “^=” |
“|=”
An augmented assignment expression like x += 1 can be rewritten as x = x + 1 to
achieve a similar, but not exactly equal effect. In the augmented version, x is only evaluated
once. Also, when possible, the actual operation is performed in-place, meaning that rather
than creating a new object and assigning that to the target, the old object is modified instead.
Annotated assignment statements
Annotation assignment is the combination, in a single statement, of a variable or
attribute annotation and an optional assignment statement:

## annotated_assignment_stmt ::= augtarget “:” expression [“=”

expression]
The difference from normal Assignment statements is that only single target and only
single right hand side value is allowed.

## The assert statement

Assert statements are a convenient way to insert debugging assertions into a program:

## The simple form, assert expression, is equivalent to

if __debug__:
if not expression: raise AssertionError

## The extended form, assert expression1, expression2, is equivalent to

if __debug__:
if not expression1: raise AssertionError(expression2)

## The pass statement

pass_stmt ::= “pass”

## pass is a null operation — when it is executed, nothing happens. It is useful as a

placeholder when a statement is required syntactically, but no code needs to be
executed, for example:

## The del statement

del_stmt ::= “del” target_list

Deletion is recursively defined very similar to the way assignment is defined. Rather
than spelling it out in full details, here are some hints.

Deletion of a target list recursively deletes each target, from left to right.

Deletion of a name removes the binding of that name from the local or global
namespace, depending on whether the name occurs in a global statement in the
same code block. If the name is unbound, a NameError exception will be raised.
Deletion of attribute references, subscriptions and slicings is passed to the primary
object involved; deletion of a slicing is in general equivalent to assignment of an empty
slice of the right type (but even this is determined by the sliced object).

## The return statement

return_stmt ::= “return” [expression_list]

return may only occur syntactically nested in a function definition, not within a nested
class definition.

## If an expression list is present, it is evaluated, else None is substituted.

return leaves the current function call with the expression list (or None) as return
value.

When return passes control out of a try statement with a finally clause,
that finally clause is executed before really leaving the function.

## The yield statement

yield_stmt ::= yield_expression

## A yield statement is semantically equivalent to a yield expression. The yield

statement can be used to omit the parentheses that would otherwise be required in
the equivalent yield expression statement. For example, the yield statements

yield <expr>
yield from <expr>

## are equivalent to the yield expression statements

(yield <expr>)
(yield from <expr>)

Yield expressions and statements are only used when defining a generator function,
and are only used in the body of the generator function. Using yield in a function
definition is sufficient to cause that definition to create a generator function instead of
a normal function.

## The raise statement

raise_stmt ::= “raise” [expression [“from” expression]]
If no expressions are present, raise re-raises the last exception that was active in the
current scope. If no exception is active in the current scope,
a RuntimeError exception is raised indicating that this is an error.

Otherwise, raise evaluates the first expression as the exception object. It must be
either a subclass or an instance of BaseException. If it is a class, the exception
instance will be obtained when needed by instantiating the class with no arguments.

The type of the exception is the exception instance’s class, the value is the instance
itself.

Exception chaining can be explicitly suppressed by specifying None in the from clause:

>>>
>>> try:
... print(1 / 0)
... except:
... raise RuntimeError("Something bad happened") from None
...
Traceback (most recent call last):
File "<stdin>", line 4, in <module>

## The break statement

break_stmt ::= “break”

break may only occur syntactically nested in a for or while loop, but not nested in
a function or class definition within that loop.

It terminates the nearest enclosing loop, skipping the optional else clause if the loop
has one.

If a for loop is terminated by break, the loop control target keeps its current value.

When break passes control out of a try statement with a finally clause,
that finally clause is executed before really leaving the loop.

## The continue statement

continue_stmt ::= “continue”

continue may only occur syntactically nested in a for or while loop, but not nested
in a function or class definition or finally clause within that loop. It continues with
the next cycle of the nearest enclosing loop.
When continue passes control out of a try statement with a finally clause,
that finally clause is executed before really starting the next loop cycle.

## The import statement

import_stmt ::= “import” module [“as” name] ( “,” module
[“as” name] )*
| “from” relative_module “import” identifier
[“as” name]
( “,” identifier [“as” name] )*
| “from” relative_module “import” “(”
identifier [“as” name]
( “,” identifier [“as” name] )* [“,”] “)”
| “from” module “import” “*”
module ::= (identifier “.”)* identifier
relative_module ::= “.”* module | “.”+
name ::= identifier

The basic import statement (no from clause) is executed in two steps:

2. define a name or names in the local namespace for the scope where
the import statement occurs.

When the statement contains multiple clauses (separated by commas) the two steps
are carried out separately for each clause, just as though the clauses had been
separated out into individual import statements.

Examples:

## import foo # foo imported and bound locally

import foo.bar.baz # foo.bar.baz imported, foo bound
locally
import foo.bar.baz as fbb # foo.bar.baz imported and bound as
fbb
from foo.bar import baz # foo.bar.baz imported and bound as
baz
from foo import attr # foo imported and foo.attr bound as
attr

Future statements
A future statement is a directive to the compiler that a particular module should be
compiled using syntax or semantics that will be available in a specified future release
of Python where the feature becomes standard.
The future statement is intended to ease migration to future versions of Python that
introduce incompatible changes to the language. It allows use of the new features on
a per-module basis before the release in which the feature becomes standard.

## future_statement ::= “from” “__future__” “import” feature [“as”

name]
(“,” feature [“as” name])*
| “from” “__future__” “import” “(” feature
[“as” name]
(“,” feature [“as” name])* [“,”] “)”
feature ::= identifier
name ::= identifier

A future statement must appear near the top of the module. The only lines that can
appear before a future statement are:

##  the module docstring (if any),

 blank lines, and
 other future statements.

## The features recognized by Python 3.0

are absolute_import, division, generators, unicode_literals, print_fun
ction, nested_scopes and with_statement. They are all redundant because they
are always enabled, and only kept for backwards compatibility.

A future statement is recognized and treated specially at compile time: Changes to the
semantics of core constructs are often implemented by generating different code. It
may even be the case that a new feature introduces new incompatible syntax (such
as a new reserved word), in which case the compiler may need to parse the module
differently. Such decisions cannot be pushed off until runtime.

For any given release, the compiler knows which feature names have been defined,
and raises a compile-time error if a future statement contains a feature not known to
it.

## The global statement

global_stmt ::= “global” identifier (“,” identifier)*

The global statement is a declaration which holds for the entire current code block.
It means that the listed identifiers are to be interpreted as globals. It would be
impossible to assign to a global variable without global, although free variables may
refer to globals without being declared global.
Names listed in a global statement must not be used in the same code block textually
preceding that global statement.

## Names listed in a global statement must not be defined as formal parameters or in

a for loop control target, class definition, function definition, import statement, or
variable annotation.

## The nonlocal statement

nonlocal_stmt ::= “nonlocal” identifier (“,” identifier)*

The nonlocal statement causes the listed identifiers to refer to previously bound
variables in the nearest enclosing scope excluding globals. This is important because
the default behavior for binding is to search the local namespace first. The statement
allows encapsulated code to rebind variables outside of the local scope besides the
global (module) scope.

## Names listed in a nonlocal statement, unlike those listed in a global statement,

must refer to pre-existing bindings in an enclosing scope (the scope in which a new
binding should be created cannot be determined unambiguously).

Names listed in a nonlocal statement must not collide with pre-existing bindings in
the local scope.

## State, Control Flow

Besides the while statement just introduced, Python knows the usual control flow statements
known from other languages, with some twists.

if Statements
Perhaps the most well-known statement type is the if statement. For example:

>>>
>>> x = int(input("Please enter an integer: "))
>>> if x < 0:
... x = 0
... print('Negative changed to zero')
... elif x == 0:
... print('Zero')
... elif x == 1:
... print('Single')
... else:
... print('More')
...
More

There can be zero or more elif parts, and the else part is optional. The keyword
‘elif’ is short for ‘else if’, and is useful to avoid excessive indentation.
An if … elif … elif … sequence is a substitute for
the switch or casestatements found in other languages.

for Statements
The for statement in Python differs a bit from what you may be used to in C or Pascal.
Rather than always iterating over an arithmetic progression of numbers (like in
Pascal), or giving the user the ability to define both the iteration step and halting
condition (as C), Python’s for statement iterates over the items of any sequence (a
list or a string), in the order that they appear in the sequence. For example (no pun
intended):

>>>
>>> # Measure some strings:
... words = ['cat', 'window', 'defenestrate']
>>> for w in words:
... print(w, len(w))
...
cat 3
window 6
defenestrate 12

If you need to modify the sequence you are iterating over while inside the loop (for
example to duplicate selected items), it is recommended that you first make a copy.
Iterating over a sequence does not implicitly make a copy. The slice notation makes
this especially convenient:

>>>
>>> for w in words[:]: # Loop over a slice copy of the entire
list.
... if len(w) > 6:
... words.insert(0, w)
...
>>> words
['defenestrate', 'cat', 'window', 'defenestrate']

With for w in words:, the example would attempt to create an infinite list,
inserting defenestrate over and over again.
The range() Function
If you do need to iterate over a sequence of numbers, the built-in
function range() comes in handy. It generates arithmetic progressions:

>>>
>>> for i in range(5):
... print(i)
...
0
1
2
3
4

The given end point is never part of the generated sequence; range(10) generates
10 values, the legal indices for items of a sequence of length 10. It is possible to let
the range start at another number, or to specify a different increment (even negative;
sometimes this is called the ‘step’):

range(5, 10)
5 through 9

range(0, 10, 3)
0, 3, 6, 9

## range(-10, -100, -30)

-10, -40, -70

To iterate over the indices of a sequence, you can combine range() and len() as
follows:

>>>
>>> a = ['Mary', 'had', 'a', 'little', 'lamb']
>>> for i in range(len(a)):
... print(i, a[i])
...
0 Mary
2 a
3 little
4 lamb

## In most such cases, however, it is convenient to use the enumerate() function,

see Looping Techniques.
A strange thing happens if you just print a range:

>>>
>>> print(range(10))
range(0, 10)

In many ways the object returned by range() behaves as if it is a list, but in fact it
isn’t. It is an object which returns the successive items of the desired sequence when
you iterate over it, but it doesn’t really make the list, thus saving space.

We say such an object is iterable, that is, suitable as a target for functions and
constructs that expect something from which they can obtain successive items until
the supply is exhausted. We have seen that the for statement is such an iterator. The
function list() is another; it creates lists from iterables:

>>>
>>> list(range(5))
[0, 1, 2, 3, 4]

Later we will see more functions that return iterables and take iterables as argument.

## break and continue Statements,and else Clauses

on Loops
The break statement, like in C, breaks out of the innermost
enclosing for or while loop.

Loop statements may have an else clause; it is executed when the loop terminates
through exhaustion of the list (with for) or when the condition becomes false
(with while), but not when the loop is terminated by a breakstatement. This is
exemplified by the following loop, which searches for prime numbers:

>>>
>>> for n in range(2, 10):
... for x in range(2, n):
... if n % x == 0:
... print(n, 'equals', x, '*', n//x)
... break
... else:
... # loop fell through without finding a factor
... print(n, 'is a prime number')
...
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3

(Yes, this is the correct code. Look closely: the else clause belongs to
the for loop, not the if statement.)

When used with a loop, the else clause has more in common with the else clause
of a try statement than it does that of if statements: a try statement’s else clause
runs when no exception occurs, and a loop’s else clause runs when
no break occurs. For more on the try statement and exceptions, see Handling
Exceptions.

The continue statement, also borrowed from C, continues with the next iteration of
the loop:

>>>
>>> for num in range(2, 10):
... if num % 2 == 0:
... print("Found an even number", num)
... continue
... print("Found a number", num)
Found an even number 2
Found a number 3
Found an even number 4
Found a number 5
Found an even number 6
Found a number 7
Found an even number 8
Found a number 9

pass Statements
The pass statement does nothing. It can be used when a statement is required
syntactically but the program requires no action. For example:

>>>
>>> while True:
... pass # Busy-wait for keyboard interrupt (Ctrl+C)
...
This is commonly used for creating minimal classes:

>>>
>>> class MyEmptyClass:
... pass
...

Another place pass can be used is as a place-holder for a function or conditional body
when you are working on new code, allowing you to keep thinking at a more abstract
level. The pass is silently ignored:

>>>
>>> def initlog(*args):
... pass # Remember to implement this!
...

Functions
Defining Functions
We can create a function that writes the Fibonacci series to an arbitrary boundary:

>>>
>>> def fib(n): # write Fibonacci series up to n
... """Print a Fibonacci series up to n."""
... a, b = 0, 1
... while a < n:
... print(a, end=' ')
... a, b = b, a+b
... print()
...
>>> # Now call the function we just defined:
... fib(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

The keyword def introduces a function definition. It must be followed by the function
name and the parenthesized list of formal parameters. The statements that form the
body of the function start at the next line, and must be indented.

The first statement of the function body can optionally be a string literal; this string
literal is the function’s documentation string, or docstring. (More about docstrings can
be found in the section Documentation Strings.) There are tools which use docstrings
to automatically produce online or printed documentation, or to let the user
interactively browse through code; it’s good practice to include docstrings in code that
you write, so make a habit of it.

The execution of a function introduces a new symbol table used for the local variables
of the function. More precisely, all variable assignments in a function store the value
in the local symbol table; whereas variable references first look in the local symbol
table, then in the local symbol tables of enclosing functions, then in the global symbol
table, and finally in the table of built-in names. Thus, global variables cannot be directly
assigned a value within a function (unless named in a global statement), although
they may be referenced.

The actual parameters (arguments) to a function call are introduced in the local symbol
table of the called function when it is called; thus, arguments are passed using call by
value (where the value is always an object reference, not the value of the
object). [1] When a function calls another function, a new local symbol table is created
for that call.

A function definition introduces the function name in the current symbol table. The
value of the function name has a type that is recognized by the interpreter as a user-
defined function. This value can be assigned to another name which can then also be
used as a function. This serves as a general renaming mechanism:

## More on Defining Functions

It is also possible to define functions with a variable number of arguments. There are
three forms, which can be combined.

## 4.7.1. Default Argument Values

The most useful form is to specify a default value for one or more arguments. This
creates a function that can be called with fewer arguments than it is defined to allow.
For example:

while True:
ok = input(prompt)
if ok in ('y', 'ye', 'yes'):
return True
if ok in ('n', 'no', 'nop', 'nope'):
return False
retries = retries - 1
if retries < 0:
raise ValueError('invalid user response')
print(reminder)

##  giving only the mandatory

argument: ask_ok('Do you really want to quit?')
 giving one of the optional
arguments: ask_ok('OK to overwrite the file?', 2)
 or even giving all
arguments: ask_ok('OK to overwrite the file?', 2, 'Come on, on
ly yes or no!')

This example also introduces the in keyword. This tests whether or not a sequence
contains a certain value.

## The default values are evaluated at the point of function definition in

the defining scope, so that

i = 5

def f(arg=i):
print(arg)

i = 6
f()

will print 5.

Keyword Arguments
Functions can also be called using keyword arguments of the form kwarg=value. For
instance, the following function:

## def parrot(voltage, state='a stiff', action='voom',

type='Norwegian Blue'):
print("-- This parrot wouldn't", action, end=' ')
print("if you put", voltage, "volts through it.")
print("-- Lovely plumage, the", type)
print("-- It's", state, "!")

## accepts one required argument (voltage) and three optional arguments

(state, action, and type). This function can be called in any of the following ways:
parrot(1000) # 1
positional argument
parrot(voltage=1000) # 1 keyword
argument
parrot(voltage=1000000, action='VOOOOOM') # 2 keyword
arguments
parrot(action='VOOOOOM', voltage=1000000) # 2 keyword
arguments
parrot('a million', 'bereft of life', 'jump') # 3
positional arguments
parrot('a thousand', state='pushing up the daisies') # 1
positional, 1 keyword

## parrot() # required argument missing

parrot(voltage=5.0, 'dead') # non-keyword argument after a
keyword argument
parrot(110, voltage=220) # duplicate value for the same
argument
parrot(actor='John Cleese') # unknown keyword argument

## Arbitrary Argument Lists

Finally, the least frequently used option is to specify that a function can be called with
an arbitrary number of arguments. These arguments will be wrapped up in a tuple
(see Tuples and Sequences). Before the variable number of arguments, zero or more
normal arguments may occur.

## def write_multiple_items(file, separator, *args):

file.write(separator.join(args))

Normally, these variadic arguments will be last in the list of formal parameters,
because they scoop up all remaining input arguments that are passed to the function.
Any formal parameters which occur after the *args parameter are ‘keyword-only’
arguments, meaning that they can only be used as keywords rather than positional
arguments.

>>>

## >>> def concat(*args, sep="/"):

... return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'

## Notation (Pseudo Code, Flow Chart, Programming Language)

pseudo code
Pseudo code is an ordered version of your code written for human understanding rather than
machine understanding.

##  not be in a specific coding language

 draft the structure of your code
 be understandable to humans

e.g.

pseudo code

## if number <= 10 then

ouput small number sentence

python code

## if number <= 10:

print("That's a small number!")
Pseudo code may seem unnecessary but it is very useful to draft bits of code without worrying
about the specifics of making it understandable to a computer.

flowcharts
A standard way to plan code is with flowcharts.

Symbol

## Ovals show a start point or end

Start/End point in the code
Arrows show connections
between different parts of the
Connection code

## Rectangles show processes

e.g. calculations
(most things the computer does
that does not involve an input,
Process output or decision)

## Parallelograms show inputs and

outputs
(remember print is normally an
Input/Output input)

Diamonds show a
decision/conditional
Conditional/ (this is normally if, else if/elif,
Decision while and for)

There is no one right or wrong way to label flowcharts; you are presenting the structure of your code in a
way that humans can understand. Only add extra details to parts of the flowchart when it is not obvious
what they do.

creating flowcharts
There are many tools you can use to create flow charts.

draw.io is a good online tool you can use to create flowcharts. You can logon with a Google Drive
account.

Example 1
This program asks the user their name then says "Hello [Name]":

## flowchart for Example 1

Note: This flow chart only shows ovals and parallelograms because the code only has
a start and end and one input and one output.

## Python code for Example 1

name = input("What is your name?")
print("Hello " + name)

## representing loops in flowcharts

The common way to represent loops is with a diamond symbol:

This is the same way we show a decision (if, else if/elif etc).
For loops normally repeat a nested block of code a set number of times or for a set range of
data.

## while loops repeat a set block of code while a condition is true.

The diamond symbol is used with loops to show the way the computer repeats a nested block of
code then moves on to the next step.

One of the easiest ways to think about it is that there are normally two paths the computer can go
down when it reaches a loop:

##  the code in the loop

 the code after the loop

For example:

i = 0
while i < 10 :
print ( i )
i += 1

Note: In this example there is no code after the loop so we represent this with the end symbol.

functions in flowcharts
Another type of symbol that is important for more advanced programs is the symbol used for functions.
Functions are sometimes also known as modules or pre-defined processes; they represent a completed
block of code that can be called form other parts of a program.

Functions are not taught in the Level 1 Python course and may not be required for basic programs.

(They are not required for Level 1 NCEA in New Zealand but they are for Level 2.)

## representing the contents of functions in flowcharts.

A common way to represent the contents of functions is with separate unconnected flowcharts.

The name of the function is normally added to the start symbol in these unconnected flowcharts.

There may be times when you do not need to show the contents of every function in your flowchart.

The example below shows a rough diagram of a program that makes use of functions that convert
Farenheit to Celcius and compare the current temperate to the monthly average. This is a rough example
only to show the idea of how you can represent functions in flowcharts; often the functions will be far
more complex than the examples below.
different types of inputs
When planning and creating code, it is important that you think about the different types of input

Inputs can be integers (whole numbers), floats (numbers with decimal places), strings (text
characters) or even empty.

In Python when we create a variable with input we can specificy what type of variable it is.

For example:
integerInput = int ( input ( "Please input an integer e.g. 10" ))
floatInput = float ( input ( "Please input a float e.g. 3.75" ))
stringInput = str ( input ( "Please input a string e.g. Hello" ))

## Important things to note:

Strings can contain numbers; this will simply add the characters for these numbers into a string.

Floats can have numbers after a decimal place but do not need them.

## expected, boundary and invalid inputs

When testing and planning code we define three different types of inputs.

Types of inputs:

 expected
 invalid
 boundary
Expected inputs are inputs that we expect the users to add.

Expected inputs will be the right type of data within the expected range required for the program to work
properly.

Boundary inputs are numbers that are just inside and outside of the required range when inputs are
numbers (integers or floats).
Invalid inputs are inputs that we would not expect the user to add.

Invalid inputs will be the wrong type of data or outside of the range required for the program to work
properly.

## planning for different inputs

Often users will not use programs in the way we expect them to; for this reason it is important to plan,
create and test programs for the range expected, boundary and invalid inputs.

There are several different ways to plan and create programs that respond
to invalid and boundary inputs.
Problems when strings are input
When an input is required to be a string the computer will allow any combination of text characters
numbers and symbols to be entered into that string.

Common problems related to inputting strings often relate to variations in capitalisation, spacing and
spelling; the program won't recognise the string if the string the user enters differs to the coded string
the computer uses to evaluate the input.

We can account for these errors by getting the program to decapitalise or capitalise characters in strings
and remove spaces.

## Example of planning for different inputs where

strings are converted
answer = input("Which metal is heavier, silver or gold?")
print("That is correct, you get 10 points!")
else:
print("Sorry, that is incorrect, you lose 2 points")
.lower() and .strip() change text characters to lower case and strip spaces from the start and
end of inputs; note it will not strip spaces from between words.
Else statements as used above are a very common and effective way of dealing with invalid
inputs.
In the type of code above we will not get any errors from invalid inputs. Because an 'else'
statement has been used any input that is not gold, GOLD, Gold etc... will lead the program to
run the code indented below the else statement.

## Algorithmic Problem Solving

We learn by seeing others solve problems and by
solving problems by ourselves. Being exposed to different problem-solving
techniques and seeing how different algorithms are designed helps us to take
on the next challenging problem that we are given. By considering a number
of different algorithms, we can begin to develop pattern recognition so that
the next time a similar problem arises, we are better able to solve it.

Algorithms are often quite different from one another. Consider the example
of sqrt seen earlier. It is entirely possible that there are many different ways
to implement the details to compute the square root function. One algorithm
may use many fewer resources than another. One algorithm might take 10
times as long to return the result as the other. We would like to have some
way to compare these two solutions. Even though they both work, one is
perhaps “better” than the other. We might suggest that one is more efficient
or that one simply works faster or uses less memory. As we study algorithms,
we can learn analysis techniques that allow us to compare and contrast
solutions based solely on their own characteristics, not the characteristics of
the program or computer used to implement them.

## In the worst case scenario, we may have a problem that is intractable,

meaning that there is no algorithm that can solve the problem in a realistic
amount of time. It is important to be able to distinguish between those
problems that have solutions, those that do not, and those where
solutions exist but require too much time or other resources to work
reasonably.
There will often be trade-offs that we will need to identify and decide upon. As
computer scientists, in addition to our ability to solve problems, we will also
need to know and understand solution evaluation techniques. In the end, there are
often many ways to solve a problem. Finding a solution and then deciding whether it
is a good one are tasks that we will do over and over again.

## Simple strategies for developing algorithms (iteration, recursion)

Iteration
Computers are often used to automate repetitive tasks. Repeating identical or similar
tasks without making errors is something that computers do well and people do poorly.

## Repeated execution of a set of statements is called iteration. Because iteration is so

common, Python provides several language features to make it easier.

## The for loop revisited

Recall that the for loop processes each item in a list. Each item in turn is (re-)assigned to
the loop variable, and the body of the loop is executed. We saw this example in an earlier
chapter:

## for f in ["Joe", "Zoe", "Brad", "Angelina", "Zuki", "Thandi",

"Paris"]:
1
invitation = "Hi " + f + ". Please come to my party on
2
Saturday!"
3
print(invitation)

Running through all the items in a list is called traversing the list, or traversal.
The while statement
Here is a fragment of code that demonstrates the use of the while statement:

def sum_to(n):
1 """ Return the sum of 1+2+3 ... n """
2 ss = 0
3 v = 1
4 while v <= n:
5 ss = ss + v
6 v = v + 1
7 return ss
8
9 # For your test suite
10 test(sum_to(4) == 10)
11 test(sum_to(1000) == 500500)
12

You can almost read the while statement as if it were English. It means, while v is less
than or equal to n, continue executing the body of the loop. Within the body, each time,
increment v. When v passes n, return your accumulated sum.

## More formally, here is precise flow of execution for a while statement:

 Evaluate the condition at line 5, yielding a value which is either False or True.
 If the value is False, exit the while statement and continue execution at the next
statement (line 8 in this case).
 If the value is True, execute each of the statements in the body (lines 6 and 7) and
then go back to the while statement at line 5.

The body consists of all of the statements indented below the while keyword.

Notice that if the loop condition is False the first time we get loop, the statements in the
body of the loop are never executed.

The body of the loop should change the value of one or more variables so that eventually
the condition becomes false and the loop terminates. Otherwise the loop will repeat
forever, which is called an infinite loop. An endless source of amusement for computer
scientists is the observation that the directions on shampoo, “lather, rinse, repeat”, are an
infinite loop.

Two-dimensional tables
A two-dimensional table is a table where you read the value at the intersection of a row
and a column. A multiplication table is a good example. Let’s say you want to print a
multiplication table for the values from 1 to 6.
A good way to start is to write a loop that prints the multiples of 2, all on one line:

## for i in range(1, 7):

1
print(2 * i, end=" ")
2
print()
3

Here we’ve used the range function, but made it start its sequence at 1. As the loop
executes, the value of i changes from 1 to 6. When all the elements of the range have
been assigned to i, the loop terminates. Each time through the loop, it displays the value
of 2 * i, followed by three spaces.

Recursion
Recursion means “defining something in terms of itself” usually at some smaller scale,
perhaps multiple times, to achieve your objective. For example, we might say “A human
being is someone whose mother is a human being”, or “a directory is a structure that
holds files and (smaller) directories”, or “a family tree starts with a couple who have
children, each with their own family sub-trees”.

Programming languages generally support recursion, which means that, in order to solve
a problem, functions can call themselves to solve smaller sub problems.

Drawing Fractals
For our purposes, a fractal is a drawing which also has self-similar structure, where it can
be defined in terms of itself.

Let us start by looking at the famous Koch fractal. An order 0 Koch fractal is simply a
straight line of a given size.

An order 1 Koch fractal is obtained like this: instead of drawing just one line, draw
instead four smaller segments, in the pattern shown here:

Now what would happen if we repeated this Koch pattern again on each of the order 1
segments? We’d get this order 2 Koch fractal:
Repeating our pattern again gets us an order 3 Koch fractal:

Now let us think about it the other way around. To draw a Koch fractal of order 3, we can
simply draw four order 2 Koch fractals. But each of these in turn needs four order 1 Koch
fractals, and each of those in turn needs four order 0 fractals. Ultimately, the only
drawing that will take place is at order 0. This is very simple to code up in Python:

## ef koch(t, order, size):

"""
Make turtle t draw a Koch fractal of 'order' and 'size'.
Leave the turtle facing the same direction.
"""

## if order == 0: # The base case is just a straight line

t.forward(size)
else:
koch(t, order-1, size/3) # Go 1/3 of the way
t.left(60)
koch(t, order-1, size/3)
t.right(120)
koch(t, order-1, size/3)
t.left(60)
koch(t, order-1, size/3)

Fibonacci numbers
The famous Fibonacci sequence 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 134, ... was devised
by Fibonacci (1170-1250), who used this to model the breeding of (pairs) of rabbits. If, in
generation 7 you had 21 pairs in total, of which 13 were adults, then next generation the
adults will all have bred new children, and the previous children will have grown up to
become adults. So in generation 8 you’ll have 13+21=34, of which 21 are adults.
def fib(n):
if n <= 1:
return n
t = fib(n-1) + fib(n-2)
return t

## Illustrative problems: find minimum in a list, insert a card in a list of sorted

cards, guess an integer number in a range, towers of hanoi.
find minimum in a list
list =[1,2,3,4,5,0,7,-1]
print(min(list))

Output
D:\Python\python.exe "C:/Users/G/PycharmProjects/Test/min in list.py"
-1
Process finished with exit code 0

## insert a card in a list of sorted cards

We can add one item to a list using append() method or add several items
using extend()method.
card = [‘a’, ‘i’]
card.append(‘o’)
print(card)
# Output: [‘a’, ‘i’, ‘o’]
card.extend([‘u’])
print(card)
# Output: [‘a’, ‘i’, ‘o’, ‘u’]
card = ['a', 'i', 'o', ‘u’]
# inserting element to list at 2nd position
card.insert(2, 'e')
print(card)
# Output: [‘a’, ‘e’, ‘i’, ‘o’, ‘u’]

## guess an integer number in a range

import random
rng = random.Random()
number = rng.randrange(1, 1000) # Get random number between [1 and 1000).

guesses = 0
msg = ""

while True:
guess = int(input(msg + "\nGuess my number between 1 and 1000: "))
guesses += 1
if guess > number:
msg += str(guess) + " is too high.\n"
elif guess < number:
msg += str(guess) + " is too low.\n"
else:
break

## input("\n\nGreat, you got it in {0} guesses!\n\n".format(guesses))

output
D:\Python\python.exe 50 is too low. 199 is too low. Guess my number
"C:/Users/G/PycharmProj 500 is too high. 200 is too low. between 1 and 1000: 297
ects/Test/guess the 100 is too low. 250 is too low. 30 is too low.
number.py" 190 is too low. 300 is too high. 50 is too low.
195 is too low. 500 is too high.
Guess my number 193 is too low. Guess my number 100 is too low.
between 1 and 1000: 30 195 is too low. between 1 and 1000: 260 190 is too low.
30 is too low. 30 is too low. 195 is too low.
Guess my number 50 is too low. 193 is too low.
Guess my number between 1 and 1000: 199 500 is too high. 195 is too low.
between 1 and 1000: 50 30 is too low. 100 is too low. 199 is too low.
30 is too low. 50 is too low. 190 is too low. 200 is too low.
50 is too low. 500 is too high. 195 is too low. 250 is too low.
100 is too low. 193 is too low. 300 is too high.
Guess my number 190 is too low. 195 is too low. 260 is too low.
between 1 and 1000: 500 195 is too low. 199 is too low. 290 is too low.
30 is too low. 193 is too low. 200 is too low. 295 is too high.
50 is too low. 195 is too low. 250 is too low. 297 is too high.
500 is too high. 199 is too low. 300 is too high.
260 is too low. Guess my number
Guess my number Guess my number between 1 and 1000: 293
between 1 and 1000: 100 between 1 and 1000: 200 Guess my number
30 is too low. 30 is too low. between 1 and 1000: 290
50 is too low. 50 is too low. 30 is too low. Great, you got it in 17
500 is too high. 500 is too high. 50 is too low. guesses!
100 is too low. 100 is too low. 500 is too high.
190 is too low. 100 is too low.
Guess my number 195 is too low. 190 is too low.
between 1 and 1000: 190 193 is too low. 195 is too low.
30 is too low. 195 is too low. 193 is too low.
50 is too low. 199 is too low. 195 is too low.
500 is too high. 200 is too low. 199 is too low.
100 is too low. 200 is too low.
190 is too low. Guess my number 250 is too low.
between 1 and 1000: 250 300 is too high.
Guess my number 30 is too low. 260 is too low.
between 1 and 1000: 195 50 is too low. 290 is too low.
30 is too low. 500 is too high.
50 is too low. 100 is too low. Guess my number
500 is too high. 190 is too low. between 1 and 1000: 295
100 is too low. 195 is too low. 30 is too low.
190 is too low. 193 is too low. 50 is too low.
195 is too low. 195 is too low. 500 is too high.
199 is too low. 100 is too low.
Guess my number 200 is too low. 190 is too low.
between 1 and 1000: 193 250 is too low. 195 is too low.
30 is too low. 193 is too low.
50 is too low. Guess my number 195 is too low.
500 is too high. between 1 and 1000: 300 199 is too low.
100 is too low. 30 is too low. 200 is too low.
190 is too low. 50 is too low. 250 is too low.
195 is too low. 500 is too high. 300 is too high.
193 is too low. 100 is too low. 260 is too low.
190 is too low. 290 is too low.
Guess my number 195 is too low. 295 is too high.
between 1 and 1000: 195 193 is too low.
30 is too low. 195 is too low.
Tower of haoni
def hanoi(n, source, helper, target):
print
"hanoi( ", n, source, helper, target, " called"
if n > 0:
# move tower of size n - 1 to helper:
hanoi(n - 1, source, target, helper)
# move disk from source peg to target peg
if source[0]:
disk = source[0].pop()
print("moving " + str(disk) + " from " + source[1] + " to " +
target[1])
target[0].append(disk)
# move tower of size n-1 from helper to target
hanoi(n - 1, helper, source, target)

## source = ([4, 3, 2, 1], "source")

target = ([], "target")
helper = ([], "helper")
hanoi(len(source[0]), source, helper, target)

## print(source, helper, target)

output
D:\Python\python.exe "C:/Users/G/PycharmProjects/Test/tower of hanoi.py"
moving 1 from source to helper
moving 2 from source to target
moving 1 from helper to target
moving 3 from source to helper
moving 1 from target to source
moving 2 from target to helper
moving 1 from source to helper
moving 4 from source to target
moving 1 from helper to target
moving 2 from helper to source
moving 1 from target to source
moving 3 from helper to target
moving 1 from source to helper
moving 2 from source to target
moving 1 from helper to target
([], 'source') ([], 'helper') ([4, 3, 2, 1], 'target')