From 6577e0cbea9d8572e4dede283355bc17dc803676 Mon Sep 17 00:00:00 2001 From: Arjun Patel Date: Wed, 20 Feb 2019 15:09:59 -0800 Subject: [PATCH] Initial commit --- .gitattributes | 2 + .vscode/settings.json | 3 + python_basics/foreach.py | 11 + python_basics/helloWorld.py | 3 + python_basics/listcomp.py | 5 + python_basics/listcomp2.py | 2 + python_basics/quickSort.py | 12 + python_basics/quicksort_try.py | 5 + python_basics/shop.py | 43 ++ python_basics/shopTest.py | 16 + tutorial/__pycache__/addition.cpython-36.pyc | Bin 0 -> 327 bytes .../__pycache__/buyLotsOfFruit.cpython-36.pyc | Bin 0 -> 961 bytes tutorial/__pycache__/grading.cpython-36.pyc | Bin 0 -> 9447 bytes .../__pycache__/projectParams.cpython-36.pyc | Bin 0 -> 341 bytes tutorial/__pycache__/shop.cpython-36.pyc | Bin 0 -> 1812 bytes .../__pycache__/shopAroundTown.cpython-36.pyc | Bin 0 -> 3389 bytes tutorial/__pycache__/shopSmart.cpython-36.pyc | Bin 0 -> 1247 bytes .../__pycache__/testClasses.cpython-36.pyc | Bin 0 -> 6662 bytes .../__pycache__/testParser.cpython-36.pyc | Bin 0 -> 2021 bytes .../__pycache__/textDisplay.cpython-36.pyc | Bin 0 -> 2959 bytes tutorial/__pycache__/town.cpython-36.pyc | Bin 0 -> 3724 bytes .../tutorialTestClasses.cpython-36.pyc | Bin 0 -> 1673 bytes tutorial/__pycache__/util.cpython-36.pyc | Bin 0 -> 26811 bytes tutorial/addition.py | 23 + tutorial/autograder.py | 359 +++++++++ tutorial/buyLotsOfFruit.py | 49 ++ tutorial/grading.py | 322 ++++++++ tutorial/projectParams.py | 18 + tutorial/shop.py | 60 ++ tutorial/shopAroundTown.py | 114 +++ tutorial/shopSmart.py | 56 ++ tutorial/testClasses.py | 207 ++++++ tutorial/testParser.py | 86 +++ tutorial/test_cases/CONFIG | 1 + tutorial/test_cases/q1/CONFIG | 2 + tutorial/test_cases/q1/addition1.solution | 3 + tutorial/test_cases/q1/addition1.test | 7 + tutorial/test_cases/q1/addition2.solution | 3 + tutorial/test_cases/q1/addition2.test | 7 + tutorial/test_cases/q1/addition3.solution | 3 + tutorial/test_cases/q1/addition3.test | 7 + tutorial/test_cases/q2/CONFIG | 2 + tutorial/test_cases/q2/food_price1.solution | 3 + tutorial/test_cases/q2/food_price1.test | 7 + tutorial/test_cases/q2/food_price2.solution | 3 + tutorial/test_cases/q2/food_price2.test | 7 + tutorial/test_cases/q2/food_price3.solution | 3 + tutorial/test_cases/q2/food_price3.test | 7 + tutorial/test_cases/q3/CONFIG | 2 + tutorial/test_cases/q3/select_shop1.solution | 3 + tutorial/test_cases/q3/select_shop1.test | 21 + tutorial/test_cases/q3/select_shop2.solution | 3 + tutorial/test_cases/q3/select_shop2.test | 21 + tutorial/test_cases/q3/select_shop3.solution | 3 + tutorial/test_cases/q3/select_shop3.test | 23 + tutorial/textDisplay.py | 85 +++ tutorial/town.py | 105 +++ tutorial/tutorialTestClasses.py | 57 ++ tutorial/util.py | 696 ++++++++++++++++++ 59 files changed, 2480 insertions(+) create mode 100644 .gitattributes create mode 100644 .vscode/settings.json create mode 100644 python_basics/foreach.py create mode 100644 python_basics/helloWorld.py create mode 100644 python_basics/listcomp.py create mode 100644 python_basics/listcomp2.py create mode 100644 python_basics/quickSort.py create mode 100644 python_basics/quicksort_try.py create mode 100644 python_basics/shop.py create mode 100644 python_basics/shopTest.py create mode 100644 tutorial/__pycache__/addition.cpython-36.pyc create mode 100644 tutorial/__pycache__/buyLotsOfFruit.cpython-36.pyc create mode 100644 tutorial/__pycache__/grading.cpython-36.pyc create mode 100644 tutorial/__pycache__/projectParams.cpython-36.pyc create mode 100644 tutorial/__pycache__/shop.cpython-36.pyc create mode 100644 tutorial/__pycache__/shopAroundTown.cpython-36.pyc create mode 100644 tutorial/__pycache__/shopSmart.cpython-36.pyc create mode 100644 tutorial/__pycache__/testClasses.cpython-36.pyc create mode 100644 tutorial/__pycache__/testParser.cpython-36.pyc create mode 100644 tutorial/__pycache__/textDisplay.cpython-36.pyc create mode 100644 tutorial/__pycache__/town.cpython-36.pyc create mode 100644 tutorial/__pycache__/tutorialTestClasses.cpython-36.pyc create mode 100644 tutorial/__pycache__/util.cpython-36.pyc create mode 100644 tutorial/addition.py create mode 100644 tutorial/autograder.py create mode 100644 tutorial/buyLotsOfFruit.py create mode 100644 tutorial/grading.py create mode 100644 tutorial/projectParams.py create mode 100644 tutorial/shop.py create mode 100644 tutorial/shopAroundTown.py create mode 100644 tutorial/shopSmart.py create mode 100644 tutorial/testClasses.py create mode 100644 tutorial/testParser.py create mode 100644 tutorial/test_cases/CONFIG create mode 100644 tutorial/test_cases/q1/CONFIG create mode 100644 tutorial/test_cases/q1/addition1.solution create mode 100644 tutorial/test_cases/q1/addition1.test create mode 100644 tutorial/test_cases/q1/addition2.solution create mode 100644 tutorial/test_cases/q1/addition2.test create mode 100644 tutorial/test_cases/q1/addition3.solution create mode 100644 tutorial/test_cases/q1/addition3.test create mode 100644 tutorial/test_cases/q2/CONFIG create mode 100644 tutorial/test_cases/q2/food_price1.solution create mode 100644 tutorial/test_cases/q2/food_price1.test create mode 100644 tutorial/test_cases/q2/food_price2.solution create mode 100644 tutorial/test_cases/q2/food_price2.test create mode 100644 tutorial/test_cases/q2/food_price3.solution create mode 100644 tutorial/test_cases/q2/food_price3.test create mode 100644 tutorial/test_cases/q3/CONFIG create mode 100644 tutorial/test_cases/q3/select_shop1.solution create mode 100644 tutorial/test_cases/q3/select_shop1.test create mode 100644 tutorial/test_cases/q3/select_shop2.solution create mode 100644 tutorial/test_cases/q3/select_shop2.test create mode 100644 tutorial/test_cases/q3/select_shop3.solution create mode 100644 tutorial/test_cases/q3/select_shop3.test create mode 100644 tutorial/textDisplay.py create mode 100644 tutorial/town.py create mode 100644 tutorial/tutorialTestClasses.py create mode 100644 tutorial/util.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..6445272 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.pythonPath": "C:\\Users\\talksik\\Anaconda3\\envs\\cs188\\python.exe" +} \ No newline at end of file diff --git a/python_basics/foreach.py b/python_basics/foreach.py new file mode 100644 index 0000000..936bd45 --- /dev/null +++ b/python_basics/foreach.py @@ -0,0 +1,11 @@ +# This is what a comment looks like +fruits = ['apples', 'oranges', 'pears', 'bananas'] +for fruit in fruits: + print(fruit + ' for sale') + +fruitPrices = {'apples': 2.00, 'oranges': 1.50, 'pears': 1.75} +for fruit, price in fruitPrices.items(): + if price < 2.00: + print('%s cost %f a pound' % (fruit, price)) + else: + print(fruit + ' are too expensive!') diff --git a/python_basics/helloWorld.py b/python_basics/helloWorld.py new file mode 100644 index 0000000..e4904dd --- /dev/null +++ b/python_basics/helloWorld.py @@ -0,0 +1,3 @@ +# Comment: This is a fairly simple Python script + +print('Hello, World!') diff --git a/python_basics/listcomp.py b/python_basics/listcomp.py new file mode 100644 index 0000000..dc85a2a --- /dev/null +++ b/python_basics/listcomp.py @@ -0,0 +1,5 @@ +nums = [1, 2, 3, 4, 5, 6] +oddNums = [x for x in nums if x % 2 == 1] +print(oddNums) +oddNumsPlusOne = [x + 1 for x in nums if x % 2 == 1] +print(oddNumsPlusOne) diff --git a/python_basics/listcomp2.py b/python_basics/listcomp2.py new file mode 100644 index 0000000..e4f159c --- /dev/null +++ b/python_basics/listcomp2.py @@ -0,0 +1,2 @@ +strings = ['Some string', 'Art', 'Music', 'Artificial Intelligence'] +print([x.lower() for x in strings if len(x) > 5]) diff --git a/python_basics/quickSort.py b/python_basics/quickSort.py new file mode 100644 index 0000000..341997d --- /dev/null +++ b/python_basics/quickSort.py @@ -0,0 +1,12 @@ +def quickSort(lst): + if len(lst) <= 1: + return lst + smaller = [x for x in lst[1:] if x < lst[0]] + larger = [x for x in lst[1:] if x >= lst[0]] + return quickSort(smaller) + [lst[0]] + quickSort(larger) + + +# Main Function +if __name__ == '__main__': + lst = [2, 4, 5, 1] + print(quickSort(lst)) diff --git a/python_basics/quicksort_try.py b/python_basics/quicksort_try.py new file mode 100644 index 0000000..cb3abd6 --- /dev/null +++ b/python_basics/quicksort_try.py @@ -0,0 +1,5 @@ +def quicksort(arr): + pivot = arr[0] + for item in arr[1:]: + if item > pivot: + \ No newline at end of file diff --git a/python_basics/shop.py b/python_basics/shop.py new file mode 100644 index 0000000..d48f8f0 --- /dev/null +++ b/python_basics/shop.py @@ -0,0 +1,43 @@ +class FruitShop: + + def __init__(self, name, fruitPrices): + """ + name: Name of the fruit shop + + fruitPrices: Dictionary with keys as fruit + strings and prices for values e.g. + {'apples':2.00, 'oranges': 1.50, 'pears': 1.75} + """ + self.fruitPrices = fruitPrices + self.name = name + print('Welcome to %s fruit shop' % (name)) + + def getCostPerPound(self, fruit): + """ + fruit: Fruit string + Returns cost of 'fruit', assuming 'fruit' + is in our inventory or None otherwise + """ + if fruit not in self.fruitPrices: + print("Sorry we don't have %s" % (fruit)) + return None + return self.fruitPrices[fruit] + + def getPriceOfOrder(self, orderList): + """ + orderList: List of (fruit, numPounds) tuples + + Returns cost of orderList. If any of the fruit are + """ + totalCost = 0.0 + for fruit, numPounds in orderList: + costPerPound = self.getCostPerPound(fruit) + if costPerPound != None: + totalCost += numPounds * costPerPound + return totalCost + + def getName(self): + return self.name + + def __str__(self): + return "" % self.getName() diff --git a/python_basics/shopTest.py b/python_basics/shopTest.py new file mode 100644 index 0000000..5d934e5 --- /dev/null +++ b/python_basics/shopTest.py @@ -0,0 +1,16 @@ +import shop + +shopName = 'the Berkeley Bowl' +fruitPrices = {'apples': 1.00, 'oranges': 1.50, 'pears': 1.75} +berkeleyShop = shop.FruitShop(shopName, fruitPrices) +applePrice = berkeleyShop.getCostPerPound('apples') +print(applePrice) +print('Apples cost $%.2f at %s.' % (applePrice, shopName)) + +otherName = 'the Stanford Mall' +otherFruitPrices = {'kiwis': 6.00, 'apples': 4.50, 'peaches': 8.75} +otherFruitShop = shop.FruitShop(otherName, otherFruitPrices) +otherPrice = otherFruitShop.getCostPerPound('apples') +print(otherPrice) +print('Apples cost $%.2f at %s.' % (otherPrice, otherName)) +print("My, that's expensive!") diff --git a/tutorial/__pycache__/addition.cpython-36.pyc b/tutorial/__pycache__/addition.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f526a12adbb51441142dc650411b07544892fff3 GIT binary patch literal 327 zcmYjMO-sW-5S>j@E0KbNwCF+Rq7al`ys8w$Q$eKOiV!xPv<=wZupftp-t^D-mvZ$F zc=qIMyma8r&b-+-`}TG)NS}VDZ{rx?8}=gNdcv^BoCxMXf}&5D3yNPMOPcd^<`vdk zx7G@!cY0+cm1bOT(?!sR2S?Rn{=+-4# zzd^l3iBb_3&DD#vwj`AIm-e6H;s#A8sMHGOb-_#~+Forc<+7+1vK7S_n1rAHJ(5I~ ztF&T^ZOqMSa1}-n4TT9lpIOt-bJB}E?}$#hmwJ4uJ-pj!^6%xt33oR9F^T^GmFq~Q literal 0 HcmV?d00001 diff --git a/tutorial/__pycache__/buyLotsOfFruit.cpython-36.pyc b/tutorial/__pycache__/buyLotsOfFruit.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5646ec4c6ac494ead9f9d7e2392bd450ffdedb37 GIT binary patch literal 961 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 WUTM2OuQ}6Iu{iD@wby}V^!^2*k!Jt^ literal 0 HcmV?d00001 diff --git a/tutorial/__pycache__/shop.cpython-36.pyc b/tutorial/__pycache__/shop.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca1afedd0ca11ab6351dc0fb1b34de273e3fecbe GIT binary patch literal 1812 zcmaJ>QE%He5GEzrmQ}}Di=t?O4dBb>83>GYMbN+tiUJ9?hYfD9U?^Z{5CoBSRLi1E zQtjf|PwvZp%7Fcg{f52hb?^HDd)gh9sCJNz0C|+|$h-T#J3ian3yQzL3;$>l@)y|} zF6>{z)DK|!mT*2ueB}{z+iy&jl$=KnPJ0Hl{JzDEY0hM!K)i!YCD038 zjbskh0kxstr&`q3qdv8W^&QoKn9GK}-=bG7&^Dypp9*sd5u6KkE{mLh20trYngR9b z@`;TdD|&FtvW5py^k7)~>OM0CR^-QSxD9N5%K?*l zb_L)iS-}xt?5FEWmy_DF^`Jo+8(08z`@UQn2Y#Rv$ ziU_MVSd80s;NuQ5e}ns7yx4AjHF;%hS$dP^m}# zHa;HsHbqexMA2OGBEx+riryA+R%sMEx3!@5aEB?XeO&Ot*}@o^P}qSA$w3RtuJ8CA bKk&Q0>$_HiETP3+M@#>tisFSm4_4$~c;%;F literal 0 HcmV?d00001 diff --git a/tutorial/__pycache__/shopAroundTown.cpython-36.pyc b/tutorial/__pycache__/shopAroundTown.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c7c12d629d94ca6a57d93540b85bce91a59ca9a GIT binary patch literal 3389 zcma)9-ESMm5#ReFj}#?Kv@OYr?Y0*M9ECU9vF$jvgqRu+r zQF}+&7LHHMN71L`J^w)7^7q_p9}2w`efC3}-^`H|CAV&m*xlLLo!!})-^?yIXJ@PZ zfBf-lbxDZ7iK(v!dK)GA7b+=yAq{DUM%VOBgJ>(Xe2ZJ#x1}weZ-np2k}RVwNmo|T zmSt7WpmkM6){e{{gnlhQkhkQA9m}uEx}1}je=_|Uv{&T3y!w;n*TAjIMQ~@8^?Xh) zshOeaUt)S$)j-cPeND}RUQn0Ryjs9&b@`EeSFXtG@-|9Cz9;XXd@QdACI7OrpI=dR zbr}*@)uOziszaRNq zDs0DH<)yKg9LBwM??Cr4M)bw8jkk<#PLDm*Qt4#N`}6IUR<9STWM$P`U;DJN>fK(6 zbt~#HXLG~}gD&?s(0^9lNxe=h@p?Ldw(ESbeU+r1#;I^dT6KjL#hOfPjET*a3(}ka zhx8jlzx(e(8y7eD?`eQ)zd0bQ5r-Y_DdV%v{@%6P8=z(8-$8ic&yUCjuNlsg(Hm!CJ9i^lB_YP z7I0t0OWaF?ZYvxi3;0i4N6LF~s3Kl(zyE5yYY`ID3(=u@RvT8~&qPg5+(SvOpz4TB zJgW^1m|6_QkLIzxCw?a1mpdxmZFN<{%+0k=bDPxEB+Aw61?ukr?vW%Tt@U2LuHL01 zAxwMsR%v!$aEL#Q9#d2{X7b7e5P4lO)&=70*xnaxzk+R1mon!64vwgNPvDS19IrvhTnYsq$@d{h66`88*o0&RgIn z8D|-laBZVzRE;^*^M=dRJbHQ#V-uH=%_-KXP41!ki%8rKJovphFgUv06g+83`&;uG zqKqTWIL{lHnUR@GkZL$5!34`;fn)O9IdoE@?!_;V9u)%_^H#lJO|5xnxP?m?vSXup zBWn@xG5<~6+0gwm9pW8SjJx{4fa2ns=m399BlI;4O=x8!HcU-N&uzq43Q$^DxUeo6 zR=cRkB4eoCJcl0u>3KAMa-zaB6%9ecaZ)JxYj!P>VLZSyaRhvfivwmy#T#; z8#XMiqTJ!zFs~ua^>BGVi4mllOwe!&3tj>kreiQSg4_;L)%9ma2K@s%7vh3fU?Wi? zDy&(9MQx*wU|2R54W7}aJVM_%cX1%p!8i+mj6u&9`0K<1v_G;2Z(S%?@Tf< z!N0fw&a!hm?m>=`oOpBwJVjI&7d~46&lhB3)oLFWi?rh?Z3Pk7NWZlFThPA<%^Cf3 zg#KydpV?=Gdp0h>N+K6v?}Igl{a7pZ%YQ#@k)P zY|x~eez%}W1N~k>Q;OC%rs&^H(OyB*upaT59`T8#i9Y=|zJ|?O>ko)~i>eP%HJ0;o zvnk^?EL&~jfrUqbYBn{Wl5&S{r?^%5BH-l0kudQIT|>>LU*hX5!JmjguXDY@^=4k- z%aZO+J_W1EI{9qk#IHzTzJ;eYr0M?F^rt0v_`T;-(^h9_JcC~D-tES+AF6v~x#Tlc mwpl~8x|X}>*4zcRQZ=i%$mm+D@vq7eO;K$dF6PpxYy1j;v`t+A literal 0 HcmV?d00001 diff --git a/tutorial/__pycache__/shopSmart.cpython-36.pyc b/tutorial/__pycache__/shopSmart.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b9943ab218c8350f9c3fb938cf7fd820a461fa04 GIT binary patch literal 1247 zcmZ`&OKaOe5Z;w!S(YDpG!2wO*qdW8A$D%2w1$>YXh}kI2y9z~s$JVkWXWAgq%rcz z{S~G3-2R3B6kU5Ly#;#fsk1A`F{P2&VRpWG?0hpmsMqc9zhAuBw-EY)R=zUmeK_$K z5DZboFw+7&)~JT1t_M2RnZZgQ5jA*;nHQS)#O|@Jp+-$sWfrS_)@YgQ=N7B;@?4`8 zur-v`R@OCcgSE;lyb3WjUT5pvWE}L2GdP`9}kY@pju{LSw5ZGw=be~Z^YdR z%*T;6X=8a+?zF;PZ&W~*mkMzV$-W_BK$$-^8F4yxMY1=!$}7v;L&<*w1hN{~RzC|j zu#oe~Eyo$Ve&{&4S**=Z>4?zvlf1lGn}x;d4+{wOTbh7BD-t${yd(>C7T8w5xOb=3 z+l?aA&9)+Hb?>INTZbOG$ykAmpv%OfTIi~h(%15`? zOQAakgqn)9m>SF%9a>oqS=3YrSi7PwRnDGp&SwL=09Sd7{9h)Hk0!NBXm;e9( literal 0 HcmV?d00001 diff --git a/tutorial/__pycache__/testClasses.cpython-36.pyc b/tutorial/__pycache__/testClasses.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da868e7c2484270b19c2c9983ad9071186320dc7 GIT binary patch literal 6662 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 PBo~;{23+7f&Fy~x917Iq literal 0 HcmV?d00001 diff --git a/tutorial/__pycache__/textDisplay.cpython-36.pyc b/tutorial/__pycache__/textDisplay.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c3e3bf9da1d76f8017fea56567c31e4a0a2d394b GIT binary patch literal 2959 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 literal 0 HcmV?d00001 diff --git a/tutorial/__pycache__/util.cpython-36.pyc b/tutorial/__pycache__/util.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..50361c41737956d429188f352cc38e600985df07 GIT binary patch literal 26811 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": """ + We noticed that your project threw an IndexError on q1. + While many things may cause this, it may have been from + assuming a certain number of successors from a state space + or assuming a certain number of actions available from a given + state. Try making your code more general (no hardcoded indices) + and submit again! + """ + }, + 'q3': { + "": """ + We noticed that your project threw an AttributeError on q3. + While many things may cause this, it may have been from assuming + a certain size or structure to the state space. For example, if you have + a line of code assuming that the state is (x, y) and we run your code + on a state space with (x, y, z), this error could be thrown. Try + making your code more general and submit again! + + """ + } +} + +import pprint + +def splitStrings(d): + d2 = dict(d) + for k in d: + if k[0:2] == "__": + del d2[k] + continue + if d2[k].find("\n") >= 0: + d2[k] = d2[k].split("\n") + return d2 + + +def printTest(testDict, solutionDict): + pp = pprint.PrettyPrinter(indent=4) + print("Test case:") + for line in testDict["__raw_lines__"]: + print(" |", line) + print("Solution:") + for line in solutionDict["__raw_lines__"]: + print(" |", line) + + +def runTest(testName, moduleDict, printTestCase=False, display=None): + import testParser + import testClasses + for module in moduleDict: + setattr(sys.modules[__name__], module, moduleDict[module]) + + testDict = testParser.TestParser(testName + ".test").parse() + solutionDict = testParser.TestParser(testName + ".solution").parse() + test_out_file = os.path.join('%s.test_output' % testName) + testDict['test_out_file'] = test_out_file + testClass = getattr(projectTestClasses, testDict['class']) + + questionClass = getattr(testClasses, 'Question') + question = questionClass({'max_points': 0}, display) + testCase = testClass(question, testDict) + + if printTestCase: + printTest(testDict, solutionDict) + + # This is a fragile hack to create a stub grades object + grades = grading.Grades(projectParams.PROJECT_NAME, [(None,0)]) + testCase.execute(grades, moduleDict, solutionDict) + + +# returns all the tests you need to run in order to run question +def getDepends(testParser, testRoot, question): + allDeps = [question] + questionDict = testParser.TestParser(os.path.join(testRoot, question, 'CONFIG')).parse() + if 'depends' in questionDict: + depends = questionDict['depends'].split() + for d in depends: + # run dependencies first + allDeps = getDepends(testParser, testRoot, d) + allDeps + return allDeps + +# get list of questions to grade +def getTestSubdirs(testParser, testRoot, questionToGrade): + problemDict = testParser.TestParser(os.path.join(testRoot, 'CONFIG')).parse() + if questionToGrade != None: + questions = getDepends(testParser, testRoot, questionToGrade) + if len(questions) > 1: + print('Note: due to dependencies, the following tests will be run: %s' % ' '.join(questions)) + return questions + if 'order' in problemDict: + return problemDict['order'].split() + return sorted(os.listdir(testRoot)) + + +# evaluate student code +def evaluate(generateSolutions, testRoot, moduleDict, exceptionMap=ERROR_HINT_MAP, + edxOutput=False, muteOutput=False, gsOutput=False, + printTestCase=False, questionToGrade=None, display=None): + # imports of testbench code. note that the testClasses import must follow + # the import of student code due to dependencies + import testParser + import testClasses + for module in moduleDict: + setattr(sys.modules[__name__], module, moduleDict[module]) + + questions = [] + questionDicts = {} + test_subdirs = getTestSubdirs(testParser, testRoot, questionToGrade) + for q in test_subdirs: + subdir_path = os.path.join(testRoot, q) + if not os.path.isdir(subdir_path) or q[0] == '.': + continue + + # create a question object + questionDict = testParser.TestParser(os.path.join(subdir_path, 'CONFIG')).parse() + questionClass = getattr(testClasses, questionDict['class']) + question = questionClass(questionDict, display) + questionDicts[q] = questionDict + + # load test cases into question + tests = filter(lambda t: re.match('[^#~.].*\.test\Z', t), os.listdir(subdir_path)) + tests = map(lambda t: re.match('(.*)\.test\Z', t).group(1), tests) + for t in sorted(tests): + test_file = os.path.join(subdir_path, '%s.test' % t) + solution_file = os.path.join(subdir_path, '%s.solution' % t) + test_out_file = os.path.join(subdir_path, '%s.test_output' % t) + testDict = testParser.TestParser(test_file).parse() + if testDict.get("disabled", "false").lower() == "true": + continue + testDict['test_out_file'] = test_out_file + testClass = getattr(projectTestClasses, testDict['class']) + testCase = testClass(question, testDict) + def makefun(testCase, solution_file): + if generateSolutions: + # write solution file to disk + return lambda grades: testCase.writeSolution(moduleDict, solution_file) + else: + # read in solution dictionary and pass as an argument + testDict = testParser.TestParser(test_file).parse() + solutionDict = testParser.TestParser(solution_file).parse() + if printTestCase: + return lambda grades: printTest(testDict, solutionDict) or testCase.execute(grades, moduleDict, solutionDict) + else: + return lambda grades: testCase.execute(grades, moduleDict, solutionDict) + question.addTestCase(testCase, makefun(testCase, solution_file)) + + # Note extra function is necessary for scoping reasons + def makefun(question): + return lambda grades: question.execute(grades) + setattr(sys.modules[__name__], q, makefun(question)) + questions.append((q, question.getMaxPoints())) + + grades = grading.Grades(projectParams.PROJECT_NAME, questions, + gsOutput=gsOutput, edxOutput=edxOutput, muteOutput=muteOutput) + if questionToGrade == None: + for q in questionDicts: + for prereq in questionDicts[q].get('depends', '').split(): + grades.addPrereq(q, prereq) + + grades.grade(sys.modules[__name__], bonusPic = projectParams.BONUS_PIC) + return grades.points + + + +def getDisplay(graphicsByDefault, options=None): + graphics = graphicsByDefault + if options is not None and options.noGraphics: + graphics = False + if graphics: + try: + import graphicsDisplay + return graphicsDisplay.PacmanGraphics(1, frameTime=.05) + except ImportError: + pass + import textDisplay + return textDisplay.NullGraphics() + + + + +if __name__ == '__main__': + options = readCommand(sys.argv) + if options.generateSolutions: + confirmGenerate() + codePaths = options.studentCode.split(',') + # moduleCodeDict = {} + # for cp in codePaths: + # moduleName = re.match('.*?([^/]*)\.py', cp).group(1) + # moduleCodeDict[moduleName] = readFile(cp, root=options.codeRoot) + # moduleCodeDict['projectTestClasses'] = readFile(options.testCaseCode, root=options.codeRoot) + # moduleDict = loadModuleDict(moduleCodeDict) + + moduleDict = {} + for cp in codePaths: + moduleName = re.match('.*?([^/]*)\.py', cp).group(1) + moduleDict[moduleName] = loadModuleFile(moduleName, os.path.join(options.codeRoot, cp)) + moduleName = re.match('.*?([^/]*)\.py', options.testCaseCode).group(1) + moduleDict['projectTestClasses'] = loadModuleFile(moduleName, os.path.join(options.codeRoot, options.testCaseCode)) + + + if options.runTest != None: + runTest(options.runTest, moduleDict, printTestCase=options.printTestCase, display=getDisplay(True, options)) + else: + evaluate(options.generateSolutions, options.testRoot, moduleDict, + gsOutput=options.gsOutput, + edxOutput=options.edxOutput, muteOutput=options.muteOutput, printTestCase=options.printTestCase, + questionToGrade=options.gradeQuestion, display=getDisplay(options.gradeQuestion!=None, options)) diff --git a/tutorial/buyLotsOfFruit.py b/tutorial/buyLotsOfFruit.py new file mode 100644 index 0000000..2afc4e6 --- /dev/null +++ b/tutorial/buyLotsOfFruit.py @@ -0,0 +1,49 @@ +# buyLotsOfFruit.py +# ----------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +""" +To run this script, type + + python buyLotsOfFruit.py + +Once you have correctly implemented the buyLotsOfFruit function, +the script should produce the output: + +Cost of [('apples', 2.0), ('pears', 3.0), ('limes', 4.0)] is 12.25 +""" +from __future__ import print_function + +fruitPrices = {'apples': 2.00, 'oranges': 1.50, 'pears': 1.75, + 'limes': 0.75, 'strawberries': 1.00} + + +def buyLotsOfFruit(orderList): + """ + orderList: List of (fruit, numPounds) tuples + + Returns cost of order + """ + totalCost = 0.0 + "*** YOUR CODE HERE ***" + for t in orderList: + item, pounds = t + totalCost += fruitPrices[item] * pounds + return totalCost + + +# Main Method +if __name__ == '__main__': + "This code runs when you invoke the script from the command line" + orderList = [('apples', 2.0), ('pears', 3.0), ('limes', 4.0)] + print('Cost of', orderList, 'is', buyLotsOfFruit(orderList)) diff --git a/tutorial/grading.py b/tutorial/grading.py new file mode 100644 index 0000000..edde8e3 --- /dev/null +++ b/tutorial/grading.py @@ -0,0 +1,322 @@ +# grading.py +# ---------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +"Common code for autograders" + +from __future__ import print_function +import cgi +import time +import sys +import json +import traceback +import pdb +from collections import defaultdict +import util + + +class Grades: + "A data structure for project grades, along with formatting code to display them" + + def __init__(self, projectName, questionsAndMaxesList, + gsOutput=False, edxOutput=False, muteOutput=False): + """ + Defines the grading scheme for a project + projectName: project name + questionsAndMaxesDict: a list of (question name, max points per question) + """ + self.questions = [el[0] for el in questionsAndMaxesList] + self.maxes = dict(questionsAndMaxesList) + self.points = Counter() + self.messages = dict([(q, []) for q in self.questions]) + self.project = projectName + self.start = time.localtime()[1:6] + self.sane = True # Sanity checks + self.currentQuestion = None # Which question we're grading + self.edxOutput = edxOutput + self.gsOutput = gsOutput # GradeScope output + self.mute = muteOutput + self.prereqs = defaultdict(set) + + # print 'Autograder transcript for %s' % self.project + print('Starting on %d-%d at %d:%02d:%02d' % self.start) + + def addPrereq(self, question, prereq): + self.prereqs[question].add(prereq) + + def grade(self, gradingModule, exceptionMap={}, bonusPic=False): + """ + Grades each question + gradingModule: the module with all the grading functions (pass in with sys.modules[__name__]) + """ + + completedQuestions = set([]) + for q in self.questions: + print('\nQuestion %s' % q) + print('=' * (9 + len(q))) + print() + self.currentQuestion = q + + incompleted = self.prereqs[q].difference(completedQuestions) + if len(incompleted) > 0: + prereq = incompleted.pop() + print( \ + """*** NOTE: Make sure to complete Question %s before working on Question %s, + *** because Question %s builds upon your answer for Question %s. + """ % (prereq, q, q, prereq)) + continue + + if self.mute: util.mutePrint() + try: + util.TimeoutFunction(getattr(gradingModule, q), 1800)(self) # Call the question's function + # TimeoutFunction(getattr(gradingModule, q),1200)(self) # Call the question's function + except Exception as inst: # originally, Exception, inst + self.addExceptionMessage(q, inst, traceback) + self.addErrorHints(exceptionMap, inst, q[1]) + except: + self.fail('FAIL: Terminated with a string exception.') + finally: + if self.mute: util.unmutePrint() + + if self.points[q] >= self.maxes[q]: + completedQuestions.add(q) + + print('\n### Question %s: %d/%d ###\n' % (q, self.points[q], self.maxes[q])) + + print('\nFinished at %d:%02d:%02d' % time.localtime()[3:6]) + print("\nProvisional grades\n==================") + + for q in self.questions: + print('Question %s: %d/%d' % (q, self.points[q], self.maxes[q])) + print('------------------') + print('Total: %d/%d' % (self.points.totalCount(), sum(self.maxes.values()))) + if bonusPic and self.points.totalCount() == 25: + print(""" + + ALL HAIL GRANDPAC. + LONG LIVE THE GHOSTBUSTING KING. + + --- ---- --- + | \ / + \ / | + | + \--/ \--/ + | + | + + | + | + + + | + @@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + \ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + \ / @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + V \ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ + \ / @@@@@@@@@@@@@@@@@@@@@@@@@@ + V @@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@ + /\ @@@@@@@@@@@@@@@@@@@@@@ + / \ @@@@@@@@@@@@@@@@@@@@@@@@@ + /\ / @@@@@@@@@@@@@@@@@@@@@@@@@@@ + / \ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + / @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@ + +""") + print(""" +Your grades are NOT yet registered. To register your grades, make sure +to follow your instructor's guidelines to receive credit on your project. +""") + + if self.edxOutput: + self.produceOutput() + if self.gsOutput: + self.produceGradeScopeOutput() + + def addExceptionMessage(self, q, inst, traceback): + """ + Method to format the exception message, this is more complicated because + we need to cgi.escape the traceback but wrap the exception in a
 tag
+        """
+        self.fail('FAIL: Exception raised: %s' % inst)
+        self.addMessage('')
+        for line in traceback.format_exc().split('\n'):
+            self.addMessage(line)
+
+    def addErrorHints(self, exceptionMap, errorInstance, questionNum):
+        typeOf = str(type(errorInstance))
+        questionName = 'q' + questionNum
+        errorHint = ''
+
+        # question specific error hints
+        if exceptionMap.get(questionName):
+            questionMap = exceptionMap.get(questionName)
+            if (questionMap.get(typeOf)):
+                errorHint = questionMap.get(typeOf)
+        # fall back to general error messages if a question specific
+        # one does not exist
+        if (exceptionMap.get(typeOf)):
+            errorHint = exceptionMap.get(typeOf)
+
+        # dont include the HTML if we have no error hint
+        if not errorHint:
+            return ''
+
+        for line in errorHint.split('\n'):
+            self.addMessage(line)
+
+    def produceGradeScopeOutput(self):
+        out_dct = {}
+
+        # total of entire submission
+        total_possible = sum(self.maxes.values())
+        total_score = sum(self.points.values())
+        out_dct['score'] = total_score
+        out_dct['max_score'] = total_possible
+        out_dct['output'] = "Total score (%d / %d)" % (total_score, total_possible)
+
+        # individual tests
+        tests_out = []
+        for name in self.questions:
+            test_out = {}
+            # test name
+            test_out['name'] = name
+            # test score
+            test_out['score'] = self.points[name]
+            test_out['max_score'] = self.maxes[name]
+            # others
+            is_correct = self.points[name] >= self.maxes[name]
+            test_out['output'] = "  Question {num} ({points}/{max}) {correct}".format(
+                num=(name[1] if len(name) == 2 else name),
+                points=test_out['score'],
+                max=test_out['max_score'],
+                correct=('X' if not is_correct else ''),
+            )
+            test_out['tags'] = []
+            tests_out.append(test_out)
+        out_dct['tests'] = tests_out
+
+        # file output
+        with open('gradescope_response.json', 'w') as outfile:
+            json.dump(out_dct, outfile)
+        return
+
+    def produceOutput(self):
+        edxOutput = open('edx_response.html', 'w')
+        edxOutput.write("
") + + # first sum + total_possible = sum(self.maxes.values()) + total_score = sum(self.points.values()) + checkOrX = '' + if (total_score >= total_possible): + checkOrX = '' + header = """ +

+ Total score ({total_score} / {total_possible}) +

+ """.format(total_score=total_score, + total_possible=total_possible, + checkOrX=checkOrX + ) + edxOutput.write(header) + + for q in self.questions: + if len(q) == 2: + name = q[1] + else: + name = q + checkOrX = '' + if (self.points[q] >= self.maxes[q]): + checkOrX = '' + # messages = '\n
\n'.join(self.messages[q]) + messages = "
%s
" % '\n'.join(self.messages[q]) + output = """ +
+
+
+ Question {q} ({points}/{max}) {checkOrX} +
+
+ {messages} +
+
+
+ """.format(q=name, + max=self.maxes[q], + messages=messages, + checkOrX=checkOrX, + points=self.points[q] + ) + # print "*** output for Question %s " % q[1] + # print output + edxOutput.write(output) + edxOutput.write("
") + edxOutput.close() + edxOutput = open('edx_grade', 'w') + edxOutput.write(str(self.points.totalCount())) + edxOutput.close() + + def fail(self, message, raw=False): + "Sets sanity check bit to false and outputs a message" + self.sane = False + self.assignZeroCredit() + self.addMessage(message, raw) + + def assignZeroCredit(self): + self.points[self.currentQuestion] = 0 + + def addPoints(self, amt): + self.points[self.currentQuestion] += amt + + def deductPoints(self, amt): + self.points[self.currentQuestion] -= amt + + def assignFullCredit(self, message="", raw=False): + self.points[self.currentQuestion] = self.maxes[self.currentQuestion] + if message != "": + self.addMessage(message, raw) + + def addMessage(self, message, raw=False): + if not raw: + # We assume raw messages, formatted for HTML, are printed separately + if self.mute: util.unmutePrint() + print('*** ' + message) + if self.mute: util.mutePrint() + message = cgi.escape(message) + self.messages[self.currentQuestion].append(message) + + def addMessageToEmail(self, message): + print("WARNING**** addMessageToEmail is deprecated %s" % message) + for line in message.split('\n'): + pass + # print '%%% ' + line + ' %%%' + # self.messages[self.currentQuestion].append(line) + + +class Counter(dict): + """ + Dict with default 0 + """ + + def __getitem__(self, idx): + try: + return dict.__getitem__(self, idx) + except KeyError: + return 0 + + def totalCount(self): + """ + Returns the sum of counts for all keys. + """ + return sum(self.values()) diff --git a/tutorial/projectParams.py b/tutorial/projectParams.py new file mode 100644 index 0000000..fa1a1ec --- /dev/null +++ b/tutorial/projectParams.py @@ -0,0 +1,18 @@ +# projectParams.py +# ---------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +STUDENT_CODE_DEFAULT = 'addition.py,buyLotsOfFruit.py,shopSmart.py,shopAroundTown.py' +PROJECT_TEST_CLASSES = 'tutorialTestClasses.py' +PROJECT_NAME = 'Project 0: Tutorial' +BONUS_PIC = False diff --git a/tutorial/shop.py b/tutorial/shop.py new file mode 100644 index 0000000..b4d604d --- /dev/null +++ b/tutorial/shop.py @@ -0,0 +1,60 @@ +# shop.py +# ------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +class FruitShop: + + def __init__(self, name, fruitPrices): + """ + name: Name of the fruit shop + + fruitPrices: Dictionary with keys as fruit + strings and prices for values e.g. + {'apples':2.00, 'oranges': 1.50, 'pears': 1.75} + """ + self.fruitPrices = fruitPrices + self.name = name + print('Welcome to %s fruit shop' % (name)) + + def getCostPerPound(self, fruit): + """ + fruit: Fruit string + Returns cost of 'fruit', assuming 'fruit' + is in our inventory or None otherwise + """ + if fruit not in self.fruitPrices: + return None + return self.fruitPrices[fruit] + + def getPriceOfOrder(self, orderList): + """ + orderList: List of (fruit, numPounds) tuples + + Returns cost of orderList, only including the values of + fruits that this fruit shop has. + """ + totalCost = 0.0 + for fruit, numPounds in orderList: + costPerPound = self.getCostPerPound(fruit) + if costPerPound != None: + totalCost += numPounds * costPerPound + return totalCost + + def getName(self): + return self.name + + def __str__(self): + return "" % self.getName() + + def __repr__(self): + return str(self) diff --git a/tutorial/shopAroundTown.py b/tutorial/shopAroundTown.py new file mode 100644 index 0000000..fd1a6c2 --- /dev/null +++ b/tutorial/shopAroundTown.py @@ -0,0 +1,114 @@ +# shopAroundTown.py +# ----------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +""" +Here's the intended output of this script, once you fill it in: + +Welcome to shop1 fruit shop +Welcome to shop2 fruit shop +Welcome to shop3 fruit shop +Orders: [('apples', 1.0), ('oranges', 3.0), ('limes', 2.0)] +At gas price 1 the best route is: ['shop1', 'shop2', 'shop3'] +At gas price 3 the best route is: ['shop1', 'shop3'] +At gas price 5 the best route is: ['shop2'] +At gas price -1 the best route is: ['shop2', 'shop1', 'shop3'] +""" + +from __future__ import print_function +import shop +import town + + +def shopAroundTown(orderList, fruitTown, gasCost): + """ + orderList: List of (fruit, numPound) tuples + fruitTown: A Town object + gasCost: A number representing the cost of going one mile + Returns a list of shops in the order that is the optimal route to take when + buying the fruit in the orderList + """ + possibleRoutes = [] + subsets = getAllSubsets(fruitTown.getShops()) + for subset in subsets: + names = [shop.getName() for shop in subset] + if fruitTown.allFruitsCarriedAtShops(orderList, names): + possibleRoutes += getAllPermutations(subset) + minCost, bestRoute = None, None + for route in possibleRoutes: + cost = fruitTown.getPriceOfOrderOnRoute(orderList, route, gasCost) + if minCost == None or cost < minCost: + minCost, bestRoute = cost, route + return bestRoute + + +def getAllSubsets(lst): + """ + lst: A list + Returns the powerset of lst, i.e. a list of all the possible subsets of lst + """ + if not lst: + return [] + withFirst = [[lst[0]] + rest for rest in getAllSubsets(lst[1:])] + withoutFirst = getAllSubsets(lst[1:]) + return withFirst + withoutFirst + + +def getAllPermutations(lst): + """ + lst: A list + Returns a list of all permutations of lst + """ + if not lst: + return [] + elif len(lst) == 1: + return lst + allPermutations = [] + for i in range(len(lst)): + item = lst[i] + withoutItem = lst[:i] + lst[i:] + allPermutations += prependToAll(item, getAllPermutations(withoutItem)) + return allPermutations + + +def prependToAll(item, lsts): + """ + item: Any object + lsts: A list of lists + Returns a copy of lsts with item prepended to each list contained in lsts + """ + return [[item] + lst for lst in lsts] + + +if __name__ == '__main__': + "This code runs when you invoke the script from the command line" + orders = [('apples', 1.0), ('oranges', 3.0), ('limes', 2.0)] + dir1 = {'apples': 2.0, 'oranges': 1.0} + dir2 = {'apples': 1.0, 'oranges': 5.0, 'limes': 3.0} + dir3 = {'apples': 2.0, 'limes': 2.0} + shop1 = shop.FruitShop('shop1', dir1) + shop2 = shop.FruitShop('shop2', dir2) + shop3 = shop.FruitShop('shop3', dir3) + shops = [shop1, shop2, shop3] + distances = {('home', 'shop1'): 2, + ('home', 'shop2'): 1, + ('home', 'shop3'): 1, + ('shop1', 'shop2'): 2.5, + ('shop1', 'shop3'): 2.5, + ('shop2', 'shop3'): 1 + } + fruitTown = town.Town(shops, distances) + print("Orders:", orders) + for price in (1, 3, 5, -1): + print("At gas price", price, "the best route is:", \ + shopAroundTown(orders, fruitTown, price)) diff --git a/tutorial/shopSmart.py b/tutorial/shopSmart.py new file mode 100644 index 0000000..b4323f8 --- /dev/null +++ b/tutorial/shopSmart.py @@ -0,0 +1,56 @@ +# shopSmart.py +# ------------ +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +""" +Here's the intended output of this script, once you fill it in: + +Welcome to shop1 fruit shop +Welcome to shop2 fruit shop +For orders: [('apples', 1.0), ('oranges', 3.0)] best shop is shop1 +For orders: [('apples', 3.0)] best shop is shop2 +""" +from __future__ import print_function +import shop + + +def shopSmart(orderList, fruitShops): + """ + orderList: List of (fruit, numPound) tuples + fruitShops: List of FruitShops + + Return the shop where order would be the least amount in total + """ + "*** YOUR CODE HERE ***" + cheapest_shop, cheapest_order = fruitShops[0], fruitShops[0].getPriceOfOrder( + orderList) + for each_shop in fruitShops: + currOder = each_shop.getPriceOfOrder(orderList) + if currOder < cheapest_order: + cheapest_shop, cheapest_order = each_shop, currOder + return cheapest_shop + + +if __name__ == '__main__': + "This code runs when you invoke the script from the command line" + orders = [('apples', 1.0), ('oranges', 3.0)] + dir1 = {'apples': 2.0, 'oranges': 1.0} + shop1 = shop.FruitShop('shop1', dir1) + dir2 = {'apples': 1.0, 'oranges': 5.0} + shop2 = shop.FruitShop('shop2', dir2) + shops = [shop1, shop2] + print("For orders ", orders, ", the best shop is", + shopSmart(orders, shops).getName()) + orders = [('apples', 3.0)] + print("For orders: ", orders, ", the best shop is", + shopSmart(orders, shops).getName()) diff --git a/tutorial/testClasses.py b/tutorial/testClasses.py new file mode 100644 index 0000000..7a4ce35 --- /dev/null +++ b/tutorial/testClasses.py @@ -0,0 +1,207 @@ +# testClasses.py +# -------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +# import modules from python standard library +from __future__ import print_function +import inspect +import re +import sys + + +# Class which models a question in a project. Note that questions have a +# maximum number of points they are worth, and are composed of a series of +# test cases +class Question(object): + + def raiseNotDefined(self): + print('Method not implemented: %s' % inspect.stack()[1][3]) + sys.exit(1) + + def __init__(self, questionDict, display): + self.maxPoints = int(questionDict['max_points']) + self.testCases = [] + self.display = display + + def getDisplay(self): + return self.display + + def getMaxPoints(self): + return self.maxPoints + + # Note that 'thunk' must be a function which accepts a single argument, + # namely a 'grading' object + def addTestCase(self, testCase, thunk): + self.testCases.append((testCase, thunk)) + + def execute(self, grades): + self.raiseNotDefined() + + +# Question in which all test cases must be passed in order to receive credit +class PassAllTestsQuestion(Question): + + def execute(self, grades): + # TODO: is this the right way to use grades? The autograder doesn't seem to use it. + testsFailed = False + grades.assignZeroCredit() + for _, f in self.testCases: + if not f(grades): + testsFailed = True + if testsFailed: + grades.fail("Tests failed.") + else: + grades.assignFullCredit() + + +class ExtraCreditPassAllTestsQuestion(Question): + def __init__(self, questionDict, display): + Question.__init__(self, questionDict, display) + self.extraPoints = int(questionDict['extra_points']) + + def execute(self, grades): + # TODO: is this the right way to use grades? The autograder doesn't seem to use it. + testsFailed = False + grades.assignZeroCredit() + for _, f in self.testCases: + if not f(grades): + testsFailed = True + if testsFailed: + grades.fail("Tests failed.") + else: + grades.assignFullCredit() + grades.addPoints(self.extraPoints) + + +# Question in which predict credit is given for test cases with a ``points'' property. +# All other tests are mandatory and must be passed. +class HackedPartialCreditQuestion(Question): + + def execute(self, grades): + # TODO: is this the right way to use grades? The autograder doesn't seem to use it. + grades.assignZeroCredit() + + points = 0 + passed = True + for testCase, f in self.testCases: + testResult = f(grades) + if "points" in testCase.testDict: + if testResult: points += float(testCase.testDict["points"]) + else: + passed = passed and testResult + + ## FIXME: Below terrible hack to match q3's logic + if int(points) == self.maxPoints and not passed: + grades.assignZeroCredit() + else: + grades.addPoints(int(points)) + + +class Q6PartialCreditQuestion(Question): + """Fails any test which returns False, otherwise doesn't effect the grades object. + Partial credit tests will add the required points.""" + + def execute(self, grades): + grades.assignZeroCredit() + + results = [] + for _, f in self.testCases: + results.append(f(grades)) + if False in results: + grades.assignZeroCredit() + + +class PartialCreditQuestion(Question): + """Fails any test which returns False, otherwise doesn't effect the grades object. + Partial credit tests will add the required points.""" + + def execute(self, grades): + grades.assignZeroCredit() + + for _, f in self.testCases: + if not f(grades): + grades.assignZeroCredit() + grades.fail("Tests failed.") + return False + + +class NumberPassedQuestion(Question): + """Grade is the number of test cases passed.""" + + def execute(self, grades): + grades.addPoints([f(grades) for _, f in self.testCases].count(True)) + + +# Template modeling a generic test case +class TestCase(object): + + def raiseNotDefined(self): + print('Method not implemented: %s' % inspect.stack()[1][3]) + sys.exit(1) + + def getPath(self): + return self.path + + def __init__(self, question, testDict): + self.question = question + self.testDict = testDict + self.path = testDict['path'] + self.messages = [] + + def __str__(self): + self.raiseNotDefined() + + def execute(self, grades, moduleDict, solutionDict): + self.raiseNotDefined() + + def writeSolution(self, moduleDict, filePath): + self.raiseNotDefined() + return True + + # Tests should call the following messages for grading + # to ensure a uniform format for test output. + # + # TODO: this is hairy, but we need to fix grading.py's interface + # to get a nice hierarchical project - question - test structure, + # then these should be moved into Question proper. + def testPass(self, grades): + grades.addMessage('PASS: %s' % (self.path,)) + for line in self.messages: + grades.addMessage(' %s' % (line,)) + return True + + def testFail(self, grades): + grades.addMessage('FAIL: %s' % (self.path,)) + for line in self.messages: + grades.addMessage(' %s' % (line,)) + return False + + # This should really be question level? + # + def testPartial(self, grades, points, maxPoints): + grades.addPoints(points) + extraCredit = max(0, points - maxPoints) + regularCredit = points - extraCredit + + grades.addMessage('%s: %s (%s of %s points)' % ( + "PASS" if points >= maxPoints else "FAIL", self.path, regularCredit, maxPoints)) + if extraCredit > 0: + grades.addMessage('EXTRA CREDIT: %s points' % (extraCredit,)) + + for line in self.messages: + grades.addMessage(' %s' % (line,)) + + return True + + def addMessage(self, message): + self.messages.extend(message.split('\n')) diff --git a/tutorial/testParser.py b/tutorial/testParser.py new file mode 100644 index 0000000..15646d0 --- /dev/null +++ b/tutorial/testParser.py @@ -0,0 +1,86 @@ +# testParser.py +# ------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + +from __future__ import print_function +import re +import sys + + +class TestParser(object): + + def __init__(self, path): + # save the path to the test file + self.path = path + + def removeComments(self, rawlines): + # remove any portion of a line following a '#' symbol + fixed_lines = [] + for l in rawlines: + idx = l.find('#') + if idx == -1: + fixed_lines.append(l) + else: + fixed_lines.append(l[0:idx]) + return '\n'.join(fixed_lines) + + def parse(self): + # read in the test case and remove comments + test = {} + with open(self.path) as handle: + raw_lines = handle.read().split('\n') + + test_text = self.removeComments(raw_lines) + test['__raw_lines__'] = raw_lines + test['path'] = self.path + test['__emit__'] = [] + lines = test_text.split('\n') + i = 0 + # read a property in each loop cycle + while (i < len(lines)): + # skip blank lines + if re.match('\A\s*\Z', lines[i]): + test['__emit__'].append(("raw", raw_lines[i])) + i += 1 + continue + m = re.match('\A([^"]*?):\s*"([^"]*)"\s*\Z', lines[i]) + if m: + test[m.group(1)] = m.group(2) + test['__emit__'].append(("oneline", m.group(1))) + i += 1 + continue + m = re.match('\A([^"]*?):\s*"""\s*\Z', lines[i]) + if m: + msg = [] + i += 1 + while (not re.match('\A\s*"""\s*\Z', lines[i])): + msg.append(raw_lines[i]) + i += 1 + test[m.group(1)] = '\n'.join(msg) + test['__emit__'].append(("multiline", m.group(1))) + i += 1 + continue + print('error parsing test file: %s' % self.path) + sys.exit(1) + return test + + +def emitTestDict(testDict, handle): + for kind, data in testDict['__emit__']: + if kind == "raw": + handle.write(data + "\n") + elif kind == "oneline": + handle.write('%s: "%s"\n' % (data, testDict[data])) + elif kind == "multiline": + handle.write('%s: """\n%s\n"""\n' % (data, testDict[data])) + else: + raise Exception("Bad __emit__") diff --git a/tutorial/test_cases/CONFIG b/tutorial/test_cases/CONFIG new file mode 100644 index 0000000..eec8c0b --- /dev/null +++ b/tutorial/test_cases/CONFIG @@ -0,0 +1 @@ +order: "q1 q2 q3" diff --git a/tutorial/test_cases/q1/CONFIG b/tutorial/test_cases/q1/CONFIG new file mode 100644 index 0000000..279f0f0 --- /dev/null +++ b/tutorial/test_cases/q1/CONFIG @@ -0,0 +1,2 @@ +max_points: "1" +class: "PassAllTestsQuestion" diff --git a/tutorial/test_cases/q1/addition1.solution b/tutorial/test_cases/q1/addition1.solution new file mode 100644 index 0000000..6261975 --- /dev/null +++ b/tutorial/test_cases/q1/addition1.solution @@ -0,0 +1,3 @@ +# This is the solution file for test_cases/q1/addition1.test. +# The result of evaluating the test must equal the below when cast to a string. +result: "2" diff --git a/tutorial/test_cases/q1/addition1.test b/tutorial/test_cases/q1/addition1.test new file mode 100644 index 0000000..2d807c7 --- /dev/null +++ b/tutorial/test_cases/q1/addition1.test @@ -0,0 +1,7 @@ +class: "EvalTest" +success: "add(a,b) returns the sum of a and b" +failure: "add(a,b) must return the sum of a and b" + +# A python expression to be evaluated. This expression must return the +# same result for the student and instructor's code. +test: "addition.add(1,1)" diff --git a/tutorial/test_cases/q1/addition2.solution b/tutorial/test_cases/q1/addition2.solution new file mode 100644 index 0000000..49ebaa0 --- /dev/null +++ b/tutorial/test_cases/q1/addition2.solution @@ -0,0 +1,3 @@ +# This is the solution file for test_cases/q1/addition2.test. +# The result of evaluating the test must equal the below when cast to a string. +result: "5" diff --git a/tutorial/test_cases/q1/addition2.test b/tutorial/test_cases/q1/addition2.test new file mode 100644 index 0000000..76a0d91 --- /dev/null +++ b/tutorial/test_cases/q1/addition2.test @@ -0,0 +1,7 @@ +class: "EvalTest" +success: "add(a,b) returns the sum of a and b" +failure: "add(a,b) must return the sum of a and b" + +# A python expression to be evaluated. This expression must return the +# same result for the student and instructor's code. +test: "addition.add(2,3)" diff --git a/tutorial/test_cases/q1/addition3.solution b/tutorial/test_cases/q1/addition3.solution new file mode 100644 index 0000000..c258470 --- /dev/null +++ b/tutorial/test_cases/q1/addition3.solution @@ -0,0 +1,3 @@ +# This is the solution file for test_cases/q1/addition3.test. +# The result of evaluating the test must equal the below when cast to a string. +result: "7.9" diff --git a/tutorial/test_cases/q1/addition3.test b/tutorial/test_cases/q1/addition3.test new file mode 100644 index 0000000..462ff13 --- /dev/null +++ b/tutorial/test_cases/q1/addition3.test @@ -0,0 +1,7 @@ +class: "EvalTest" +success: "add(a,b) returns the sum of a and b" +failure: "add(a,b) must return the sum of a and b" + +# A python expression to be evaluated. This expression must return the +# same result for the student and instructor's code. +test: "addition.add(10,-2.1)" diff --git a/tutorial/test_cases/q2/CONFIG b/tutorial/test_cases/q2/CONFIG new file mode 100644 index 0000000..279f0f0 --- /dev/null +++ b/tutorial/test_cases/q2/CONFIG @@ -0,0 +1,2 @@ +max_points: "1" +class: "PassAllTestsQuestion" diff --git a/tutorial/test_cases/q2/food_price1.solution b/tutorial/test_cases/q2/food_price1.solution new file mode 100644 index 0000000..b2a8e87 --- /dev/null +++ b/tutorial/test_cases/q2/food_price1.solution @@ -0,0 +1,3 @@ +# This is the solution file for test_cases/q2/food_price1.test. +# The result of evaluating the test must equal the below when cast to a string. +result: "12.25" diff --git a/tutorial/test_cases/q2/food_price1.test b/tutorial/test_cases/q2/food_price1.test new file mode 100644 index 0000000..93d2c3d --- /dev/null +++ b/tutorial/test_cases/q2/food_price1.test @@ -0,0 +1,7 @@ +class: "EvalTest" +success: "buyLotsOfFruit correctly computes the cost of the order" +failure: "buyLotsOfFruit must compute the correct cost of the order" + +# A python expression to be evaluated. This expression must return the +# same result for the student and instructor's code. +test: "buyLotsOfFruit.buyLotsOfFruit([ ('apples', 2.0), ('pears',3.0), ('limes',4.0) ])" diff --git a/tutorial/test_cases/q2/food_price2.solution b/tutorial/test_cases/q2/food_price2.solution new file mode 100644 index 0000000..e3ec5e8 --- /dev/null +++ b/tutorial/test_cases/q2/food_price2.solution @@ -0,0 +1,3 @@ +# This is the solution file for test_cases/q2/food_price2.test. +# The result of evaluating the test must equal the below when cast to a string. +result: "14.75" diff --git a/tutorial/test_cases/q2/food_price2.test b/tutorial/test_cases/q2/food_price2.test new file mode 100644 index 0000000..b70e8d9 --- /dev/null +++ b/tutorial/test_cases/q2/food_price2.test @@ -0,0 +1,7 @@ +class: "EvalTest" +success: "buyLotsOfFruit correctly computes the cost of the order" +failure: "buyLotsOfFruit must compute the correct cost of the order" + +# A python expression to be evaluated. This expression must return the +# same result for the student and instructor's code. +test: "buyLotsOfFruit.buyLotsOfFruit([ ('apples', 4.0), ('pears',3.0), ('limes',2.0) ])" diff --git a/tutorial/test_cases/q2/food_price3.solution b/tutorial/test_cases/q2/food_price3.solution new file mode 100644 index 0000000..23976d6 --- /dev/null +++ b/tutorial/test_cases/q2/food_price3.solution @@ -0,0 +1,3 @@ +# This is the solution file for test_cases/q2/food_price3.test. +# The result of evaluating the test must equal the below when cast to a string. +result: "6.4375" diff --git a/tutorial/test_cases/q2/food_price3.test b/tutorial/test_cases/q2/food_price3.test new file mode 100644 index 0000000..9d9a395 --- /dev/null +++ b/tutorial/test_cases/q2/food_price3.test @@ -0,0 +1,7 @@ +class: "EvalTest" +success: "buyLotsOfFruit correctly computes the cost of the order" +failure: "buyLotsOfFruit must compute the correct cost of the order" + +# A python expression to be evaluated. This expression must return the +# same result for the student and instructor's code. +test: "buyLotsOfFruit.buyLotsOfFruit([ ('apples', 1.25), ('pears',1.50), ('limes',1.75) ])" diff --git a/tutorial/test_cases/q3/CONFIG b/tutorial/test_cases/q3/CONFIG new file mode 100644 index 0000000..279f0f0 --- /dev/null +++ b/tutorial/test_cases/q3/CONFIG @@ -0,0 +1,2 @@ +max_points: "1" +class: "PassAllTestsQuestion" diff --git a/tutorial/test_cases/q3/select_shop1.solution b/tutorial/test_cases/q3/select_shop1.solution new file mode 100644 index 0000000..083c232 --- /dev/null +++ b/tutorial/test_cases/q3/select_shop1.solution @@ -0,0 +1,3 @@ +# This is the solution file for test_cases/q3/select_shop1.test. +# The result of evaluating the test must equal the below when cast to a string. +result: "" diff --git a/tutorial/test_cases/q3/select_shop1.test b/tutorial/test_cases/q3/select_shop1.test new file mode 100644 index 0000000..05b358a --- /dev/null +++ b/tutorial/test_cases/q3/select_shop1.test @@ -0,0 +1,21 @@ +class: "EvalTest" +success: "shopSmart(order, shops) selects the cheapest shop" +failure: "shopSmart(order, shops) must select the cheapest shop" + +# Python statements initializing variables for the test below. +preamble: """ +import shop + +dir1 = {'apples': 2.0, 'oranges':1.0} +shop1 = shop.FruitShop('shop1',dir1) +dir2 = {'apples': 1.0, 'oranges': 5.0} +shop2 = shop.FruitShop('shop2',dir2) +shops = [shop1, shop2] + +order = [('apples',1.0), ('oranges',3.0)] +ans = shopSmart.shopSmart(order, shops) +""" + +# A python expression to be evaluated. This expression must return the +# same result for the student and instructor's code. +test: "ans" diff --git a/tutorial/test_cases/q3/select_shop2.solution b/tutorial/test_cases/q3/select_shop2.solution new file mode 100644 index 0000000..8497881 --- /dev/null +++ b/tutorial/test_cases/q3/select_shop2.solution @@ -0,0 +1,3 @@ +# This is the solution file for test_cases/q3/select_shop2.test. +# The result of evaluating the test must equal the below when cast to a string. +result: "" diff --git a/tutorial/test_cases/q3/select_shop2.test b/tutorial/test_cases/q3/select_shop2.test new file mode 100644 index 0000000..04cb6f7 --- /dev/null +++ b/tutorial/test_cases/q3/select_shop2.test @@ -0,0 +1,21 @@ +class: "EvalTest" +success: "shopSmart(order, shops) selects the cheapest shop" +failure: "shopSmart(order, shops) must select the cheapest shop" + +# Python statements initializing variables for the test below. +preamble: """ +import shop + +dir1 = {'apples': 2.0, 'oranges':1.0} +shop1 = shop.FruitShop('shop1',dir1) +dir2 = {'apples': 1.0, 'oranges': 5.0} +shop2 = shop.FruitShop('shop2',dir2) +shops = [shop1, shop2] + +order = [('apples',3.0)] +ans = shopSmart.shopSmart(order, shops) +""" + +# A python expression to be evaluated. This expression must return the +# same result for the student and instructor's code. +test: "ans" diff --git a/tutorial/test_cases/q3/select_shop3.solution b/tutorial/test_cases/q3/select_shop3.solution new file mode 100644 index 0000000..a6b6352 --- /dev/null +++ b/tutorial/test_cases/q3/select_shop3.solution @@ -0,0 +1,3 @@ +# This is the solution file for test_cases/q3/select_shop3.test. +# The result of evaluating the test must equal the below when cast to a string. +result: "" diff --git a/tutorial/test_cases/q3/select_shop3.test b/tutorial/test_cases/q3/select_shop3.test new file mode 100644 index 0000000..24eeb06 --- /dev/null +++ b/tutorial/test_cases/q3/select_shop3.test @@ -0,0 +1,23 @@ +class: "EvalTest" +success: "shopSmart(order, shops) selects the cheapest shop" +failure: "shopSmart(order, shops) must select the cheapest shop" + +# Python statements initializing variables for the test below. +preamble: """ +import shop + +dir1 = {'apples': 2.0, 'oranges':1.0} +shop1 = shop.FruitShop('shop1',dir1) +dir2 = {'apples': 1.0, 'oranges': 5.0} +shop2 = shop.FruitShop('shop2',dir2) +dir3 = {'apples': 1.5, 'oranges': 2.0} +shop3 = shop.FruitShop('shop3',dir3) +shops = [shop1, shop2, shop3] + +order = [('apples',10.0), ('oranges',3.0)] +ans = shopSmart.shopSmart(order, shops) +""" + +# A python expression to be evaluated. This expression must return the +# same result for the student and instructor's code. +test: "ans" diff --git a/tutorial/textDisplay.py b/tutorial/textDisplay.py new file mode 100644 index 0000000..21f2b2c --- /dev/null +++ b/tutorial/textDisplay.py @@ -0,0 +1,85 @@ +# textDisplay.py +# -------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + +from __future__ import print_function +import time + +try: + import pacman +except: + pass + +DRAW_EVERY = 1 +SLEEP_TIME = 0 # This can be overwritten by __init__ +DISPLAY_MOVES = False +QUIET = False # Supresses output + + +class NullGraphics: + def initialize(self, state, isBlue=False): + pass + + def update(self, state): + pass + + def checkNullDisplay(self): + return True + + def pause(self): + time.sleep(SLEEP_TIME) + + def draw(self, state): + print(state) + + def updateDistributions(self, dist): + pass + + def finish(self): + pass + + +class PacmanGraphics: + def __init__(self, speed=None): + if speed != None: + global SLEEP_TIME + SLEEP_TIME = speed + + def initialize(self, state, isBlue=False): + self.draw(state) + self.pause() + self.turn = 0 + self.agentCounter = 0 + + def update(self, state): + numAgents = len(state.agentStates) + self.agentCounter = (self.agentCounter + 1) % numAgents + if self.agentCounter == 0: + self.turn += 1 + if DISPLAY_MOVES: + ghosts = [pacman.nearestPoint(state.getGhostPosition(i)) for i in range(1, numAgents)] + print("%4d) P: %-8s" % (self.turn, str(pacman.nearestPoint(state.getPacmanPosition()))), + '| Score: %-5d' % state.score, '| Ghosts:', ghosts) + if self.turn % DRAW_EVERY == 0: + self.draw(state) + self.pause() + if state._win or state._lose: + self.draw(state) + + def pause(self): + time.sleep(SLEEP_TIME) + + def draw(self, state): + print(state) + + def finish(self): + pass diff --git a/tutorial/town.py b/tutorial/town.py new file mode 100644 index 0000000..dfbce01 --- /dev/null +++ b/tutorial/town.py @@ -0,0 +1,105 @@ +# town.py +# ------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +import shop + + +class Town: + + def __init__(self, shops, distances): + """ + shops: List of FruitShop objects + + distances: Dictionary with keys as pairs (tuples) of names of places + ('home' or name strings of FruitShops) and numbers for values which + represent the distance between the two places in miles, e.g. + {('home','shop1') : 1, ('home','shop2') : 1, ('shop1','shop2') : 2} + """ + self.shops = shops + self.distances = distances + + def getFruitCostPerPoundOnRoute(self, fruit, route): + """ + fruit: Fruit string + + route: List of shop names + Returns the best cost per pound of 'fruit' at any of the shops along + the route. If none of the shops carry 'fruit', returns None + """ + routeShops = [shop for shop in self.shops if shop.getName() in route] + costs = [] + for shop in routeShops: + cost = shop.getCostPerPound(fruit) + if cost is not None: + costs.append(cost) + if not costs: + # None of the shops carry this fruit + return None + return min(costs) + + def allFruitsCarriedAtShops(self, orderList, shops): + """ + orderList: List of (fruit, numPounds) tuples + + shops: List of shop names + Returns whether all fruit in the order list can be purchased at at least + one of these shops. + """ + return None not in [self.getFruitCostPerPoundOnRoute(fruit, shops) + for fruit, _ in orderList] + + def getDistance(self, loc1, loc2): + """ + loc1: A name of a place ('home' or the name of a FruitShop in town) + + loc2: A name of a place ('home' or the name of a FruitShop in town) + Returns the distance between these two places in this town. + """ + if (loc1, loc2) in self.distances: + return self.distances[(loc1, loc2)] + return self.distances[(loc2, loc1)] + + def getTotalDistanceOnRoute(self, route): + """ + route: List of shop names + Returns the total distance traveled by starting at 'home', going to + each shop on the route in order, then returning to 'home' + """ + if not route: + return 0 + totalDistance = self.getDistance('home', route[0]) + for i in xrange(len(route) - 1): + totalDistance += self.getDistance(route[i], route[i + 1]) + totalDistance += self.getDistance(route[-1], 'home') + return totalDistance + + def getPriceOfOrderOnRoute(self, orderList, route, gasCost): + """ + orderList: List of (fruit, numPounds) tuples + + route: List of shop names + + gasCost: A number representing the cost of driving 1 mile + Returns cost of orderList on this route. If any fruit are not available + on this route, returns None. + """ + totalCost = self.getTotalDistanceOnRoute(route) * gasCost + for fruit, numPounds in orderList: + costPerPound = self.getFruitCostPerPoundOnRoute(fruit, route) + if costPerPound is not None: + totalCost += numPounds * costPerPound + return totalCost + + def getShops(self): + return self.shops diff --git a/tutorial/tutorialTestClasses.py b/tutorial/tutorialTestClasses.py new file mode 100644 index 0000000..07ea4c3 --- /dev/null +++ b/tutorial/tutorialTestClasses.py @@ -0,0 +1,57 @@ +# tutorialTestClasses.py +# ---------------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +import testClasses + + +# Simple test case which evals an arbitrary piece of python code. +# The test is correct if the output of the code given the student's +# solution matches that of the instructor's. +class EvalTest(testClasses.TestCase): + + def __init__(self, question, testDict): + super(EvalTest, self).__init__(question, testDict) + self.preamble = compile(testDict.get('preamble', ""), "%s.preamble" % self.getPath(), 'exec') + self.test = compile(testDict['test'], "%s.test" % self.getPath(), 'eval') + self.success = testDict['success'] + self.failure = testDict['failure'] + + def evalCode(self, moduleDict): + bindings = dict(moduleDict) + # exec self.preamble in bindings + exec(self.preamble, bindings) + return str(eval(self.test, bindings)) + + def execute(self, grades, moduleDict, solutionDict): + result = self.evalCode(moduleDict) + if result == solutionDict['result']: + grades.addMessage('PASS: %s' % self.path) + grades.addMessage('\t%s' % self.success) + return True + else: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('\t%s' % self.failure) + grades.addMessage('\tstudent result: "%s"' % result) + grades.addMessage('\tcorrect result: "%s"' % solutionDict['result']) + + return False + + def writeSolution(self, moduleDict, filePath): + handle = open(filePath, 'w') + handle.write('# This is the solution file for %s.\n' % self.path) + handle.write('# The result of evaluating the test must equal the below when cast to a string.\n') + + handle.write('result: "%s"\n' % self.evalCode(moduleDict)) + handle.close() + return True diff --git a/tutorial/util.py b/tutorial/util.py new file mode 100644 index 0000000..50cd74a --- /dev/null +++ b/tutorial/util.py @@ -0,0 +1,696 @@ +# util.py +# ------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +# util.py +# ------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +import sys +import inspect +import heapq, random + + +# import cStringIO + + +class FixedRandom: + def __init__(self): + fixedState = (3, (2147483648, 507801126, 683453281, 310439348, 2597246090, + 2209084787, 2267831527, 979920060, 3098657677, 37650879, 807947081, 3974896263, + 881243242, 3100634921, 1334775171, 3965168385, 746264660, 4074750168, 500078808, + 776561771, 702988163, 1636311725, 2559226045, 157578202, 2498342920, 2794591496, + 4130598723, 496985844, 2944563015, 3731321600, 3514814613, 3362575829, 3038768745, + 2206497038, 1108748846, 1317460727, 3134077628, 988312410, 1674063516, 746456451, + 3958482413, 1857117812, 708750586, 1583423339, 3466495450, 1536929345, 1137240525, + 3875025632, 2466137587, 1235845595, 4214575620, 3792516855, 657994358, 1241843248, + 1695651859, 3678946666, 1929922113, 2351044952, 2317810202, 2039319015, 460787996, 3654096216, + 4068721415, 1814163703, 2904112444, 1386111013, 574629867, 2654529343, 3833135042, 2725328455, + 552431551, 4006991378, 1331562057, 3710134542, 303171486, 1203231078, 2670768975, 54570816, + 2679609001, 578983064, 1271454725, 3230871056, 2496832891, 2944938195, 1608828728, 367886575, + 2544708204, 103775539, 1912402393, 1098482180, 2738577070, 3091646463, 1505274463, 2079416566, + 659100352, 839995305, 1696257633, 274389836, 3973303017, 671127655, 1061109122, 517486945, + 1379749962, 3421383928, 3116950429, 2165882425, 2346928266, 2892678711, 2936066049, + 1316407868, 2873411858, 4279682888, 2744351923, 3290373816, 1014377279, 955200944, 4220990860, + 2386098930, 1772997650, 3757346974, 1621616438, 2877097197, 442116595, 2010480266, 2867861469, + 2955352695, 605335967, 2222936009, 2067554933, 4129906358, 1519608541, 1195006590, 1942991038, + 2736562236, 279162408, 1415982909, 4099901426, 1732201505, 2934657937, 860563237, 2479235483, + 3081651097, 2244720867, 3112631622, 1636991639, 3860393305, 2312061927, 48780114, 1149090394, + 2643246550, 1764050647, 3836789087, 3474859076, 4237194338, 1735191073, 2150369208, 92164394, + 756974036, 2314453957, 323969533, 4267621035, 283649842, 810004843, 727855536, 1757827251, + 3334960421, 3261035106, 38417393, 2660980472, 1256633965, 2184045390, 811213141, 2857482069, + 2237770878, 3891003138, 2787806886, 2435192790, 2249324662, 3507764896, 995388363, 856944153, + 619213904, 3233967826, 3703465555, 3286531781, 3863193356, 2992340714, 413696855, 3865185632, + 1704163171, 3043634452, 2225424707, 2199018022, 3506117517, 3311559776, 3374443561, + 1207829628, 668793165, 1822020716, 2082656160, 1160606415, 3034757648, 741703672, 3094328738, + 459332691, 2702383376, 1610239915, 4162939394, 557861574, 3805706338, 3832520705, 1248934879, + 3250424034, 892335058, 74323433, 3209751608, 3213220797, 3444035873, 3743886725, 1783837251, + 610968664, 580745246, 4041979504, 201684874, 2673219253, 1377283008, 3497299167, 2344209394, + 2304982920, 3081403782, 2599256854, 3184475235, 3373055826, 695186388, 2423332338, 222864327, + 1258227992, 3627871647, 3487724980, 4027953808, 3053320360, 533627073, 3026232514, 2340271949, + 867277230, 868513116, 2158535651, 2487822909, 3428235761, 3067196046, 3435119657, 1908441839, + 788668797, 3367703138, 3317763187, 908264443, 2252100381, 764223334, 4127108988, 384641349, + 3377374722, 1263833251, 1958694944, 3847832657, 1253909612, 1096494446, 555725445, 2277045895, + 3340096504, 1383318686, 4234428127, 1072582179, 94169494, 1064509968, 2681151917, 2681864920, + 734708852, 1338914021, 1270409500, 1789469116, 4191988204, 1716329784, 2213764829, 3712538840, + 919910444, 1318414447, 3383806712, 3054941722, 3378649942, 1205735655, 1268136494, 2214009444, + 2532395133, 3232230447, 230294038, 342599089, 772808141, 4096882234, 3146662953, 2784264306, + 1860954704, 2675279609, 2984212876, 2466966981, 2627986059, 2985545332, 2578042598, + 1458940786, 2944243755, 3959506256, 1509151382, 325761900, 942251521, 4184289782, 2756231555, + 3297811774, 1169708099, 3280524138, 3805245319, 3227360276, 3199632491, 2235795585, + 2865407118, 36763651, 2441503575, 3314890374, 1755526087, 17915536, 1196948233, 949343045, + 3815841867, 489007833, 2654997597, 2834744136, 417688687, 2843220846, 85621843, 747339336, + 2043645709, 3520444394, 1825470818, 647778910, 275904777, 1249389189, 3640887431, 4200779599, + 323384601, 3446088641, 4049835786, 1718989062, 3563787136, 44099190, 3281263107, 22910812, + 1826109246, 745118154, 3392171319, 1571490704, 354891067, 815955642, 1453450421, 940015623, + 796817754, 1260148619, 3898237757, 176670141, 1870249326, 3317738680, 448918002, 4059166594, + 2003827551, 987091377, 224855998, 3520570137, 789522610, 2604445123, 454472869, 475688926, + 2990723466, 523362238, 3897608102, 806637149, 2642229586, 2928614432, 1564415411, 1691381054, + 3816907227, 4082581003, 1895544448, 3728217394, 3214813157, 4054301607, 1882632454, + 2873728645, 3694943071, 1297991732, 2101682438, 3952579552, 678650400, 1391722293, 478833748, + 2976468591, 158586606, 2576499787, 662690848, 3799889765, 3328894692, 2474578497, 2383901391, + 1718193504, 3003184595, 3630561213, 1929441113, 3848238627, 1594310094, 3040359840, + 3051803867, 2462788790, 954409915, 802581771, 681703307, 545982392, 2738993819, 8025358, + 2827719383, 770471093, 3484895980, 3111306320, 3900000891, 2116916652, 397746721, 2087689510, + 721433935, 1396088885, 2751612384, 1998988613, 2135074843, 2521131298, 707009172, 2398321482, + 688041159, 2264560137, 482388305, 207864885, 3735036991, 3490348331, 1963642811, 3260224305, + 3493564223, 1939428454, 1128799656, 1366012432, 2858822447, 1428147157, 2261125391, + 1611208390, 1134826333, 2374102525, 3833625209, 2266397263, 3189115077, 770080230, 2674657172, + 4280146640, 3604531615, 4235071805, 3436987249, 509704467, 2582695198, 4256268040, 3391197562, + 1460642842, 1617931012, 457825497, 1031452907, 1330422862, 4125947620, 2280712485, 431892090, + 2387410588, 2061126784, 896457479, 3480499461, 2488196663, 4021103792, 1877063114, 2744470201, + 1046140599, 2129952955, 3583049218, 4217723693, 2720341743, 820661843, 1079873609, 3360954200, + 3652304997, 3335838575, 2178810636, 1908053374, 4026721976, 1793145418, 476541615, 973420250, + 515553040, 919292001, 2601786155, 1685119450, 3030170809, 1590676150, 1665099167, 651151584, + 2077190587, 957892642, 646336572, 2743719258, 866169074, 851118829, 4225766285, 963748226, + 799549420, 1955032629, 799460000, 2425744063, 2441291571, 1928963772, 528930629, 2591962884, + 3495142819, 1896021824, 901320159, 3181820243, 843061941, 3338628510, 3782438992, 9515330, + 1705797226, 953535929, 764833876, 3202464965, 2970244591, 519154982, 3390617541, 566616744, + 3438031503, 1853838297, 170608755, 1393728434, 676900116, 3184965776, 1843100290, 78995357, + 2227939888, 3460264600, 1745705055, 1474086965, 572796246, 4081303004, 882828851, 1295445825, + 137639900, 3304579600, 2722437017, 4093422709, 273203373, 2666507854, 3998836510, 493829981, + 1623949669, 3482036755, 3390023939, 833233937, 1639668730, 1499455075, 249728260, 1210694006, + 3836497489, 1551488720, 3253074267, 3388238003, 2372035079, 3945715164, 2029501215, + 3362012634, 2007375355, 4074709820, 631485888, 3135015769, 4273087084, 3648076204, 2739943601, + 1374020358, 1760722448, 3773939706, 1313027823, 1895251226, 4224465911, 421382535, 1141067370, + 3660034846, 3393185650, 1850995280, 1451917312, 3841455409, 3926840308, 1397397252, + 2572864479, 2500171350, 3119920613, 531400869, 1626487579, 1099320497, 407414753, 2438623324, + 99073255, 3175491512, 656431560, 1153671785, 236307875, 2824738046, 2320621382, 892174056, + 230984053, 719791226, 2718891946, 624), None) + self.random = random.Random() + self.random.setstate(fixedState) + + +""" + Data structures useful for implementing SearchAgents +""" + + +class Stack: + "A container with a last-in-first-out (LIFO) queuing policy." + + def __init__(self): + self.list = [] + + def push(self, item): + "Push 'item' onto the stack" + self.list.append(item) + + def pop(self): + "Pop the most recently pushed item from the stack" + return self.list.pop() + + def isEmpty(self): + "Returns true if the stack is empty" + return len(self.list) == 0 + + +class Queue: + "A container with a first-in-first-out (FIFO) queuing policy." + + def __init__(self): + self.list = [] + + def push(self, item): + "Enqueue the 'item' into the queue" + self.list.insert(0, item) + + def pop(self): + """ + Dequeue the earliest enqueued item still in the queue. This + operation removes the item from the queue. + """ + return self.list.pop() + + def isEmpty(self): + "Returns true if the queue is empty" + return len(self.list) == 0 + + +class PriorityQueue: + """ + Implements a priority queue data structure. Each inserted item + has a priority associated with it and the client is usually interested + in quick retrieval of the lowest-priority item in the queue. This + data structure allows O(1) access to the lowest-priority item. + """ + + def __init__(self): + self.heap = [] + self.count = 0 + + def push(self, item, priority): + entry = (priority, self.count, item) + heapq.heappush(self.heap, entry) + self.count += 1 + + def pop(self): + (_, _, item) = heapq.heappop(self.heap) + return item + + def isEmpty(self): + return len(self.heap) == 0 + + def update(self, item, priority): + # If item already in priority queue with higher priority, update its priority and rebuild the heap. + # If item already in priority queue with equal or lower priority, do nothing. + # If item not in priority queue, do the same thing as self.push. + for index, (p, c, i) in enumerate(self.heap): + if i == item: + if p <= priority: + break + del self.heap[index] + self.heap.append((priority, c, item)) + heapq.heapify(self.heap) + break + else: + self.push(item, priority) + + +class PriorityQueueWithFunction(PriorityQueue): + """ + Implements a priority queue with the same push/pop signature of the + Queue and the Stack classes. This is designed for drop-in replacement for + those two classes. The caller has to provide a priority function, which + extracts each item's priority. + """ + + def __init__(self, priorityFunction): + "priorityFunction (item) -> priority" + self.priorityFunction = priorityFunction # store the priority function + PriorityQueue.__init__(self) # super-class initializer + + def push(self, item): + "Adds an item to the queue with priority from the priority function" + PriorityQueue.push(self, item, self.priorityFunction(item)) + + +def manhattanDistance(xy1, xy2): + "Returns the Manhattan distance between points xy1 and xy2" + return abs(xy1[0] - xy2[0]) + abs(xy1[1] - xy2[1]) + + +""" + Data structures and functions useful for various course projects + + The search project should not need anything below this line. +""" + + +class Counter(dict): + """ + A counter keeps track of counts for a set of keys. + + The counter class is an extension of the standard python + dictionary type. It is specialized to have number values + (integers or floats), and includes a handful of additional + functions to ease the task of counting data. In particular, + all keys are defaulted to have value 0. Using a dictionary: + + a = {} + print(a['test']) + + would give an error, while the Counter class analogue: + + >>> a = Counter() + >>> print(a['test']) + 0 + + returns the default 0 value. Note that to reference a key + that you know is contained in the counter, + you can still use the dictionary syntax: + + >>> a = Counter() + >>> a['test'] = 2 + >>> print(a['test']) + 2 + + This is very useful for counting things without initializing their counts, + see for example: + + >>> a['blah'] += 1 + >>> print(a['blah']) + 1 + + The counter also includes additional functionality useful in implementing + the classifiers for this assignment. Two counters can be added, + subtracted or multiplied together. See below for details. They can + also be normalized and their total count and arg max can be extracted. + """ + + def __getitem__(self, idx): + self.setdefault(idx, 0) + return dict.__getitem__(self, idx) + + def incrementAll(self, keys, count): + """ + Increments all elements of keys by the same count. + + >>> a = Counter() + >>> a.incrementAll(['one','two', 'three'], 1) + >>> a['one'] + 1 + >>> a['two'] + 1 + """ + for key in keys: + self[key] += count + + def argMax(self): + """ + Returns the key with the highest value. + """ + if len(self.keys()) == 0: return None + all = self.items() + values = [x[1] for x in all] + maxIndex = values.index(max(values)) + return all[maxIndex][0] + + def sortedKeys(self): + """ + Returns a list of keys sorted by their values. Keys + with the highest values will appear first. + + >>> a = Counter() + >>> a['first'] = -2 + >>> a['second'] = 4 + >>> a['third'] = 1 + >>> a.sortedKeys() + ['second', 'third', 'first'] + """ + sortedItems = self.items() + compare = lambda x, y: sign(y[1] - x[1]) + sortedItems.sort(cmp=compare) + return [x[0] for x in sortedItems] + + def totalCount(self): + """ + Returns the sum of counts for all keys. + """ + return sum(self.values()) + + def normalize(self): + """ + Edits the counter such that the total count of all + keys sums to 1. The ratio of counts for all keys + will remain the same. Note that normalizing an empty + Counter will result in an error. + """ + total = float(self.totalCount()) + if total == 0: return + for key in self.keys(): + self[key] = self[key] / total + + def divideAll(self, divisor): + """ + Divides all counts by divisor + """ + divisor = float(divisor) + for key in self: + self[key] /= divisor + + def copy(self): + """ + Returns a copy of the counter + """ + return Counter(dict.copy(self)) + + def __mul__(self, y): + """ + Multiplying two counters gives the dot product of their vectors where + each unique label is a vector element. + + >>> a = Counter() + >>> b = Counter() + >>> a['first'] = -2 + >>> a['second'] = 4 + >>> b['first'] = 3 + >>> b['second'] = 5 + >>> a['third'] = 1.5 + >>> a['fourth'] = 2.5 + >>> a * b + 14 + """ + sum = 0 + x = self + if len(x) > len(y): + x, y = y, x + for key in x: + if key not in y: + continue + sum += x[key] * y[key] + return sum + + def __radd__(self, y): + """ + Adding another counter to a counter increments the current counter + by the values stored in the second counter. + + >>> a = Counter() + >>> b = Counter() + >>> a['first'] = -2 + >>> a['second'] = 4 + >>> b['first'] = 3 + >>> b['third'] = 1 + >>> a += b + >>> a['first'] + 1 + """ + for key, value in y.items(): + self[key] += value + + def __add__(self, y): + """ + Adding two counters gives a counter with the union of all keys and + counts of the second added to counts of the first. + + >>> a = Counter() + >>> b = Counter() + >>> a['first'] = -2 + >>> a['second'] = 4 + >>> b['first'] = 3 + >>> b['third'] = 1 + >>> (a + b)['first'] + 1 + """ + addend = Counter() + for key in self: + if key in y: + addend[key] = self[key] + y[key] + else: + addend[key] = self[key] + for key in y: + if key in self: + continue + addend[key] = y[key] + return addend + + def __sub__(self, y): + """ + Subtracting a counter from another gives a counter with the union of all keys and + counts of the second subtracted from counts of the first. + + >>> a = Counter() + >>> b = Counter() + >>> a['first'] = -2 + >>> a['second'] = 4 + >>> b['first'] = 3 + >>> b['third'] = 1 + >>> (a - b)['first'] + -5 + """ + addend = Counter() + for key in self: + if key in y: + addend[key] = self[key] - y[key] + else: + addend[key] = self[key] + for key in y: + if key in self: + continue + addend[key] = -1 * y[key] + return addend + + +def raiseNotDefined(): + fileName = inspect.stack()[1][1] + line = inspect.stack()[1][2] + method = inspect.stack()[1][3] + + print("*** Method not implemented: %s at line %s of %s" % (method, line, fileName)) + sys.exit(1) + + +def normalize(vectorOrCounter): + """ + normalize a vector or counter by dividing each value by the sum of all values + """ + normalizedCounter = Counter() + if type(vectorOrCounter) == type(normalizedCounter): + counter = vectorOrCounter + total = float(counter.totalCount()) + if total == 0: return counter + for key in counter.keys(): + value = counter[key] + normalizedCounter[key] = value / total + return normalizedCounter + else: + vector = vectorOrCounter + s = float(sum(vector)) + if s == 0: return vector + return [el / s for el in vector] + + +def nSample(distribution, values, n): + if sum(distribution) != 1: + distribution = normalize(distribution) + rand = [random.random() for i in range(n)] + rand.sort() + samples = [] + samplePos, distPos, cdf = 0, 0, distribution[0] + while samplePos < n: + if rand[samplePos] < cdf: + samplePos += 1 + samples.append(values[distPos]) + else: + distPos += 1 + cdf += distribution[distPos] + return samples + + +def sample(distribution, values=None): + if type(distribution) == Counter: + items = sorted(distribution.items()) + distribution = [i[1] for i in items] + values = [i[0] for i in items] + if sum(distribution) != 1: + distribution = normalize(distribution) + choice = random.random() + i, total = 0, distribution[0] + while choice > total: + i += 1 + total += distribution[i] + return values[i] + + +def sampleFromCounter(ctr): + items = sorted(ctr.items()) + return sample([v for k, v in items], [k for k, v in items]) + + +def getProbability(value, distribution, values): + """ + Gives the probability of a value under a discrete distribution + defined by (distributions, values). + """ + total = 0.0 + for prob, val in zip(distribution, values): + if val == value: + total += prob + return total + + +def flipCoin(p): + r = random.random() + return r < p + + +def chooseFromDistribution(distribution): + "Takes either a counter or a list of (prob, key) pairs and samples" + if type(distribution) == dict or type(distribution) == Counter: + return sample(distribution) + r = random.random() + base = 0.0 + for prob, element in distribution: + base += prob + if r <= base: return element + + +def nearestPoint(pos): + """ + Finds the nearest grid point to a position (discretizes). + """ + (current_row, current_col) = pos + + grid_row = int(current_row + 0.5) + grid_col = int(current_col + 0.5) + return (grid_row, grid_col) + + +def sign(x): + """ + Returns 1 or -1 depending on the sign of x + """ + if (x >= 0): + return 1 + else: + return -1 + + +def arrayInvert(array): + """ + Inverts a matrix stored as a list of lists. + """ + result = [[] for i in array] + for outer in array: + for inner in range(len(outer)): + result[inner].append(outer[inner]) + return result + + +def matrixAsList(matrix, value=True): + """ + Turns a matrix into a list of coordinates matching the specified value + """ + rows, cols = len(matrix), len(matrix[0]) + cells = [] + for row in range(rows): + for col in range(cols): + if matrix[row][col] == value: + cells.append((row, col)) + return cells + + +def lookup(name, namespace): + """ + Get a method or class from any imported module from its name. + Usage: lookup(functionName, globals()) + """ + dots = name.count('.') + if dots > 0: + moduleName, objName = '.'.join(name.split('.')[:-1]), name.split('.')[-1] + module = __import__(moduleName) + return getattr(module, objName) + else: + modules = [obj for obj in namespace.values() if str(type(obj)) == ""] + options = [getattr(module, name) for module in modules if name in dir(module)] + options += [obj[1] for obj in namespace.items() if obj[0] == name] + if len(options) == 1: return options[0] + if len(options) > 1: raise Exception('Name conflict for %s') + raise Exception('%s not found as a method or class' % name) + + +def pause(): + """ + Pauses the output stream awaiting user feedback. + """ + print("") + raw_input() + + +# code to handle timeouts +# +# FIXME +# NOTE: TimeoutFuncton is NOT reentrant. Later timeouts will silently +# disable earlier timeouts. Could be solved by maintaining a global list +# of active time outs. Currently, questions which have test cases calling +# this have all student code so wrapped. +# +import signal +import time + + +class TimeoutFunctionException(Exception): + """Exception to raise on a timeout""" + pass + + +class TimeoutFunction: + def __init__(self, function, timeout): + self.timeout = timeout + self.function = function + + def handle_timeout(self, signum, frame): + raise TimeoutFunctionException() + + def __call__(self, *args, **keyArgs): + # If we have SIGALRM signal, use it to cause an exception if and + # when this function runs too long. Otherwise check the time taken + # after the method has returned, and throw an exception then. + if hasattr(signal, 'SIGALRM'): + old = signal.signal(signal.SIGALRM, self.handle_timeout) + signal.alarm(self.timeout) + try: + result = self.function(*args, **keyArgs) + finally: + signal.signal(signal.SIGALRM, old) + signal.alarm(0) + else: + startTime = time.time() + result = self.function(*args, **keyArgs) + timeElapsed = time.time() - startTime + if timeElapsed >= self.timeout: + self.handle_timeout(None, None) + return result + + +_ORIGINAL_STDOUT = None +_ORIGINAL_STDERR = None +_MUTED = False + + +class WritableNull: + def write(self, string): + pass + + +def mutePrint(): + global _ORIGINAL_STDOUT, _ORIGINAL_STDERR, _MUTED + if _MUTED: + return + _MUTED = True + + _ORIGINAL_STDOUT = sys.stdout + # _ORIGINAL_STDERR = sys.stderr + sys.stdout = WritableNull() + # sys.stderr = WritableNull() + + +def unmutePrint(): + global _ORIGINAL_STDOUT, _ORIGINAL_STDERR, _MUTED + if not _MUTED: + return + _MUTED = False + + sys.stdout = _ORIGINAL_STDOUT + # sys.stderr = _ORIGINAL_STDERR