Lecture 4: Decisions - PowerPoint PPT Presentation

About This Presentation
Title:

Lecture 4: Decisions

Description:

Lecture 4: Decisions Or else – PowerPoint PPT presentation

Number of Views:81
Avg rating:3.0/5.0
Slides: 43
Provided by: Goog6698
Category:

less

Transcript and Presenter's Notes

Title: Lecture 4: Decisions


1
Lecture 4 Decisions
  • Or else

2
If-Else
  • So that your functions can act differently
    depending on the situation, they need to be able
    to make decisions
  • In C, the statement for choosing exactly one from
    two possible alternatives is if-else.
  • The statement is of the form if(C) B1 else
    B2 , where C is an expression and B1 and B2 are
    blocks of statements
  • Curly braces make blocks unambiguous
  • When an if-statement is executed, the condition C
    is first evaluated, and depending on whether it
    is true or false, execute precisely one of the
    bodies B1 and B2.
  • If you have nothing meaningful to put in B2, you
    can leave it out entirely, along with the keyword
    else

3
Example The Sign of an Integer
  • / Returns the sign (, -, 0) of its argument as
    a char. /
  • char sign(int num)
  •     if(num lt 0) return '-'
  •     else
  •         / We can nest if-else for multiple
    choices. /
  •         if(num gt 0) return ''
  •         else return '0'
  •    
  •             

4
Comparison Operators
  • Comparison operators are operators just like or
  • The previous example demonstrated the comparison
    operators lt and gt for "less than" and "greater
    than"
  • C also has comparison operators lt and gt for
    "less or equal" and "greater or equal"
  • For equality comparison, since the operator
    already stands for assignment, the operator is
    used instead for equality comparison
  • For inequality comparisons, use the operator !

5
Truth Values
  • Unlike many other high-level languages, C does
    not have a separate boolean data type for truth
    values true and false
  • Instead, C uses int to represent truth values so
    that 0 stands for false, and any nonzero value
    stands for true
  • Combined with the unfortunate fact that stands
    for assignment, this causes endless errors, since
    the statement if(a b) ...  is perfectly
    legal in C
  • Instead of testing whether a equals b, this
    statement first assigns the value from b to a,
    and then implicitly tests if this value was
    nonzero
  • In C, all comparisons always evaluate to either 0
    or 1
  • What does (a 0) (b 0) (c 0) compute?

6
Example Maximum of Three
  • / Given three numbers, find and return the
    largest one. /
  • int max(int a, int b, int c)
  •     int m a / Largest number we have seen so
    far. /
  •     if(b gt m) m b
  •     / At this point, m contains the larger value
    of a and b. /
  •     if(c gt m) m c
  •     / At this point, m contains the largest of
    the values of
  •     a, b and c. Exactly what we were asked to
    find. /
  •     return m

7
Leaving out Redundant Braces
  • If a block of code consists of a single
    statement, it is legal (but dangerous) to leave
    out the braces around it
  • However, once the block contains two or more
    statements, the braces are necessary to make the
    code unambiguous
  • The infamous hanging else problem
  • if(a lt b) / condition 1 /
  •     if(c lt d)  / condition 2 /
  •         printf("One")
  •     else / so, is this now alternative to 1 or
    2 ? /
  •         printf("Two")

8
Ladders
  • By nesting if-else statements, we can make a
    selection from any fixed number of possibilities.
  • One idiomatic way to do this is to indent the
    code and leave out redundant curly braces to
    create a ladder, a structure that executes
    precisely one of several possibilities, the one
    whose condition first evaluates to true, skipping
    the rest
  • if(C1) B1
  • else if(C2) B2 
  • ...
  • else if(Cn) Bn 
  • else Bn1 / Last branch unconditional /

9
Combining Conditions
  • We know how to test if the integer x is larger
    than 0, but how to test if x is strictly between
    0 and 100?
  • The condition (0 lt x lt 100) looks reasonable but
    is always true, regardless of the value of x
  • This condition first checks whether 0 lt x, which
    is always either 0 or 1, and then checks whether
    this 0 or 1 is less than 100, which it inevitably
    is
  • We rather want to say "0 lt x and x lt 100"...
  • The desired effect can be achieved by nesting
    conditions, but this would get complicated if
    there is an else-branch, or if we wanted to
    combine the subconditions with logical or

10
Logical Operators
  • Fortunately, C does have operators and, or and
    not that you can freely use to combine individual
    conditions into arbitrarily complex compound
    conditions
  • Unfortunately, these operators are not named and,
    or and not, the way some reasonable person might
    have named them, but they are denoted with
    symbols , and !
  • For example, the problematic condition of the
    previous slide would be written as (0 lt x x lt
    100)
  • When using both  and  in a condition, note
    that has higher precedence than
  • C also has operators , ! and that perform
    these logical operations bitwise, for each bit in
    parallel
  • For example, 2 4 1, but 2 4 0

11
Days In Month
  • / Ignoring leap years, return the number of days
    in the
  • given month. This implementation uses a ladder.
    /
  • int days_in_month(int m)
  •     if(m lt 1 m gt 12) return -1 / Error
    /
  •     else if(m 2) return 28
  •     else if(m 4 m 6 m 9 m
    11)
  •         return 30
  •    
  •     else return 31

12
Maximum of Three, Again
  • int max(int a, int b, int c)
  •     if(a gt b a gt c) return a
  •     / At this point, we know that a cannot be
    maximum,
  •     so return whichever of is larger of b and c.
    /
  •     if(b gt c) return b
  •     else return c
  •    
  •     / Verify that this function also works
    correctly even if
  •     any two, or even all three, of the arguments
    are equal./

13
Median of Three, First Way
  • int median(int a, int b, int c)
  •     if(a lt b b lt c) return b
  •     if(c lt b b lt a) return b
  •     / At this point, we know that b was not the
    median./    
  •     if(b lt a a lt c) return a
  •     if(c lt a a lt b) return a
  •     / At this point, we know that a was not the
    median./
  •     return c / Only one possibility therefore
    remains /

14
Median of Three, Another Way
  • int median(int a, int b, int c)
  •     if(a gt b a gt c) / a is the maximum /
  •         if(b gt c) return b
  •         else return c
  •     
  •     if (a lt b a lt c) / a is the minimum /
  •         if(b gt c) return c
  •         else return b
  •    
  •     / a is neither maximum or minimum, so... /
  •     return a

15
Example Leap Year
  • Given a year, check whether it is a leap year
  • To be a leap year, the year must be divisible by
    4
  • However, the complete rule says the year also may
    not be divisible by 100, unless it is also
    divisible by 400
  • We have leap years because each seasonal year is
    about 365.24... days, so without the leap year
    correction, our calendar years and seasons would
    slowly drift apart
  • To correct this, lengthen the calendar year by
    1/4 day every year... or simpler, by one day
    every four years
  • Since the discrepancy is not exactly 1/4, an
    additional correction is needed every 25th leap
    year (every 100 years), and every 100th leap
    year (every 400 years)

16
Leap Year Code
  • int is_leap_year(int y)
  •     if(y 4 ! 0) return 0
  •     / Here, we know the year is divisible by 4.
    /
  •     if(y 100 ! 0) return 1
  •     / Here, we know the year is divisible by
    100. /
  •     if(y 400 ! 0) return 0
  •     / Here, we know the year is divisible by
    400. /
  •     return 1
  •     / Note how the last return is unconditional.
    /

17
Leap Year Code, Reverse Logic
  • int is_leap_year(int y)
  •     if(y 400 0) return 1
  •     / Here, we know the year is not divisible by
    400. /
  •     if(y 100 0) return 0
  •     / Here, we know the year is not divisible by
    100. /
  •     if(y 4 0) return 1
  •     / Here, we know the year is not divisible by
    4. /
  •     return 0
  •     / Again, the last return is unconditional.
    /

18
Leap Year Code, One-Liner Version
  • int is_leap_year(int y)
  •     return y 4 0 ( y 100 ! 0 y
    400 0)
  • / This probably looks weird at first, but works
    fine. The expression whose value we return
    essentially says that "y is divisible by 4, and,
    if y is divisible by 100, it is also divisible by
    400." In propositional logic, the implication (if
    A, then B) can be written (not-A or B), and both
    formulas always evaluate to the same result. /

19
Meaning of If-Then
  • In the everyday parlance, "if-then" has two
    different meanings, and corresponds to two
    different C things
  • Condition-Action "If you are thirsty, drink
    water"
  • Assumes time, implemented as an if-statement
  • Cause-Effect "If x gt y gt 1, then x2 gt y2"
  • Exists outside time in an unchanging Platonic
    world of mathematical truths, so this could not
    be an if-statement, but rather a propositional
    logic implication
  • (x gt y y gt 1) implies (x2 gt y2)
  • Propositional logic implication is syntactic
    sugar for the disjunction with the antecedent
    negated
  • !(x gt y y gt 1) (xx gt yy)

20
Example Solving the Quadratic
  • int quadratic(double a, double b, double c,
    double x1, double x2)
  •     double D  b b - 4 a c
  •     if(fabs(D) lt 0.0000001) / Double root /
  •         x1 x2 -b / (2 a)
  •         return 1
  •    
  •     else if(D gt 0) / Two real roots /
  •         x1 (-b sqrt(D)) / (2a) / Assume
    include ltmath.hgt /
  •         x2  (-b - sqrt(D)) / (2a)
  •         return 2
  •    
  •     else return 0 / No real roots /

21
Statements and Values
  • Consider the statement if(a lt b) c 42 else
    c 99 that sets the value of c depending on
    whether a lt b
  • A clever wag might try to rewrite this statement
    in a more concise form c if(a lt b) 42 else
    99
  • This idea works in many languages, but does not
    even compile in C that distinguishes between
    statements (that do something) and
    expressions (that evaluate to a value)
  • Since if-else is not an expression, trying to
    evaluate it and assign the result to a variable
    is nonsense
  • Similarly, trying to use an expression such as
    22 as an individual statement would be nonsense,
    since it wouldn't do anything, and thus is not
    allowed to compile

22
Ternary Selection
  • The ternary selection operator works as a simple
    if-else, but in a way that it is an expression,
    not a statement
  • The expression (a lt b) ? 42 99 consists of
    three parts. When evaluating this expression, its
    first condition is evaluated, and depending on
    whether it is true or false, evaluate one of the
    two expressions separated by colon, and use its
    value as the value of the entire expression
  • Ternary selection is used for small decisions in
    places we don't want to split into multiple
    if-else statements
  • printf("d s", x, ((x 1)? "item" "items") )

23
Switch
  • switch is a decision structure that allows the
    choice of one case based on an integer index, not
    a truth value
  • Alternative to the if-else ladder
  • After the keyword, a switch statement has an
    integer expression, followed by the possible
    cases in braces
  • May also have an optional a default case, sort of
    in the sense of "none of the above"
  • Execution evaluates the expression and jumps to
    the case corresponding to the value
  • Theoretically, compiler can create a jump table
    to make this faster than stepping through an
    if-else ladder

24
Falling through
  • Switch behaves very differently from what a
    reasonable person might assume
  • The execution jumps to the case given by the
    expression, but after executing that case, the
    execution does not skip the remaining cases and
    exit the switch
  • Instead, the execution continues the cases in
    order until a break statement causes the switch
    to exit
  • This fallthrough behaviour causes many logic
    errors
  • Its advantage is that we can combine cases with
    identical bodies into one case, leaving all cases
    but one empty and letting execution fall through
    them to the last one
  • If you believe that you understand C loops and
    pointers well, explain me how Duff's device works

25
Example Days in Month
  • int days_in_month(int m)
  •     int d
  •     switch(m)
  •         case 1 case 3 case 5 case 7 case
    8 case 10 case 12
  •             d 31 break
  •         case 4 case 6 case 9 case 11
  •             d 30 break
  •         case 2
  •             d 28 break
  •         default
  •             d -1 break
  •    
  •     return d

26
Short Circuit Evaluation
  • C does not guarantee the order in which
    subexpressions are evaluated in formulas such as
    f(x) g(y), unless one of them needs the result
    of other one, as in f(g(y))
  • However, C guarantees that logical expressions
    are always evaluated strictly from left to right
  • For a condition of the form (A B) to be true,
    both A and B must be true, so as soon as A turns
    out to be false, the truth value of B cannot make
    any difference
  • Short circuit evaluation means that B is not
    evaluated in this case at all (important, if B
    has side effects)
  • This trick can be used in expressions where the
    side effect would be a runtime crash, such as (x
    ! 0 z/x gt y)

27
Optimizing Compound Conditions
  • With short circuit evaluation, it might make a
    difference for execution time whether a test is A
    B, or B A 
  • This depends on how much each subcondition
    "costs" in time, and how likely it is to be true
  • Rough idea is to first do the test that is more
    likely to be false and cheaper to evaluate
  • Example A costs 4 units of time and has 30
    chance to be true, whereas B costs 3 units of
    time but has 60 chance to be true
  • Average cost of evaluating A B is 4 0.33
    4.9
  • Average cost of evaluating B A is 3 0.64
    5.4

28
De Morgan's Laws
  • Puzzle what does the expression !!a !!b
    !!c compute, when a, b and c are int variables?
  • Logical negations can obfuscate formulas
  • De Morgan's Laws point out that any condition of
    the form A B is equivalent to !(!A !B), and
    that any condition of the form A B is
    equivalent to !(!A !B)
  • Turns out to hold even with short circuit
    evaluation
  • These formulas are commonly applied from right to
    left, to get rid of excessive negations
  • e2 lt s1 e1 lt s2, the condition that two real
    line intervals s1,e1 and s2,e2 do not
    overlap, negated turns into the condition e2 gt
    s1 e1 gt s2 that they do overlap

29
Recursion
  • Recursion is a powerful technique to solve
    problems that are self-similar in that the
    problem can be expressed in terms of smaller
    versions of that problem
  • Classic example factorial n! 1 2 ... (n -
    1) n
  • Realize that n! (n - 1)! n, and the code
    writes itself
  • int factorial(int n)
  •     if(n lt 2) return 1 / base case /
  •     else return factorial(n - 1) n

30
Self-similarity
  • To be able to use recursion to solve some
    mathematical function f(n), you need to massage
    its right-hand side so that f appears there for
    some parameter value less than n
  • For example, n! n! is true, but useless for
    recursion
  • Especially if f is some complicated and powerful
    function, the more complicated and powerful it
    also is whenever it appears in the right hand
    side definition
  • Aim for f(n) s(f(n-1)) where s is some simple
    function
  • To avoid infinite regress, every recursion must
    have at least one base case that stops the
    descent (can have several, even infinitely many)
  • The recursive function always starts with the
    check whether you have reached a base case

31
Fibonacci Numbers
  • Fibonacci numbers are a famous series of integers
    where each value equals the sum of two previous
    values
  • 1, 1, 2, 3, 5, 8, 13, 21, 34, ...
  • Given the position index in the series (starting
    from 0, as is convention in programming), compute
    the value
  • For example, f(0) 1, f(6) 13, etc.
  • Easy to write as a branching recursive function
  • int fib(int n)
  •     if(n lt 2) return 1
  •     else return fib(n - 1) fib(n - 2)

32
Linear Iteration
  • As a special case, recursion can be used to do
    iteration without using the explicit loops of the
    next week
  • Problem output numbers from start to end
  • To apply recursion to this problem, we must
    somehow find the self-similarity hiding in it
  • Solution 1 first output the number start, then
    output numbers from start 1 to end 
  • Solution 2 compute the midpoint mid (start
    end) / 2, then output numbers from start to mid -
    1, then output mid, then output numbers from mid
    1 to end
  • Both solutions have the same base case of start gt
    end

33
Example Output Numbers
  • void output_numbers1(int start, int end)
  •     if(start gt end) return
  •     printf("d ", start)
  •     output_numbers(start 1, end)
  • void output_numbers2(int start, int end)
  •     int mid (start end) / 2
  •     if(start gt end) return
  •     output_numbers2(start, mid - 1)
  •     printf("d ", mid)
  •     output_numbers2(mid 1, end)

34
Towers of Hanoi
  • Towers of Hanoi is a classic puzzle that seems
    tricky but turns out to allow a simple recursive
    solution
  • There are three pegs numbered 1 to 3, and n disks
    with holes in their centers, each disk with a
    different radius
  • Initially, all disks are on the peg 1 in sorted
    order
  • A move consists of popping the topmost disk on
    any peg, and then pushing it on top of some other
    peg
  • Extra constraint to make the problem nontrivial
    at any time, the disks on each peg must be in
    sorted order
  • Problem move all the disks from peg 1 to peg 3

35
Recursive Solution
  • Towers of Hanoi can be in many different ways,
    but simplest recursive solution for n disks from
    1 to 3 first moves n - 1 disks from 1 to 2
  • With the smaller disks out of the way, pop the
    largest bottom disk from peg 1, and push it to
    3
  • Then move the n - 1 smaller disks from 2 to 3,
    and the problem is solved
  • To solve the problem for n disks, recursion must
    solve it twice for n - 1 smaller disks, thus four
    times for n - 2 disks, eight times for n - 3
    disks, ...
  • Running time exponential with respect to n

36
Towers of Hanoi in C
  • / This recursive implementation moves n disks
    from src to tgt, outputting the moves that it
    makes along the way. /
  • void hanoi(int src, int tgt, int n)
  •     int mid 6 - src - tgt / Handy trick /
  •     if(n lt 1) return
  •     hanoi(src, mid, n - 1)
  •     printf("Move disk from peg d to peg d\n",
    src, tgt)
  •     hanoi(mid, tgt, n - 1)

37
Example Euclid's GCD
  • / The greatest common divisor algorithm of
    Euclid. /
  • int gcd(int a, int b)
  •     if(b gt a) return gcd(b, a)
  •     else if(b 0) return a
  •     else return gcd(b, a b)
  • / Just because we have some extra space. /
  • int lcm(int a, int b) / e.g. lcm(15, 25) 75
    /
  •     return a / gcd(a, b) b

38
Example Binary Power
  • To compute ab the straightforward brute force
    way, you need b - 1 multiplications
  • If a is a huge matrix and b is large, this is too
    much work
  • But we can split, for example, a19  a16  a2  a1
  • Binary power algorithm turns ab to an equivalent
    formula depending on whether b is odd or even
  • If b is even, ab equals (a2)b/2
  • If b is odd, ab equals a  (a2)(b-1)/2 
  • Since b is cut in half each time, the number of
    operations is logarithmic with respect to
    b instead of linear
  • Still not optimal for multiplications, though

39
Binary Power Recursively
  • double pow(double base, int exp)
  •     if(exp lt 0) return 1.0 / pow(base, -exp)
  •     else if(exp 0) return 1
  •     else if(exp 2 0)
  •         return pow(base base, exp / 2)
  •    
  •     else
  •         return pow(base, exp - 1) base
  •    

40
Upsides of Recursion
  • Recursion turns out to be computationally
    universal in that combined with decisions, we can
    implement any computable function using recursion
    without explicit repetition with loops
  • Some problems are easier to conceptualize and
    solve with recursion, some with explicit loops
  • First programming courses rarely get to the
    interesting problems where recursive thinking
    truly shines, but these problems do exist and are
    hugely important
  • One-player search problems, two-player games,
    parsing the contents of various sorts of
    structured text and data...

41
Downsides of Recursion
  • Recursion has a couple of serious downsides that
    programmers need to be aware of before using
    recursion for real problems in actual production
    code
  • Since each function invocation always creates a
    new activation record, deep recursions may cause
    a stack overflow and crash the program
  • Branching recursions may repeatedly reach and
    solve the same subproblems over and over again
  • For example, the recursion example for Fibonacci
    numbers requires an exponential time to run!
  • Aware of these problems, programmers think
    recursively but use techniques of tail recursion,
    memoization, dynamic programming... to keep their
    recursions efficient

42
 
  • "Most of you are familiar with the virtues of a
    programmer.  There are three, of course
    laziness, impatience, and hubris."
  • Larry Wall
  • "It has often been said that a person does not
    really understand something until he teaches it
    to someone else. Actually a person does not
    really understand something until after teaching
    it to a computer, i.e., express it as an
    algorithm."
  • Donald Knuth
Write a Comment
User Comments (0)
About PowerShow.com