Warwick Business School Agenda: techniques and applications of conditional code Finding an optimal solution Arts Centre ‘manual optimisation’ (ArtsCentre2) Conditional (IF) statements ‘Pseudo-code’ Operational Control system (English) Traffic Lights! ‘Select - Case’ statements : Exercises Counting and Andrew’s Guessing Game! More fun next Lesson! Warwick Business School 1 Basic sequence of instructions This is what we have already seen, and is almost implicit: Instructions execute in sequence, each immediately after the previous instruction, unless the code (or the user) intervenes. e.g. Any recorded macro, such as: Importing file of data (Order1); generating and using a new set of random numbers; printing a routine set of data or charts from a workbook. Warwick Business School Sequence of execution of VBA The White Rabbit put on his spectacles. “Where shall I begin please, your Majesty?” he asked. “Begin at the beginning,” the King said, gravely, “and go on until you come to the end: then Stop.” Lewis Carroll: Alice in Wonderland Warwick Business School 2 Conditional instructions Often you need the application to perform different actions depending on the situation (as represented by certain variables). This diverts (‘branches’) the sequence of instructions. Basic syntax: Should I do a spin on this programme? If Condition Then Instructions Else Instructions End If Warwick Business School Structured Programming in VBA Conditional statements in VBA Example: Arts Centre problem Dynamic method to find the maximum revenue: First, express a simple algorithm, using ‘Pseudo-code’: Set optimum revenue, optimum # tickets to 0 Add 1 to # tickets If Revenue for this # tickets > previous maximum Then Save current # tickets and new optimal revenue End If Notice how we use additional cells in the user interface, and preferably name them for easy use in the macro. Warwick Business School 3 Structured Programming in VBA Conditional statements in VBA Next, write the code in VBA Sub Increment() 'N.B. Ensure variables are initialised before starting Range("TicketsInput").Value = Range("TicketsInput").Value + 1 If Range("RevenueOutput").Value > Range("OptimalRevenue").Value Then 'new maximum revenue, so replace the old one Range("OptimalRevenue").Value = Range("RevenueOutput").Value Range("OptimalTickets").Value = Range("TicketsInput").Value End If End Sub Watch & Test how this VBA code executes, and how it interacts with the spreadsheet Warwick Business School ArtsCentre1a -> ArtsCentre2.xlsm ‘Pseudo-code’: an intermediate representation of code A method of expressing program logic in a structured way, whilst not constrained by the need to know detailed nor accurate VBA code/syntax “Your own language” Keywords for conditions and loops Indentation to emphasise blocks of code within conditions or loops (structure) Description of instructions rather than VBA syntax Uses: Planning/designing; Documentation Warwick Business School 4 Flowchart vs Pseudo-code maps directly on to structured VBA Warwick Business School http://www.slideshare.net/nicky_walters/pseudocode-flowcharts Conditional statements in VBA Example: TrafficLights0 Write the pseudo-code: 1. How do we most easily refer to the ‘Red light’ cell? 2. How do we instruct Excel to change its colour? 3. How do we express the conditions? We examine different ways to code this. Warwick Business School 5 The full logic. But what is wrong with this ‘pseudo-code’?: If Red light is on and Yellow light is off Then Set Yellow light on End if If Red light is on And Yellow light is on Then Set Red light off Set Yellow light off Set Green light on End if If Green light is on Then Set Yellow light on Set Green light off End if If Yellow light is on And Green light is off Then Set Yellow light off Set Red light on End if Warwick Business School TrafficLights0: ChangeError ‘Nested’ IF instructions You might want to (pseudo-) code: If Red light is on Then If Yellow light is off then Set Yellow light on Else Set Red light off Set Yellow light off Set Green light on End if Else If Green light is on Then Set Yellow light on Set Green light off Else Set Red light on Set Yellow light off End if End if Warwick Business School This will work, but … TrafficLights0: ChangeNoStateNested 6 ‘Nested’ IF instructions The condition This block of instructions is followed if the condition is TRUE These block of instructions are followed if the condition is FALSE If Red light is on Then If Yellow light is off then Set Yellow light on Else Set Red light off Set Yellow light off Set Green light on End if Else If Green light is on Then Set Yellow light on Set Green light off Else Set Red light on Set Yellow light off End if End if The condition This instruction is followed if the condition is TRUE These instructions are followed if the condition is FALSE Warwick Business School Conditional statements format The following is valid: If Condition then Else Instruction End If Do nothing if the condition is true It could be expressed alternatively: If Not(Condition) then Instruction End If Or even (if there is only one instruction) If Not(Condition) then Instruction Warwick Business School 7 Conditional statements in VBA You can use ELSEIF to separate the different conditions more clearly If Range("Red").Interior.Color = vbRed And Range("Yellow").Interior.Color = vbWhite And Range("Green").Interior.Color = vbWhite Then Range("Red").Interior.Color = vbRed Range("Yellow").Interior.Color = vbYellow Range("Green").Interior.Color = vbWhite Note: ‘Compound conditions’ ELSEIF ... But beware if your code shows ‘ELSEIF:’ Warwick Business School TrafficLights0: ChangeNoState Conditional statements in VBA This ‘shortcut’ works: is it robust? If Red Set ElseIf Set Set Set ElseIf Set Set ElseIf Set Set ENDIF light is On And Yellow light is Off Then Yellow light On Red light is on and Yellow light is On Then Red light Off Yellow light Off Green light ON Green light is ON Then Yellow light ON Green light Off Yellow light is ON Then Red light ON Yellow light OFF Warwick Business School TrafficLights0: ChangeNoStateShortcut 8 Conditional statements in VBA Smarter coding technique: recognise the independent ‘cases’ / ‘states’ Don’t worry about coding the shapes – for now! Warwick Business School ‘State’ If State=4 then Set Red light on Set Yellow light off Set Green light off Set State = 1 Traffic Lights pseudo-code Else If State=1 Set Red light on Set Yellow light on Set Green light off Set State = 2 Else If State=2 Set Red light off Set Yellow light off Set Green light on Set State = 3 … End If End If End If Note the use of ‘Abstraction’ and the use of a ‘control variable’ to represent the different ‘states’. Neater, clearer, more systematic, more robust. How will we code this instruction? Uses Nested IF again; the indentation is a little unwieldy Add code for State=3 (Ex) TrafficLights0: ChangeStateNested 9 ‘State’ Select Case State Case 4 Traffic Lights pseudo-code i.e. If State=4 Set Red light on Set Yellow light off Set Green light off Set State = 1 Case 1 i.e. If State=1 Set Red light on Set Yellow light on Set Green light off Set State = 2 Case 2 i.e. If State=2 Set Red light off Set Yellow light off Set Green light on Set State = 3 Now still systematic and robust, but also explicitly indicates exclusive conditions, and saves VBA some work. … End Select Add code for State=3 (Ex) TrafficLights0: ChangeStateSelect ‘State’ State = State + 1 If State > 4 Then State = 1 Select Case State Case 1 Set Red light on Set Yellow light off Set Green light off Case 2 Traffic Lights pseudo-code: the most elegant solution Set Red light on Set Yellow light on Set Green light off Case 3 Set Red light off Set Yellow light off Automatic increment of ‘State’, with a condition that makes it cycle round values 1-4. Set Green light on … End Select Case 4 Set Red light off Set Yellow light on Set Green light off TrafficLights0: ChangeStateAutoSelect 10 Tips for working with VBA You cannot record conditions, (nor loop instructions)! You cannot ‘Undo’ macro actions! Save your model first! X Warwick Business School Interactive Guessing Game The computer thinks of a (random) number between 1 and 10 We guess what the number is The computer tells us whether our guess is correct, too low or too high We win if we guess the number using three or fewer guesses! Warwick Business School Guess0 11 Conditional statements in VBA Conditional instruction patterns - summary Warwick Business School Conditional statements in VBA Special case: a single conditional instruction No ‘Else’ nor ‘Endif’ is needed: If Condition Then Instruction (on one line only) The ‘IF’ instruction is terminated after this line Warwick Business School 12 Conditional statements in VBA Compound conditions If (Condition1 and Condition2) Then Instruction Else Instruction End If Warwick Business School Conditional statements in VBA Nested conditions If Condition C1 Then If Condition C2 Then Instruction “C1 C2” Else Instruction “C1 Not C2” End If Else Instruction “Not C1” End If e.g. TrafficLights1, Guessing game (soon); Count2 (Ex) Warwick Business School 13 Conditional statements in VBA Another variation for exclusive conditions (ensure Instruction is on a new line) If Condition1 Then Instruction1 ElseIf Condition2 Then Instruction2 ElseIf Condition3 Then Instruction3 ElseIf Condition4 Then Instruction4 Else Error? End If Warwick Business School Conditional statements in VBA General syntax for exclusive conditions that depend on the value of a single variable Select Case VariableName Case 1,5 Instruction 1 Case 2 to 4 Instruction 2 Case 6 to 9 Instruction 3 Case 10 Instruction 4 Case Else Error? End Select Warwick Business School 14 Arts_Centre_2.xlsm (a) Sub Increment() ' N.B. Ensure Tickets_Input, Optimal_Revenue, Optimal_tickets are initialised to 0 before starting Range("Tickets_Input").Value = Range("Tickets_Input").Value + 1 If Range("Revenue_Output").Value > Range("Optimal_Revenue").Value Then 'new maximum revenue so replace the old one Range("Optimal_Revenue").Value = Range("Revenue_Output").Value Range("Optimal_Tickets").Value = Range("Tickets_Input").Value End If End Sub ' Sort_Revenue Macro ' Sub Sort_Revenue() ' Sort Application.Goto Reference:="What_If" Selection.Sort Key1:=Range("D23"), Order1:=xlAscending, Header:=xlNo, _ OrderCustom:=1, MatchCase:=False, Orientation:=xlTopToBottom, _ DataOption1:=xlSortNormal End Sub TrafficLights0 macros ' Traffic Lights macros: NoStateVariable ' ' Change Macro, without using the 'State' variable ' If you use 'ElseIf', be careful to start the actions on a new line after 'Then' ' See how much more code is needed to write the conditions to identify each 'state'. ' Sub ChangeNoState() If Range("Red").Interior.Color = vbWhite And Range("Yellow").Interior.Color = vbWhite And Range("Green").Interior.Color = vbWhite Then Range("Red").Interior.Color = vbRed Range("Yellow").Interior.Color = vbWhite Range("Green").Interior.Color = vbWhite ElseIf Range("Red").Interior.Color = vbRed And Range("Yellow").Interior.Color = vbWhite And Range("Green").Interior.Color = vbWhite Then Range("Red").Interior.Color = vbRed Range("Yellow").Interior.Color = vbYellow Range("Green").Interior.Color = vbWhite ElseIf Range("Red").Interior.Color = vbRed And Range("Yellow").Interior.Color = vbYellow And Range("Green").Interior.Color = vbWhite Then Range("Red").Interior.Color = vbWhite Range("Yellow").Interior.Color = vbWhite Range("Green").Interior.Color = vbGreen ElseIf Range("Red").Interior.Color = vbWhite And Range("Yellow").Interior.Color = vbWhite And Range("Green").Interior.Color = vbGreen Then Range("Red").Interior.Color = vbWhite Range("Yellow").Interior.Color = vbYellow Range("Green").Interior.Color = vbWhite ElseIf Range("Red").Interior.Color = vbWhite And Range("Yellow").Interior.Color = vbYellow And Range("Green").Interior.Color = vbWhite Then Range("Red").Interior.Color = vbRed Range("Yellow").Interior.Color = vbWhite Range("Green").Interior.Color = vbWhite End If End Sub Sub ChangeNoStateShortCut() If Range("Red").Interior.Color = vbRed And Range("Yellow").Interior.Color = vbWhite Then Range("Yellow").Interior.Color = vbYellow ElseIf Range("Red").Interior.Color = vbRed And Range("Yellow").Interior.Color = vbYellow Then Range("Red").Interior.Color = vbWhite Range("Yellow").Interior.Color = vbWhite Range("Green").Interior.Color = vbGreen ElseIf Range("Green").Interior.Color = vbGreen Then Range("Yellow").Interior.Color = vbYellow Range("Green").Interior.Color = vbWhite ElseIf Range("Yellow").Interior.Color = vbYellow Then Range("Red").Interior.Color = vbRed Range("Yellow").Interior.Color = vbWhite End If End Sub Sub ChangeNoStateNested() If Range("Red").Interior.Color = vbRed Then If Range("Yellow").Interior.Color = vbWhite Then Range("Yellow").Interior.Color = vbYellow Else Range("Red").Interior.Color = vbWhite Range("Yellow").Interior.Color = vbWhite Range("Green").Interior.Color = vbGreen End If Else If Range("Green").Interior.Color = vbGreen Then Range("Yellow").Interior.Color = vbYellow Range("Green").Interior.Color = vbWhite Else Range("Red").Interior.Color = vbRed Range("Yellow").Interior.Color = vbWhite End If End If End Sub ' Traffic Lights macros: BestSolution ' ' Change Macro ' Sub ChangeStateAutoSelect() Range("State").Value = Range("State").Value + 1 If Range("State").Value > 4 Then Range("State").Value = 1 Select Case Range("State").Value Case 1 Range("Red").Interior.Color = vbRed Range("Yellow").Interior.Color = vbWhite Range("Green").Interior.Color = vbWhite Case 2 Range("Red").Interior.Color = vbRed Range("Yellow").Interior.Color = vbYellow Range("Green").Interior.Color = vbWhite Case 3 Range("Red").Interior.Color = vbWhite Range("Yellow").Interior.Color = vbWhite Range("Green").Interior.Color = vbGreen Case 4 Range("Red").Interior.Color = vbWhite Range("Yellow").Interior.Color = vbYellow Range("Green").Interior.Color = vbWhite End Select End Sub ' Set Lights to 'Off' (state 0) ' Sub SetOff() Range("State").Value = 0 Range("Red").Interior.Color = vbWhite Range("Yellow").Interior.Color = vbWhite Range("Green").Interior.Color = vbWhite End Sub ' Sub Auto_Open() 'automatically runs when the spreadsheet is opened SetOff End Sub ' ' Traffic Lights macros 'The rest of these examples show alternative equivalent approaches; some are a little neater 'To use one of these, attach its name to the Change button on the worksheet (Right click|Assign Macro) 'N.B. Each of these needs finishing off to code the final situation (state 4, yellow light only) (Ex) Sub ChangeStateNested() ' Change Macro using nested IFs ' This works fine, but does not demonstrate that each state is mutually exclusive ' If Range("State").Value = 4 Then Range("Red").Interior.Color = vbRed Range("Yellow").Interior.Color = vbWhite Range("Green").Interior.Color = vbWhite Range("State").Value = 1 Else If Range("State").Value = 1 Then Range("Red").Interior.Color = vbRed Range("Yellow").Interior.Color = vbYellow Range("Green").Interior.Color = vbWhite Range("State").Value = 2 Else If Range("State").Value = 2 Then Range("Red").Interior.Color = vbWhite Range("Yellow").Interior.Color = vbWhite Range("Green").Interior.Color = vbGreen Range("State").Value = 3 End If End If End If End Sub Sub ChangeStateSelect() ' Change Macro using Select ' This works fine, and clearly demonstrates that each state is mutually exclusive Select Case Range("State") Case 4 Range("Red").Interior.Color = vbRed Range("Yellow").Interior.Color = vbWhite Range("Green").Interior.Color = vbWhite Range("State").Value = 1 Case 1 Range("Red").Interior.Color = vbRed Range("Yellow").Interior.Color = vbYellow Range("Green").Interior.Color = vbWhite Range("State").Value = 2 Case 2 Range("Red").Interior.Color = vbWhite Range("Yellow").Interior.Color = vbWhite Range("Green").Interior.Color = vbGreen Range("State").Value = 3 End Select End Sub Sub ChangeStateAutoNested() ' This version uses independent IF statements. ' Only one If statement will be true, so why make VBA check the others? Range("State").Value = Range("State").Value + 1 If Range("State").Value > 4 Then Range("State").Value = 1 If Range("State").Value = 1 Then Range("Red").Interior.Color = vbRed Range("Yellow").Interior.Color = vbWhite Range("Green").Interior.Color = vbWhite End If If Range("State").Value = 2 Then Range("Red").Interior.Color = vbRed Range("Yellow").Interior.Color = vbYellow Range("Green").Interior.Color = vbWhite End If If Range("State").Value = 3 Then Range("Red").Interior.Color = vbWhite Range("Yellow").Interior.Color = vbWhite Range("Green").Interior.Color = vbGreen End If End Sub Sub ChangeStateAutoElseIf() ' Change Macro using Elseif for exclusive conditions ' If you use Elseif, be careful to start the actions on a new line after 'Then' ' Range("State").Value = Range("State").Value + 1 If Range("State").Value > 4 Then Range("State").Value = 1 If Range("State").Value = 1 Then Range("Red").Interior.Color = vbRed Range("Yellow").Interior.Color = vbWhite Range("Green").Interior.Color = vbWhite ElseIf Range("State").Value = 2 Then Range("Red").Interior.Color = vbRed Range("Yellow").Interior.Color = vbYellow Range("Green").Interior.Color = vbWhite ElseIf Range("State").Value = 3 Then Range("Red").Interior.Color = vbWhite Range("Yellow").Interior.Color = vbWhite Range("Green").Interior.Color = vbGreen End If End Sub Guess0 ' Can you cheat on your score if you win in two guesses? ' Guess0 Module 1 ' ' Button1_Click Macro ' Generate ' Sub Button1_Click() Range("Number") = Int(Rnd() * 10 + 1) 'Generate number and put in Answer sheet '(uses range name) Range("Number_of_Guesses") = 0 'Initialise number of guesses for new game End Sub ' ' Button2_Click Macro ' Guess ' Sub Button2_Click() Range("Number_of_Guesses").Value = Range("Number_of_Guesses").Value + 1 ' Increment the number of guesses ' Test the guess If Range("Guess").Value = Range("Number") And Range("Number_of_Guesses").Value <= 3 Then ' Guess is correct and <=3 guesses MsgBox ("You won; did you cheat?!") Else If Range("Number_of_Guesses").Value < 3 Then ' Guess is wrong but guesses remaining If Range("Guess").Value < Range("Number") Then MsgBox ("Too low, try again") Else MsgBox ("Too high, try again") End If Else ' Guess is wrong - too late! MsgBox ("Ha Ha, I won : the number was " & Range("Number")) End If End If End Sub Mod, Quotient: Worksheet Function vs VBA This table should help to understand the roles of Mod, Quotient etc. They are not the most common functions, but happen to have been useful for my examples. It is unfortunate that these are amongst the few worksheet functions that do not work in VBA! Meaning The whole number of multiples of B in A The remainder of A when divided by B Formula Quotient (A, B) VBA A\B Example 5\3=1 Mod (A, B) A Mod B 5 Mod 3 = 2 Excel / VBA Exercises 2018-2019 Lesson 9 1. *In the macros of TrafficLights0 example, note the alternative instructions for changing the sequence in the ‘AlternativeStructures’ module. These all use the ‘State’ variable, that offers a simple representation of the current signal from the lights. The different macros simply use alternative conditional controls constructs; which is the best? For practice, finish each of these to manage the final change of lights from green to yellow. 2. *Add buttons to reset the lights to an initial state (state 0) of ‘all lights off’, and if necessary to start the sequence of lights. Compare the code in TrafficlLights0. 3. Code instructions that implement in VBA the method introduced in the lecture but without using the ‘State’ variable i.e. (in VBA) ‘If Red light is on and Yellow light is on Then Set Red light off …’. Compare with TrafficLights0: ChangeNoState () in Module ‘NoStateVariable’. Note how ‘wordy’ this is, and how the logic is less clear as a result. 4. In the pseudo-code examples for Traffic Lights in the lecture slides, check which versions are exactly equivalent and work as required, and make sure you understand why one version (ChangeError) does not work.34 5. *Note that you cannot record a macro to ‘Count’. Check out why, by trying.35 6. Enhance the counting macro to help support you play the following game with a young niece or a nephew at Christmas or New Year celebrations (with embarrassment for exposing my sad childhood!): If the number shown is divisible by 5, the person must shout ‘Fizz’ If the number shown is divisible by 7, the person must shout ‘Buzz’ If the number shown is divisible by both 5 and 7, the person must shout ‘Fizz-Buzz’ The computer should show whether the person got it right, a) using a cell formula and a message in a cell36 and b) using a macro and showing a message box! 34 This macro is coded in Change_Error Module Error() of TrafficLights0 if you want to check it out (run the macro directly from VBA or right click the Button and attach it to that macro). If you wish, Trace it using the ‘Step into’ (F8) button in the Debug toolbar in VBA to see where it goes wrong. 35 You can record putting the value 1 in a cell, but if you run it again, of course it just puts the number 1 in the cell again (not 2, 3, …). There is not a built-in Excel command to add 1 to a cell. 36 For the formula you will need a nested IF statement. You can make use of the formula =MOD(Number,5) which returns the remainder after dividing by 5 (try it) (QUOTIENT(Number,5) gives the number of times 5 divides into Number). Use separate cells to indicate ‘Fizz’ and ‘Buzz’ at first, then in one cell. In VBA you can use X Mod Y and X \ Y as equivalent to the worksheet functions. Compare Count2. (* : most important; † : difficult) 20 Excel / VBA Exercises 2018-2019 7. * My eldest son Ben used to like to practising football shooting in the garden (now he is 30 and prefers golf, rugby and paediatric surgery!). I kept a score of his ‘hit rate’ as he went along e.g. 1 goal out of 4 shots, or 25%. That was OK, but 2 out of 7 was harder to calculate as a percentage, so I decided we needed the computer to help - not to simulate, simply to record whether he scored, and to calculate the ‘hit rate’, after each individual shot. Can you write macros that use two buttons called ‘Scored’ and ‘Saved’ to control two cells that count the number of shots and goals so far37, and use an ordinary formula in a cell to display the ‘hit rate’ for us as a percentage? What happens to the hit-rate if there are zero shots? Use an IF function to display something more appropriate in this case. Finally, add another button which will reset the score. Optional: add a stochastic element that uses just one ’Shoot’ button and randomly determines whether a goal is scored, and updates the hit-rate automatically.38 8. Optional. Consider how you might like to make the guessing game more interesting and user-friendly. 9. Design one or more ways to ensure that the user enters a valid guess in the guessing game (in particular, they might enter text by mistake, or enter a guess outside the range 1-10): First, use a nested IF formula39 in a nearby cell in Guess1, that displays an appropriate message depending on the contents of ‘Guess’. Better, add a separate ‘Check’ button that uses VBA to check whether the user has entered a valid number for a guess, and alerts them if not.40 Even better, incorporate this code to the Guess button code so that Guess will check that the data is valid before it goes ahead to evaluate the guess. Even better, use Data | Data Validation method to ensure the input is valid (set up a list of numbers 110; compare with the Orders example). a. b. c. 10. †Optional: puzzle. In the Count example, the counting or accumulation is done by triggering a macro. Is it possible to have a cell which accumulates its value from a given ‘input cell’, each time the input cell changes value, without using a macro?41 You could simply have one button to increment the value of each cell (shots, goals), but it’s more meaningful for the user if they can press one button if they scored, and another if they missed. 38 In VBA, use ‘IF Rnd < 0.5 …’ Notice the different function name, not Rand(). Most VBA function names are the same, but not this one! Note that you will get the same stream of random numbers every time you run the program, unless you insert the statement RANDOMIZE (spelled with a ‘z’) to be executed ONCE, early in the program. This command ‘shakes up’ the box of random numbers (or more technically, sets a truly random ‘seed’ for generating the numbers). Cf Goal. 37 39 Consider using the ISNUMBER(cell) and ISBLANK formula and combine two conditions by using AND(Condition1,Condition2), which is true only if both condition 1 and condition 2 are true, or OR(Condition1,Condition2), which is true if either condition 1 or condition 2 are true, inside an IF statement. Check out Guess0a 40 You must first check that the entry is indeed a number, and not text. Use the VBA function IsNumeric(range(“Guess”).value) . Nested inside this condition, you can check whether it is between 1 and 10. Unfortunately there is no ‘IsInteger(x)’ function in VBA, but you can check whether x = Int(x), once you know that x is a number! There are few short-cuts to such detailed checks. 41 Hint: you may use the F9 key to recalculate, together with iteration and circular references (* : most important; † : difficult) 21