CSCI 291 Final Exam -- Spring 2023 Problem 1: --------- The mystery function returns true if all elements in the list are identical. For [3,3,4,4], therefore, it returns False. Problem 2: --------- Sheesh, Leverett, I have *so* many things to say in response! Some key points: Working declaratively saves me from having to specify some of the lower-level details of my solutions. I can focus on *what* I want, and leave much or all of the *how* to the system. Since the low-level computational details are left to the system, I don't have to waste my time or mental energy on them, and I won't have to worry about getting those details wrong. (For example, if I can use a map in Haskell rather than writing a for loop, I don't have to worry about off-by-one errors in my loop counter -- the map makes sure it visits each item in the collection.) Leaving those details out of my programs also leaves shorter, clearer problem descriptions that make it much easier to see what my program is specifying -- the algorithm doesn't get lost in all of the imperative syntax and bookkeeping code. It can also make my programs easier to automatically parallelize. When the system is left to supply the details (as with map, or a search for solutions in Prolog), it can choose to spread those tasks across multiple cores or processors without involvement from me, the programmer. Problem 3: --------- % It's true if we can find a group, G, that they're both in. commonGroup(S1, S2, Ps) :- member(in(S1,G),Ps), member(in(S2,G),Ps). % You could also find ALL groups that each is in, and look to see if % there's any overlap, but that's less declarative: commonGroup2(S1,S2,Ps) :- findall(G, member(in(S1,G),Ps), G1), findall(G, member(in(S2,G),Ps), G2), member(G, G1), member(G, G2). Problem 4: --------- % We can use setof to get a list of the unique groups containing a % subject, S. That will fail if S isn't in any groups though, so we % need a rule that succeeds with [] in that case. allGroups(S,Ps,[]) :- \+ member(in(S,_),Ps). allGroups(S,Ps,Groups) :- setof(G, member(in(S,G),Ps), Groups). Problem 5: --------- -- Here's one way to write it. We find the groups each subject is in, -- and look to see if any of s2's groups are also in s1's list. This -- version builds a list of booleans and folds || over it to get an answer. commonGroup _ _ [] = False commonGroup s1 s2 ps = foldr1 (||) (map (\e->elem e s1Groups) s2Groups) where s1Groups = [ g | (s,g)<-ps, s==s1 ] s2Groups = [ g | (s,g)<-ps, s==s2 ] -- This takes a similar approach, but uses a list comprehension to keep -- all of s2's groups that also contain s1. If that list isn't empty we've -- found a common group. commonGroup2 _ _ [] = False commonGroup2 s1 s2 ps = not (null common) where s1Groups = [g | (s,g)<-ps, s==s1 ] -- Groups that s1 is in common = [g | (s,g)<-ps, s==s2, elem g s1Groups ] -- Groups that both are in Problem 6: --------- Here's the definition of fullSister again: fullSister(Sis, Sib) :- parent(P1,Sis), parent(P1,Sib), parent(P2,Sis), parent(P2,Sib), female(Sis), Sis \= Sib, P1 \= P2. a) Yes, the definition would have the same meaning if the female goal were moved to become the first goal in the rule. All of the same constraints are being expressed on Sis and Sib. It's true that we might select bindings for Sis that end up *not* working out (they might not have a sibling, for example), but Prolog will backtrack and try other bindings for Sis until it finds one that works. b) The width of the tree is determined by the number of ways in which goals can be satisfied. When running the query "fullSister(X,Y)" with the original program, we end up evaluating the goal "parent(P1,Sis)" first, where both inputs are unbound. There are lots of possible solutions to that goal -- it will find all possible combinations of mothers and their children, and fathers and their children. (For the record, there are 18 possible solutions.) And for each of those 18 combinations, we then branch out to consider additional parent goals. We'd end up with a narrower tree if we started with the female goal, since there are only 7 female facts in the program. After picking a female we'd still have to evaluate the parent goals, but they're now more constrained since we're asking about a particular female. (Finding Sis's parent and then other children of that same parent.) c) A ! after the female goal in the original program would change its meaning pretty dramatically. It would lock in the variable bindings made by all goals but the last two. Those final goals are there to make sure we don't declare someone to be their own fullSister. It would be possible that we'd end up with Sis and Sib being the same persion, lock in those choices, then fail because of the \= goal. Worse than that, even if the first attempt succeeded (we found two different people that satisfied the constraints), we couldn't find any *other* solutions. Thus there would be zero or one solutions, rather than the dozen or so solutions in the version without the cut.