Upload
melina-williams
View
228
Download
2
Tags:
Embed Size (px)
Citation preview
Flow of ControlFlow of Control
Chapter 5Chapter 5
Flow of ControlFlow of Control
What order computer uses to get answersWhat order computer uses to get answers– sub-goal orderingsub-goal ordering– clause orderingclause ordering
Prolog flow-of-controlProlog flow-of-control– sequence + backtracking standardsequence + backtracking standard– ways to modify thatways to modify that
TodayToday– disjunction, cut, (& negation)disjunction, cut, (& negation)
DisjunctionDisjunction
Prolog uses comma for ANDProlog uses comma for AND It uses semi-colon for ORIt uses semi-colon for OR
person(X) :-person(X) :-parent(X, _)parent(X, _);;parent(_, X).parent(_, X).
No comma here – it’s not an AND
Semi-colon usually alone on its own line
X is a person if they’re someone’s parent OR someone’s child
Control Using DisjunctionControl Using Disjunction
Prolog gives all answers for first part before Prolog gives all answers for first part before starting answers for second partstarting answers for second part– then gives all answers for second partthen gives all answers for second part
person(X) :-person(X) :-parent(X, _)parent(X, _);;parent(_, X).parent(_, X).
People will be returned in this order:
pam, tom, tom [sic], bob, bob, pat
bob, bob, liz, ann, pat, jim
Tom and Bob are parents of twoBob also has two parents
AND and ORAND and OR
Semi-colon splits rule into two partsSemi-colon splits rule into two parts– commas group together on either sidecommas group together on either side
man(X) :-man(X) :-parent(X, _),parent(X, _),male(X)male(X);;parent(_, X),parent(_, X),male(X).male(X).
First find all the parentsthat are male
Then find all the childrenthat are male
AND and OR IIAND and OR II
Use parentheses to group differentlyUse parentheses to group differentlyman(X) :-man(X) :-
((parent(X, _)parent(X, _);;parent(_, X)parent(_, X)
),),male(X).male(X).
First find the parents
Then find the children
But make sure they’re male in either case
OR versus ClausesOR versus Clauses
Putting a semi-colon in is marginally more Putting a semi-colon in is marginally more efficient than using two clausesefficient than using two clauses– but it means the same thingbut it means the same thing
man(X) :-man(X) :-parent(X, _),parent(X, _),male(X)male(X);;parent(_, X),parent(_, X),male(X).male(X).
man(X) :-man(X) :-parent(X, _),parent(X, _),male(X).male(X).
man(X) :-man(X) :-parent(_, X),parent(_, X),male(X).male(X).
ExerciseExercise
Rewrite the following predicate without the Rewrite the following predicate without the semi-colon notation:semi-colon notation:translate(Number, Word) :-translate(Number, Word) :-
Number = 1, Word = oneNumber = 1, Word = one;;Number = 2, Word = twoNumber = 2, Word = two;;Number = 3, Word = three.Number = 3, Word = three.
Preventing BacktrackingPreventing Backtracking
Backtracking is automaticBacktracking is automatic Sometimes we don’t want itSometimes we don’t want it
– first answer is only correct onefirst answer is only correct one– know there aren’t any more answersknow there aren’t any more answers
Block backtracking by using a “cut”Block backtracking by using a “cut”– predicate is !/0predicate is !/0
Example ProblemExample Problem
Grocery shoppingGrocery shopping– have a listhave a list– store has some items (not all)store has some items (not all)– visit store, buy what you can…visit store, buy what you can…– ……calculate total price…calculate total price…– ……and what’s left to buy (at another store)and what’s left to buy (at another store)?- ?- costToBuy([ham, eggs, nails], Cost, Rest).costToBuy([ham, eggs, nails], Cost, Rest).Cost = 4, Rest = [nails]Cost = 4, Rest = [nails]
This store doesn’t sell nails
Grocery ListsGrocery Lists
Write the costToBuy/3 predicateWrite the costToBuy/3 predicate If nothing on listIf nothing on list
– price is 0price is 0– nothing left to buynothing left to buy
If there is an item HIf there is an item H– if it has a price, you can buy itif it has a price, you can buy it– otherwise, you can’t buy itotherwise, you can’t buy it
Base case
Recursive case #1
Recursive case #2
Recursive CasesRecursive Cases
You can buy itYou can buy it– get its priceget its price– get the price of the rest of the stuffget the price of the rest of the stuff– add them together to get the totaladd them together to get the total
You can’t buy itYou can’t buy it– it is (or should be) on the list of can’t-buysit is (or should be) on the list of can’t-buys– get the price of everything elseget the price of everything else
Does It Have a Price?Does It Have a Price?
If it has a price, Prolog will find itIf it has a price, Prolog will find it So ask for the priceSo ask for the price
– if there is a price, you’ll get it & carry onif there is a price, you’ll get it & carry on– if there is no price, Prolog if there is no price, Prolog fails fails (= says no)(= says no)
And what if it fails?And what if it fails?– Prolog will back up and try something elseProlog will back up and try something else– the the something else something else should be should be add it to the list of add it to the list of
stuff you can’t getstuff you can’t get
costToBuy/3costToBuy/3
% costToBuy(+L, ?P, ?R)% costToBuy(+L, ?P, ?R)
costToBuy([], 0, []).costToBuy([], 0, []). % nothing on list% nothing on list
costToBuy([H|T], P, R) :-costToBuy([H|T], P, R) :- % can buy H% can buy H
price(H, PH),price(H, PH), % (H has a price)% (H has a price)
costToBuy(T, PT, R),costToBuy(T, PT, R),
P is PH + PT.P is PH + PT.
costToBuy([H|T], P, [H|R]) :-costToBuy([H|T], P, [H|R]) :- % couldn’t buy H% couldn’t buy H
costToBuy(T, P, R).costToBuy(T, P, R). % (H had no price)% (H had no price)
At the StoreAt the Store
price(milk, 5).price(milk, 5).
price(eggs, 1).price(eggs, 1).
price(ham, 3).price(ham, 3).
price(bread, 1).price(bread, 1).
price(tomato, 2).price(tomato, 2).
price(fish, 3).price(fish, 3).
costToBuy([], 0, []).costToBuy([], 0, []).
costToBuy([H|T], P, R) :-costToBuy([H|T], P, R) :-
price(H, PH),price(H, PH),
costToBuy(T, PT, R),costToBuy(T, PT, R),
P is PH + PT.P is PH + PT.
costToBuy([H|T], P, [H|R]) :-costToBuy([H|T], P, [H|R]) :-
costToBuy(T, P, R).costToBuy(T, P, R).
Buying Stuff ExampleBuying Stuff Example
?- ?- costToBuy([ham, eggs, nails], Cost, Rest).costToBuy([ham, eggs, nails], Cost, Rest).Cost = 4Cost = 4Rest = [nails]Rest = [nails]YesYes
?- ?- costToBuy([milk, fish, bread], P, R).costToBuy([milk, fish, bread], P, R).P = 9P = 9R = []R = []YesYes
Further SolutionsFurther Solutions
?- ?- costToBuy([ham, eggs, nails], Price, Rest).costToBuy([ham, eggs, nails], Price, Rest).Price = 4Price = 4Rest = [nails] Rest = [nails] ;;
Price = 3Price = 3Rest = [eggs, nails] Rest = [eggs, nails] ;;
Price = 1Price = 1Rest = [ham, nails] Rest = [ham, nails] ;;
Price = 0Price = 0Rest = [ham, eggs, nails]Rest = [ham, eggs, nails]
costToBuy and BacktrackingcostToBuy and Backtracking
The predicate gives the correct answer firstThe predicate gives the correct answer first– adds up prices of things with pricesadds up prices of things with prices– adds un-priced objects to the can’t-buy listadds un-priced objects to the can’t-buy list
Backtracking gives more answersBacktracking gives more answers– objects objects withwith prices get added to can’t-buy list prices get added to can’t-buy list
Good Thing / Bad ThingGood Thing / Bad Thing– drop items you can’t afforddrop items you can’t afford– not not preciselyprecisely what we said it should be what we said it should be
Avoiding BacktrackingAvoiding Backtracking
We’d like to We’d like to commitcommit to the first rule (second to the first rule (second clause) once we find H has a priceclause) once we find H has a price– don’t want it to go back and try more rulesdon’t want it to go back and try more rules
We can tell Prolog to commit to a ruleWe can tell Prolog to commit to a rule– called “cutting” a predicatecalled “cutting” a predicate– predicate is called “cut”predicate is called “cut”– predicate is !/0predicate is !/0
costToBuy/3 With CutcostToBuy/3 With Cut
costToBuy([], 0, []).costToBuy([], 0, []). % nothing on list% nothing on listcostToBuy([H|T], P, R) :-costToBuy([H|T], P, R) :- % can buy H% can buy H
price(H, PH),price(H, PH), % (H has a price)% (H has a price)!,!, % commit to this % commit to this rulerulecostToBuy(T, PT, R),costToBuy(T, PT, R),P is PH + PT.P is PH + PT.
costToBuy([H|T], P, [H|R]) :-costToBuy([H|T], P, [H|R]) :- % couldn’t buy H% couldn’t buy HcostToBuy(T, P, R).costToBuy(T, P, R). % (H had no price)% (H had no price)
Going Shopping AgainGoing Shopping Again
?- ?- costToBuy([ham, eggs, nails], P, R).costToBuy([ham, eggs, nails], P, R).
P = 4P = 4R = [nails] R = [nails] ;;
NoNo Now matches what we saidNow matches what we said
– total price of things we can buytotal price of things we can buy– list of things not available to buylist of things not available to buy– no “extra” solutionsno “extra” solutions
What Cut DoesWhat Cut Does
Cut commits you to the current ruleCut commits you to the current rule– can’t backtrack to other rules can’t backtrack to other rules for this predicate, for this predicate,
on this callon this call– if this predicate is going to succeed, it’s going if this predicate is going to succeed, it’s going
to succeed in this ruleto succeed in this rule– if this rule fails, the predicate failsif this rule fails, the predicate fails– commitment occurs when you hit the !commitment occurs when you hit the !– can still use other rules if didn’t get to the !can still use other rules if didn’t get to the !
Loaves and FishesLoaves and Fishes
Looking at eggsLooking at eggs
price(eggs, P)price(eggs, P)
%% makes makes P = 1P = 1
!,!,
%% commit herecommit here
costToBuy(…),costToBuy(…),
& & ceteracetera
Looking at nailsLooking at nails
price(nails, P)price(nails, P)
%% failsfails
Look for another ruleLook for another rule
nails gets added to Rnails gets added to R
Even if you backtrack, you won’t get to the last rule for eggs
Last rule is available for nailsDidn’t get to the !
Application of CutApplication of Cut
Cut applies to Cut applies to this callthis call to to this predicatethis predicate– no more rules will be considered right nowno more rules will be considered right now– this question is gonna be answered right here!this question is gonna be answered right here!
All rules available for All rules available for other other callscalls– recursive calls start with all options openrecursive calls start with all options open– new calls dittonew calls ditto
New CallsNew Calls
?- ?- costToBuy([bread, nails], P, R).costToBuy([bread, nails], P, R).
P = 1, R = [nails]P = 1, R = [nails]
?- ?- costToBuy([bread], P1, R1),costToBuy([bread], P1, R1), costToBuy([nails], P2, R2).costToBuy([nails], P2, R2).
P1 = 1, R1 = [], P2 = 0, R2 = [nails]P1 = 1, R1 = [], P2 = 0, R2 = [nails]
?- ?- member(X, [bread, nails]), costToBuy([X], P, R).member(X, [bread, nails]), costToBuy([X], P, R).
X = bread, P = 1, R = [] X = bread, P = 1, R = [] ;;X = nails, P = 0, R = [nails]X = nails, P = 0, R = [nails]
recursion
new question
backtracking restarts question
Cut Blocks BacktrackingCut Blocks Backtracking
Backtracking goes normally…Backtracking goes normally… ……unless we backtrack to a !unless we backtrack to a !
– that means “done for this predicate”that means “done for this predicate”sillyPrices(X, Y, Z) :-sillyPrices(X, Y, Z) :-
X = yes,X = yes,!,!,member(Y, [nails, ham, eggs]),member(Y, [nails, ham, eggs]),price(Y, Z).price(Y, Z).
sillyPrices(X, Y, Z) :- price(Y, Z).sillyPrices(X, Y, Z) :- price(Y, Z).
backtracking OK here
Limited BacktrackingLimited Backtracking
?- ?- sillyPrices(yes, What, HowMuch).sillyPrices(yes, What, HowMuch).
What = hamWhat = hamHowMuch = 3 HowMuch = 3 ;;
What = eggsWhat = eggsHowMuch = 1 HowMuch = 1 ;;
NoNo
?- ?- sillyPrices(no, bread, HowMuch).sillyPrices(no, bread, HowMuch).
HowMuch = 1HowMuch = 1
Red GreenRed Green
““Red” cuts chop off answersRed” cuts chop off answers– as above – some answers are not desiredas above – some answers are not desired– without cut, Prolog would generate themwithout cut, Prolog would generate them
““Green” cuts prevent useless backtrackingGreen” cuts prevent useless backtracking– wouldn’t have got any answers anywaywouldn’t have got any answers anyway– but Prolog would have looked – takes timebut Prolog would have looked – takes time– ““efficiency” cutefficiency” cut
Green CutGreen Cut
% max(+X, +Y, –Max)% max(+X, +Y, –Max)max(X, Y, X) :-max(X, Y, X) :-
X >= Y,X >= Y,!.!. % green cut% green cut
max(X, Y, Y) :-max(X, Y, Y) :-X < Y.X < Y.
max(5, 3, X) sets X to 5, no more answersmax(5, 3, X) sets X to 5, no more answers– but without the cut Prolog would try the second but without the cut Prolog would try the second
clause as well – even tho’ it can’t workclause as well – even tho’ it can’t work
ExerciseExercise
Write a predicate admission/2 to give the Write a predicate admission/2 to give the admission price to a club functionadmission price to a club function– club members pay $2club members pay $2– non-members pay $5non-members pay $5
Use club_member/1 to tell if someone is a Use club_member/1 to tell if someone is a member or notmember or not
Name known, price to be returnedName known, price to be returned
SolutionSolution
% admission(+Person, –Price)% admission(+Person, –Price)
admission(Member, 2) :-admission(Member, 2) :-
club_member(Member),club_member(Member),
!.!.
admission(NonMember, 5).admission(NonMember, 5).
The Price of AdmissionThe Price of Admission
admission(M, 2) :- club_member(M), !.admission(M, 2) :- club_member(M), !.admission(M, 5).admission(M, 5).club_member(bob).club_member(bob).
?- ?- admission(bob, P).admission(bob, P).P = 2 ;P = 2 ;NoNo
?- ?- admission(bob, 5).admission(bob, 5).YesYes
“The only price of admission for bob is 2”
“Yes, the price of admission for bob is 5”
Huh?
What Happened?What Happened?
admission(M, 2) :- club_member(M), !.admission(M, 2) :- club_member(M), !.
?- ?- admission(bob, 5).admission(bob, 5).2 does not unify with 52 does not unify with 5
this rule is not consideredthis rule is not consideredthe ! is never reachedthe ! is never reached
go on to the next rulego on to the next rule
admission(M, 5).admission(M, 5).
M = bobM = bob
Making ExceptionsMaking Exceptions
Fix by making rule head more generalFix by making rule head more generaladmission(M, P) :- club_member(M), !, P = 2.admission(M, P) :- club_member(M), !, P = 2.admission(M, 5).admission(M, 5).club_member(bob).club_member(bob).?- ?- admission(bob, P).admission(bob, P).P = 2 P = 2 ;;NoNo?- ?- admission(bob, 5).admission(bob, 5).NoNo
“The only price of admission for bob is 2”
“The price of admission for bob is not 5”
Now It WorksNow It Works
admission(M, P) :- club_member(M), !, P = 2.admission(M, P) :- club_member(M), !, P = 2.
?- ?- admission(bob, 5).admission(bob, 5).M = bob, P = 5M = bob, P = 5
club_member(bob) succeedsclub_member(bob) succeeds! commits us to this rule! commits us to this rule5 does not unify with 25 does not unify with 2
! blocks backtracking! blocks backtrackingNoNo
ExceptionsExceptions
Second clause states general ruleSecond clause states general rule– admission is $5admission is $5
First rule captures exceptionFirst rule captures exception– for members, only $2for members, only $2
Can combine two rules into oneCan combine two rules into oneadmission(M, P) :- club_member(M), !, P = 2admission(M, P) :- club_member(M), !, P = 2
;;P = 5.P = 5.
Capturing ExceptionsCapturing Exceptions
Admission to a movie:Admission to a movie:– $2.00 for kids (under 12)$2.00 for kids (under 12)– $5.00 for youth (12 – 17)$5.00 for youth (12 – 17)– $8.00 for adults (18 – 64)$8.00 for adults (18 – 64)– $5.00 for seniors (65+)$5.00 for seniors (65+)
Code it up as it standsCode it up as it stands– one rule using ! and ;one rule using ! and ;
Movie AdmissionsMovie Admissions
admission(Age, Amount) :-admission(Age, Amount) :-Age < 12, !, Amount = 2Age < 12, !, Amount = 2;;Age < 18, !, Amount = 5Age < 18, !, Amount = 5;;Age < 65, !, Amount = 8Age < 65, !, Amount = 8;;Amount = 5.Amount = 5.
All cases combined All cases combined in one rulein one rule
Has four Has four sub-rulessub-rules ! blocks ! blocks
backtrackingbacktracking Only the ! you get to, Only the ! you get to,
tho’tho’
Tracing ExecutionTracing Execution
Suppose start with age 15Suppose start with age 15– rule head matchesrule head matches– Age < 12 fails – go to next sub-rule (;)Age < 12 fails – go to next sub-rule (;)– Age < 18 succeeds – ! commits usAge < 18 succeeds – ! commits us– Amount = 5 succeedsAmount = 5 succeeds
Ask for another answerAsk for another answer– Amount = 5 has no more solutionsAmount = 5 has no more solutions– can’t go back past !, so no more answerscan’t go back past !, so no more answers
Prolog “Else if” StatementProlog “Else if” Statement
That form (multiple cuts and semi-colons) That form (multiple cuts and semi-colons) functions like a big if-then-elsif constructfunctions like a big if-then-elsif construct
if (age < 12) amount if (age < 12) amount 2; 2;else if (age < 18) amount else if (age < 18) amount 5; 5;else if (age < 65) amount else if (age < 65) amount 8; 8;else amount else amount 5; 5; Common way to write for multiple casesCommon way to write for multiple cases
Elsif and RelationshipsElsif and Relationships
The style above only works if you are The style above only works if you are calculatingcalculating a price based on an age a price based on an age– Age is a known quantityAge is a known quantity– Price is an unknown (or dubious) quantityPrice is an unknown (or dubious) quantity– IOW: admission(+Age, ?Price)IOW: admission(+Age, ?Price)
If you want to generate ages from prices, If you want to generate ages from prices, you need to avoid the !you need to avoid the !
Admission Relationship Admission Relationship
admission(Age, 2) :- between(0, 11, Age).admission(Age, 2) :- between(0, 11, Age).admission(Age, 5) :- between(12, 17, Age).admission(Age, 5) :- between(12, 17, Age).admission(Age, 8) :- between(18, 64, Age).admission(Age, 8) :- between(18, 64, Age).admission(Age, 5) :- between(65, 120, Age).admission(Age, 5) :- between(65, 120, Age). between/3 succeeds if 3between/3 succeeds if 3rdrd argument between argument between
11stst and 2 and 2ndnd (inclusive) (inclusive)– generates 3generates 3rdrd argument if necessary argument if necessary
Note that over 120 not admitted anymore….Note that over 120 not admitted anymore….
Full ExceptionsFull Exceptions
Above fine if every category has an answerAbove fine if every category has an answer But sometimes a category may be missingBut sometimes a category may be missing
– Mary likes all animals except snakesMary likes all animals except snakes
likes(mary, X) :-likes(mary, X) :-snake(X), !, failsnake(X), !, fail;;animal(X).animal(X).
snake(sammy).snake(sammy).
fail/0 always fails
?- likes(mary, sammy).No
Fuller ExceptionsFuller Exceptions
Harry likes everything except snakesHarry likes everything except snakeslikes(harry, X) :-likes(harry, X) :-
snake(X), !, failsnake(X), !, fail;;true.true.
snake(sammy).snake(sammy). Here there are no further conditions on XHere there are no further conditions on X
– but still need to say rule succeedsbut still need to say rule succeeds
true/0 always succeeds
?- likes(harry, opera).Yes
ExerciseExercise
Write a clause of likes/2 for MicheleWrite a clause of likes/2 for Michele– Michele likes animalsMichele likes animals– except spiders and snakesexcept spiders and snakes– except she likes Sammy (who is a snake)except she likes Sammy (who is a snake)
likes(michele, X) :-likes(michele, X) :-
……
Stating Negative ConditionsStating Negative Conditions
Sometimes you want to say that some Sometimes you want to say that some condition does notcondition does not holdhold
Prolog allows thisProlog allows this– not/1not/1 this is a predicatethis is a predicate– \+/1\+/1 this is a prefix operatorthis is a prefix operator
\+ is the preferred way to go\+ is the preferred way to go not is the traditional way to gonot is the traditional way to go
Non-MembersNon-Members
a is not a member of [q, w, e, r, t, y]a is not a member of [q, w, e, r, t, y]?- ?- \+ member(a, [q,w,e,r,t,y]).\+ member(a, [q,w,e,r,t,y]).YesYes Admission prices revisedAdmission prices revisedadmission(Member, 2) :-admission(Member, 2) :-
club_member(Member).club_member(Member).admission(NonMember, 5) :-admission(NonMember, 5) :-
\+ club_member(NonMember).\+ club_member(NonMember).
How Not WorksHow Not Works
““Negation by failure”Negation by failure”– not tries to prove its argumentnot tries to prove its argument– if the argument fails, not succeedsif the argument fails, not succeeds
\+ club_member(mark).\+ club_member(mark).– tries to prove club_member(mark).tries to prove club_member(mark).– if club_member(mark) succeeds, \+ failsif club_member(mark) succeeds, \+ fails– if club_member(mark) fails, \+ succeedsif club_member(mark) fails, \+ succeeds
Not versus CutNot versus Cut
Not is generally less efficient than cutNot is generally less efficient than cut– club_member called twice for non-membersclub_member called twice for non-members
Not is more logical than cutNot is more logical than cut– can rearrange clauses so not-clause comes firstcan rearrange clauses so not-clause comes first
Exercise: rewrite costToBuy/3 using \+ Exercise: rewrite costToBuy/3 using \+ instead of !instead of !
Variables Under the NotVariables Under the Not
Handled by the predicate callHandled by the predicate call?- \+ parent(jim, X).?- \+ parent(jim, X).X = _G301X = _G301YesYes Tried to find an X that jim was parent ofTried to find an X that jim was parent of
– that failedthat failed– \+ succeeded\+ succeeded– X remains unbound (OK – no child of jim)X remains unbound (OK – no child of jim)
Variables Under the NotVariables Under the Not
Failure results in no variable bindings Failure results in no variable bindings
?- \+ parent(bob, X).?- \+ parent(bob, X).
NoNo Tried to find an X that bob was parent ofTried to find an X that bob was parent of
– that succeeded (X = ann)that succeeded (X = ann)– so \+ failedso \+ failed– answer was No, so no bindings printedanswer was No, so no bindings printed
Not Not NeverNever Binds Variables Binds Variables
No bindings printed for \+ questionsNo bindings printed for \+ questions– success means no values were foundsuccess means no values were found– failure means no bindings survivefailure means no bindings survive
Can’t use \+ or not to generate counter-Can’t use \+ or not to generate counter-examplesexamples– can’t say “find an X such that bob is not the can’t say “find an X such that bob is not the
parent of X”parent of X”
Finding Bob’s Non-ChildrenFinding Bob’s Non-Children
Need to make X into someone firstNeed to make X into someone firstperson(X) :- parent(X, _).person(X) :- parent(X, _).person(X) :- parent(_, X).person(X) :- parent(_, X).nonparent(X, Y) :-nonparent(X, Y) :-
person(X), person(Y), \+ parent(X, Y).person(X), person(Y), \+ parent(X, Y). Now can find nonparentsNow can find nonparents?- ?- nonparent(bob, X).nonparent(bob, X).X = pamX = pam
Not Does Not CommuteNot Does Not Commute
\+’s variables should be as instantiated as \+’s variables should be as instantiated as necessary when callednecessary when called– otherwise it may prevent solutions being foundotherwise it may prevent solutions being found?- ?- \+ parent(X, Y), X = jim, Y = bob.\+ parent(X, Y), X = jim, Y = bob.NoNo?- ?- X = jim, Y = bob, \+ parent(X, Y).X = jim, Y = bob, \+ parent(X, Y).X = jim, Y = bobX = jim, Y = bobYesYes
Next TimeNext Time
Input and OutputInput and Output– Chapter 6Chapter 6