diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/cs101courseware/Report2_handin_0_of_5.token b/cs101courseware/Report2_handin_0_of_5.token new file mode 100644 index 0000000000000000000000000000000000000000..b22456a48f6b99a1abe7ffca6488220f21a2b2e3 Binary files /dev/null and b/cs101courseware/Report2_handin_0_of_5.token differ diff --git a/cs101courseware/Report2_handin_5_of_0.token b/cs101courseware/Report2_handin_5_of_0.token new file mode 100644 index 0000000000000000000000000000000000000000..984da98d1e0010a7bfbf93d29eaac5f2cd2a4589 Binary files /dev/null and b/cs101courseware/Report2_handin_5_of_0.token differ diff --git a/cs101courseware/Report2_handin_5_of_5.token b/cs101courseware/Report2_handin_5_of_5.token new file mode 100644 index 0000000000000000000000000000000000000000..dba8a94bc2f0399b59e538765b683baf81260c06 Binary files /dev/null and b/cs101courseware/Report2_handin_5_of_5.token differ diff --git a/cs101courseware/Report2_resources_do_not_hand_in.dat b/cs101courseware/Report2_resources_do_not_hand_in.dat new file mode 100644 index 0000000000000000000000000000000000000000..70f3ef95826214c89a880cec61678ee63dc02b5e Binary files /dev/null and b/cs101courseware/Report2_resources_do_not_hand_in.dat differ diff --git a/cs101courseware/__pycache__/__init__.cpython-36.pyc b/cs101courseware/__pycache__/__init__.cpython-36.pyc index f0120104402414327be5b9e01a5f44b6705be45c..edc9f166ab4535e9b841d33a70cc7d9cbeb6026b 100644 Binary files a/cs101courseware/__pycache__/__init__.cpython-36.pyc and b/cs101courseware/__pycache__/__init__.cpython-36.pyc differ diff --git a/cs101courseware/__pycache__/cs101report1.cpython-36.pyc b/cs101courseware/__pycache__/cs101report1.cpython-36.pyc index 10e0fe726c06a4bb64737430fb2635d6af031acd..578cd397e12827b6037e4c9a6ca83283c311d2a4 100644 Binary files a/cs101courseware/__pycache__/cs101report1.cpython-36.pyc and b/cs101courseware/__pycache__/cs101report1.cpython-36.pyc differ diff --git a/cs101courseware/__pycache__/cs101report2.cpython-36.pyc b/cs101courseware/__pycache__/cs101report2.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5803ebf4780d5db45c1803854af192f6394fd2da Binary files /dev/null and b/cs101courseware/__pycache__/cs101report2.cpython-36.pyc differ diff --git a/cs101courseware/__pycache__/cs101report2_grade.cpython-36.pyc b/cs101courseware/__pycache__/cs101report2_grade.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..06a7bac8eb7c4fdd20fbb069c10838580df93f46 Binary files /dev/null and b/cs101courseware/__pycache__/cs101report2_grade.cpython-36.pyc differ diff --git a/cs101courseware/__pycache__/homework1.cpython-36.pyc b/cs101courseware/__pycache__/homework1.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bb6f4852c406c2c6dea4f958bd019f5f3073e78f Binary files /dev/null and b/cs101courseware/__pycache__/homework1.cpython-36.pyc differ diff --git a/cs101courseware/cs101report1.py b/cs101courseware/cs101report1.py index 1ee2d8c634d1d4022b65e90bdb033b63bb0986dc..d1136303d49fa6ed2bab183337d73939c63fa5b6 100644 --- a/cs101courseware/cs101report1.py +++ b/cs101courseware/cs101report1.py @@ -31,10 +31,10 @@ class LinearRegressionQuestion(QuestionGroup): def process_output(self, res, txt, numbers): return numbers[-1] -class Report0(Report): +class Report1(Report): title = "CS 101 Report 1" questions = [(ListReversalQuestion, 5), (LinearRegressionQuestion, 13)] pack_imports = [homework1] # Include this file in .token file if __name__ == "__main__": - evaluate_report_student(Report0()) + evaluate_report_student(Report1()) diff --git a/cs101courseware/cs101report2.py b/cs101courseware/cs101report2.py new file mode 100644 index 0000000000000000000000000000000000000000..a3d8691c950c1a6a4da8500112822ff4952ae622 --- /dev/null +++ b/cs101courseware/cs101report2.py @@ -0,0 +1,46 @@ +from unitgrade.unitgrade import QuestionGroup, Report, QPrintItem, Hidden + +class ListReversalQuestion(QuestionGroup): + title = "Reversal of list" + class ListReversalItem(QPrintItem): + l = [1, 3, 5, 1, 610] + def compute_answer_print(self): + from cs101courseware.homework1 import reverse_list + print(reverse_list(self.l)) + + class ListReversalWordsItem(ListReversalItem): + l = ["hello", "world", "summer", "dog"] + + class ListReversalWordsItemHidden(ListReversalItem, Hidden): + l = ["hello", "world", "summer", "dog"] + +# class LinearRegressionQuestion(QuestionGroup): +# title = "Linear regression and Boston dataset" +# class CoefficientsItem(QPrintItem): +# testfun = QPrintItem.assertL2 +# tol = 0.03 +# +# def compute_answer_print(self): +# from cs101courseware_example.homework1 import boston_linear +# boston_linear() +# +# def process_output(self, res, txt, numbers): +# return numbers[:-1] +# +# class RMSEItem(CoefficientsItem): +# def process_output(self, res, txt, numbers): +# return numbers[-1] + +class Report2(Report): + title = "CS 101 Report 2" + questions = [(ListReversalQuestion, 5), ] + pack_imports = [] # Include this file in .token file + +if __name__ == "__main__": + # from unitgrade_private.hidden_create_files import setup_answers, setup_grade_file_report + from unitgrade.unitgrade_helpers import evaluate_report_student + # setup_grade_file_report(Report2, minify=True, bzip=True, obfuscate=True) + # evaluate_report_student(Report2()) + evaluate_report_student(Report2()) + + diff --git a/cs101courseware/cs101report2_grade.py b/cs101courseware/cs101report2_grade.py new file mode 100644 index 0000000000000000000000000000000000000000..c0324e60978addc5e54feb198dacda40d5e3fdef --- /dev/null +++ b/cs101courseware/cs101report2_grade.py @@ -0,0 +1,421 @@ + +__version__ = "0.0.4" +import os +import compress_pickle + +def cache_write(object, file_name, verbose=True): + dn = os.path.dirname(file_name) + if not os.path.exists(dn): + os.mkdir(dn) + if verbose: print("Writing cache...", file_name) + with open(file_name, 'wb', ) as f: + compress_pickle.dump(object, f, compression="lzma") + if verbose: print("Done!") + + +def cache_exists(file_name): + # file_name = cn_(file_name) if cache_prefix else file_name + return os.path.exists(file_name) + + +def cache_read(file_name): + # file_name = cn_(file_name) if cache_prefix else file_name + if os.path.exists(file_name): + with open(file_name, 'rb') as f: + return compress_pickle.load(f, compression="lzma") + # return pickle.load(f) + else: + return None + + + +""" +git add . && git commit -m "Options" && git push && pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade + +""" +import unittest +import numpy as np +import os +from io import StringIO +import sys +import collections +import inspect + +myround = lambda x: int(x) # required. + +def setup_dir_by_class(C,base_dir): + name = C.__class__.__name__ + # base_dir = os.path.join(base_dir, name) + # if not os.path.isdir(base_dir): + # os.makedirs(base_dir) + return base_dir, name + +class Hidden: + def hide(self): + return True + +class Capturing(list): + def __enter__(self, capture_errors=True): + self._stdout = sys.stdout + sys.stdout = self._stringio = StringIO() + if capture_errors: + self._sterr = sys.stderr + sys.sterr = StringIO() # memory hole it + self.capture_errors = capture_errors + return self + + def __exit__(self, *args): + self.extend(self._stringio.getvalue().splitlines()) + del self._stringio # free up some memory + sys.stdout = self._stdout + if self.capture_errors: + sys.sterr = self._sterr + + +class QItem(unittest.TestCase): + title = None + testfun = unittest.TestCase.assertEqual + tol = 0 + + def __init__(self, working_directory=None, correct_answer_payload=None, *args, **kwargs): + self.name = self.__class__.__name__ + self._correct_answer_payload = correct_answer_payload + super().__init__(*args, **kwargs) + if self.title is None: + self.title = self.name + + def assertL2(self, computed, expected, tol=None): + if tol == None: + tol = self.tol + diff = np.abs( (np.asarray(computed) - np.asarray(expected)) ) + if np.max(diff) > tol: + print("Not equal within tolerance {tol}") + print(f"Element-wise differences {diff.tolist()}") + self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}") + + def assertL2Relative(self, computed, expected, tol=None): + if tol == None: + tol = self.tol + diff = np.abs( (np.asarray(computed) - np.asarray(expected)) ) + diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) ) + if np.sum(diff > tol) > 0: + print(f"Not equal within tolerance {tol}") + print(f"Element-wise differences {diff.tolist()}") + self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}") + + def compute_answer(self): + raise NotImplementedError("test code here") + + def test(self, computed, expected): + self.testfun(computed, expected) + + def get_points(self, verbose=False): + possible = 1 + try: + computed = self.compute_answer() + except Exception as e: + print("\n=================================================================================") + print(f"When trying to run test class '{self.name}' your code threw an error:") + print(e) + print("=================================================================================") + import traceback + traceback.print_exc() + return (0, possible) + correct = self._correct_answer_payload + try: + # print(computed, correct) + self.test(computed=computed, expected=correct) + except Exception as e: + print("=================================================================================") + print(f"Test output from test class '{self.name}' does not match expected result. Test error:") + print(e) + print("-------------------------Your output:--------------------------------------------") + print(computed) + print("-------------------------Expected output:----------------------------------------") + print(correct) + print("=================================================================================") + return (0, possible) + return (1, possible) + + def score(self): + try: + self.test() + except Exception as e: + return 0 + return 1 + +class QPrintItem(QItem): + def compute_answer_print(self): + raise Exception("Generate output here") + + def process_output(self, res, txt, numbers): + return txt + + def compute_answer(self): + with Capturing() as output: + res = self.compute_answer_print() + s = "\n".join(output) + numbers = extract_numbers(s) + return self.process_output(res, s, numbers) + +class OrderedClassMembers(type): + @classmethod + def __prepare__(self, name, bases): + return collections.OrderedDict() + def __new__(self, name, bases, classdict): + classdict['__ordered__'] = [key for key in classdict.keys() if key not in ('__module__', '__qualname__')] + return type.__new__(self, name, bases, classdict) + +class QuestionGroup(metaclass=OrderedClassMembers): + title = "Graph search" + items = None + partially_scored = False + + def __init__(self, *args, **kwargs): + self.name = self.__class__.__name__ + if self.items is None: + self.items = [] + members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__] if inspect.isclass(gt) and issubclass(gt, QItem)] + for gt in members: + self.items.append( (gt, 1) ) + self.items = [(I(), w) for I, w in self.items] + +class Report(): + title = "report title" + questions = [] + pack_imports = [] + + def __init__(self): + working_directory = os.path.abspath(os.path.dirname(inspect.getfile(type(self)))) + # working_directory = os.path.join(pathlib.Path(__file__).parent.absolute(), "payloads/") + self.wdir, self.name = setup_dir_by_class(self, working_directory) + self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat") + # print(self.computed_answers_file) + self.questions = [(Q(working_directory=self.wdir),w) for Q,w in self.questions] + if os.path.isfile(self.computed_answers_file): + self.set_payload(cache_read(self.computed_answers_file)) + + def set_payload(self, payloads): + for q, _ in self.questions: + for item, _ in q.items: + if q.name not in payloads or item.name not in payloads[q.name]: + continue + item._correct_answer_payload = payloads[q.name][item.name]['payload'] + +def extract_numbers(txt): + import re + numeric_const_pattern = '[-+]? (?: (?: \d* \. \d+ ) | (?: \d+ \.? ) )(?: [Ee] [+-]? \d+ ) ?' + rx = re.compile(numeric_const_pattern, re.VERBOSE) + all = rx.findall(txt) + all = [float(a) if '.' in a else int(a) for a in all] + return all + + +import numpy as np +from tabulate import tabulate +from datetime import datetime +import pickle +import pyfiglet + +# from unitgrade.unitgrade import Hidden +# import unitgrade as ug +# import unitgrade.unitgrade as ug +import inspect +import os +import argparse + + +parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: +To run all tests in a report: + +> python report1.py + +To run only question 2 or question 2.1 + +> python report1.py -q 2 +> python report1.py -q 2.1 + +Note this scripts does not grade your report. To grade your report, use: + +> python report1_grade.py + +Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful. +As an example, suppose the report file is Documents/course_package/report1.py, and `course_package` is a python package. Change directory to 'Documents/` and execute: + +> python -m course_package.report1 + +see https://docs.python.org/3.9/using/cmdline.html +""") +parser.add_argument('-q', nargs='?', type=str, default=None, help='Only evaluate this question (example: -q 2)') +parser.add_argument('--verbose', nargs='?', type=bool, default=False, help='Show expected output') +# parser.add_argument('integers', metavar='N', type=int, nargs='+', +# help='an integer for the accumulator') +# parser.add_argument('--sum', dest='accumulate', action='store_const', +# const=sum, default=max, +# help='sum the integers (default: find the max)') + + + +def evaluate_report_student(report, question=None, qitem=None): + args = parser.parse_args() + if question is None and args.q is not None: + question = args.q + if "." in question: + question, qitem = [int(v) for v in question.split(".")] + else: + question = int(question) + results, table_data = evaluate_report(report, question=question, qitem=qitem, verbose=args.verbose) + if question is None: + print("Provisional evaluation") + tabulate(table_data) + table = table_data + print(tabulate(table)) + print(" ") + print("Note your results have not yet been registered. \nTo register your results, please run the file:") + + fr = inspect.getouterframes(inspect.currentframe())[1].filename + print(">>>", os.path.basename(fr)[:-3] + "_grade.py") + print("In the same manner as you ran this file.") + return results + + +def upack(q): + # h = zip([(i['w'], i['possible'], i['obtained']) for i in q.values()]) + + h =[(i['w'], i['possible'], i['obtained']) for i in q.values()] + h = np.asarray(h) + return h[:,0], h[:,1], h[:,2], + # + # ws, possible, obtained = (np.asarray(x).squeeze() for x in zip([(i['w'], i['possible'], i['obtained']) for i in q.values()])) + # return ws, possible, obtained + +def evaluate_report(report, question=None, qitem=None, verbose=False): + now = datetime.now() + ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom") + b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] ) + print(b + " v" + __version__) + dt_string = now.strftime("%d/%m/%Y %H:%M:%S") + print("Started: " + dt_string) + print("Evaluating " + report.title, "(use --help for options)") + print("") + table_data = [] + nL = 80 + + score = {} + for n, (q, w) in enumerate(report.questions): + q_hidden = issubclass(q.__class__, Hidden) + if question is not None and n+1 != question: + continue + + print(f"Question {n+1}: {q.title}") + print("="*nL) + q.possible = 0 + q.obtained = 0 + + q_ = {} # Gather score in this class. + for j, (item, iw) in enumerate(q.items): + if qitem is not None and question is not None and item is not None and j+1 != qitem: + continue + + ss = f"*** q{n+1}.{j+1}) {item.title}" + el = nL-4 + if len(ss) < el: + ss += '.'*(el-len(ss)) + hidden = issubclass(item.__class__, Hidden) + if not hidden: + print(ss, end="") + (current, possible) = item.get_points() + q_[j] = {'w': iw, 'possible': possible, 'obtained': current, 'hidden': hidden} + # q.possible += possible * iw + # q.obtained += current * iw + if not hidden: + if current == possible: + print(f"PASS") + else: + print(f"*** FAILED") + + ws, possible, obtained = upack(q_) + possible = int(ws @ possible) + obtained = int(ws @ obtained) + obtained = myround(np.floor((w * obtained) / possible )) if possible > 0 else 0 + score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'ítems': q_, 'hidden': q_hidden} + + q.obtained = obtained + q.possible = possible + + s1 = f"*** Question q{n+1}" + s2 = f" {q.obtained}/{w}" + print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 ) + print(" ") + table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"]) + + ws, possible, obtained = upack(score) + possible = int( sum(possible) ) + obtained = int( sum(obtained) ) # Cast to python int + report.possible = possible + report.obtained = obtained + now = datetime.now() + dt_string = now.strftime("%H:%M:%S") + print(f"Completed: "+ dt_string) + table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ]) + results = {'total': (obtained, possible), 'details': score} + return results, table_data + + +from tabulate import tabulate +from datetime import datetime +import inspect +import json +import os +import bz2 +import pickle + +def bzwrite(json_str, token): # to get around obfuscation issues + with getattr(bz2, 'open')(token, "wt") as f: + f.write(json_str) + +def gather_upload_to_campusnet(report, output_dir=None): + n = 80 + results, table_data = evaluate_report(report) + print(" ") + print("="*n) + print("Final evaluation") + print(tabulate(table_data)) + results['sources'] = {} + print("Gathering files...") + for m in report.pack_imports: + with open(m.__file__, 'r') as f: + results['sources'][m.__name__] = f.read() + print(f"*** {m.__name__}") + + results['sources'] = {} + json_str = json.dumps(results, indent=4) + if output_dir is None: + output_dir = os.getcwd() + + payload_out_base = report.__class__.__name__ + "_handin" + + obtain, possible = results['total'] + token = "%s_%i_of_%i.token"%(payload_out_base, obtain, possible) # + str(obtained) +"_" + ".token" + token = os.path.join(output_dir, token) + bzwrite(json_str, token) + + print(" ") + print("To get credit for your results, please upload the single file: ") + print(">", token) + print("To campusnet without any modifications.") + +def source_instantiate(name, report1_source, payload): + eval("exec")(report1_source, globals()) + report = eval(name)() + pl = pickle.loads(payload) + report.set_payload(pl) + return report + +report1_source = '__version__ = "0.0.4"\nimport os\nimport compress_pickle\n\ndef cache_write(object, file_name, verbose=True):\n dn = os.path.dirname(file_name)\n if not os.path.exists(dn):\n os.mkdir(dn)\n if verbose: print("Writing cache...", file_name)\n with open(file_name, \'wb\', ) as f:\n compress_pickle.dump(object, f, compression="lzma")\n if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n # file_name = cn_(file_name) if cache_prefix else file_name\n return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n # file_name = cn_(file_name) if cache_prefix else file_name\n if os.path.exists(file_name):\n with open(file_name, \'rb\') as f:\n return compress_pickle.load(f, compression="lzma")\n # return pickle.load(f)\n else:\n return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push && pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\nimport unittest\nimport numpy as np\nimport os\nfrom io import StringIO\nimport sys\nimport collections\nimport inspect\n\nmyround = lambda x: int(x) # required.\n\ndef setup_dir_by_class(C,base_dir):\n name = C.__class__.__name__\n # base_dir = os.path.join(base_dir, name)\n # if not os.path.isdir(base_dir):\n # os.makedirs(base_dir)\n return base_dir, name\n\nclass Hidden:\n def hide(self):\n return True\n\nclass Capturing(list):\n def __enter__(self, capture_errors=True):\n self._stdout = sys.stdout\n sys.stdout = self._stringio = StringIO()\n if capture_errors:\n self._sterr = sys.stderr\n sys.sterr = StringIO() # memory hole it\n self.capture_errors = capture_errors\n return self\n\n def __exit__(self, *args):\n self.extend(self._stringio.getvalue().splitlines())\n del self._stringio # free up some memory\n sys.stdout = self._stdout\n if self.capture_errors:\n sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n title = None\n testfun = unittest.TestCase.assertEqual\n tol = 0\n\n def __init__(self, working_directory=None, correct_answer_payload=None, *args, **kwargs):\n self.name = self.__class__.__name__\n self._correct_answer_payload = correct_answer_payload\n super().__init__(*args, **kwargs)\n if self.title is None:\n self.title = self.name\n\n def assertL2(self, computed, expected, tol=None):\n if tol == None:\n tol = self.tol\n diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n if np.max(diff) > tol:\n print("Not equal within tolerance {tol}")\n print(f"Element-wise differences {diff.tolist()}")\n self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n def assertL2Relative(self, computed, expected, tol=None):\n if tol == None:\n tol = self.tol\n diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n if np.sum(diff > tol) > 0:\n print(f"Not equal within tolerance {tol}")\n print(f"Element-wise differences {diff.tolist()}")\n self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n def compute_answer(self):\n raise NotImplementedError("test code here")\n\n def test(self, computed, expected):\n self.testfun(computed, expected)\n\n def get_points(self, verbose=False):\n possible = 1\n try:\n computed = self.compute_answer()\n except Exception as e:\n print("\\n=================================================================================")\n print(f"When trying to run test class \'{self.name}\' your code threw an error:")\n print(e)\n print("=================================================================================")\n import traceback\n traceback.print_exc()\n return (0, possible)\n correct = self._correct_answer_payload\n try:\n # print(computed, correct)\n self.test(computed=computed, expected=correct)\n except Exception as e:\n print("=================================================================================")\n print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n print(e)\n print("-------------------------Your output:--------------------------------------------")\n print(computed)\n print("-------------------------Expected output:----------------------------------------")\n print(correct)\n print("=================================================================================")\n return (0, possible)\n return (1, possible)\n\n def score(self):\n try:\n self.test()\n except Exception as e:\n return 0\n return 1\n\nclass QPrintItem(QItem):\n def compute_answer_print(self):\n raise Exception("Generate output here")\n\n def process_output(self, res, txt, numbers):\n return txt\n\n def compute_answer(self):\n with Capturing() as output:\n res = self.compute_answer_print()\n s = "\\n".join(output)\n numbers = extract_numbers(s)\n return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n @classmethod\n def __prepare__(self, name, bases):\n return collections.OrderedDict()\n def __new__(self, name, bases, classdict):\n classdict[\'__ordered__\'] = [key for key in classdict.keys() if key not in (\'__module__\', \'__qualname__\')]\n return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n title = "Graph search"\n items = None\n partially_scored = False\n\n def __init__(self, *args, **kwargs):\n self.name = self.__class__.__name__\n if self.items is None:\n self.items = []\n members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__] if inspect.isclass(gt) and issubclass(gt, QItem)]\n for gt in members:\n self.items.append( (gt, 1) )\n self.items = [(I(), w) for I, w in self.items]\n\nclass Report():\n title = "report title"\n questions = []\n pack_imports = []\n\n def __init__(self):\n working_directory = os.path.abspath(os.path.dirname(inspect.getfile(type(self))))\n # working_directory = os.path.join(pathlib.Path(__file__).parent.absolute(), "payloads/")\n self.wdir, self.name = setup_dir_by_class(self, working_directory)\n self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n # print(self.computed_answers_file)\n self.questions = [(Q(working_directory=self.wdir),w) for Q,w in self.questions]\n if os.path.isfile(self.computed_answers_file):\n self.set_payload(cache_read(self.computed_answers_file))\n\n def set_payload(self, payloads):\n for q, _ in self.questions:\n for item, _ in q.items:\n if q.name not in payloads or item.name not in payloads[q.name]:\n continue\n item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n\ndef extract_numbers(txt):\n import re\n numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n rx = re.compile(numeric_const_pattern, re.VERBOSE)\n all = rx.findall(txt)\n all = [float(a) if \'.\' in a else int(a) for a in all]\n return all\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pickle\nimport pyfiglet\n\n# from unitgrade.unitgrade import Hidden\n# import unitgrade as ug\n# import unitgrade.unitgrade as ug\nimport inspect\nimport os\nimport argparse\n\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python report1.py\n\nTo run only question 2 or question 2.1\n\n> python report1.py -q 2\n> python report1.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nAs an example, suppose the report file is Documents/course_package/report1.py, and `course_package` is a python package. Change directory to \'Documents/` and execute:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""")\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (example: -q 2)\')\nparser.add_argument(\'--verbose\', nargs=\'?\', type=bool, default=False, help=\'Show expected output\')\n# parser.add_argument(\'integers\', metavar=\'N\', type=int, nargs=\'+\',\n# help=\'an integer for the accumulator\')\n# parser.add_argument(\'--sum\', dest=\'accumulate\', action=\'store_const\',\n# const=sum, default=max,\n# help=\'sum the integers (default: find the max)\')\n\n\n\ndef evaluate_report_student(report, question=None, qitem=None):\n args = parser.parse_args()\n if question is None and args.q is not None:\n question = args.q\n if "." in question:\n question, qitem = [int(v) for v in question.split(".")]\n else:\n question = int(question)\n results, table_data = evaluate_report(report, question=question, qitem=qitem, verbose=args.verbose)\n if question is None:\n print("Provisional evaluation")\n tabulate(table_data)\n table = table_data\n print(tabulate(table))\n print(" ")\n print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n\n fr = inspect.getouterframes(inspect.currentframe())[1].filename\n print(">>>", os.path.basename(fr)[:-3] + "_grade.py")\n print("In the same manner as you ran this file.")\n return results\n\n\ndef upack(q):\n # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n\n h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n h = np.asarray(h)\n return h[:,0], h[:,1], h[:,2],\n #\n # ws, possible, obtained = (np.asarray(x).squeeze() for x in zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]))\n # return ws, possible, obtained\n\ndef evaluate_report(report, question=None, qitem=None, verbose=False):\n now = datetime.now()\n ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n print(b + " v" + __version__)\n dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n print("Started: " + dt_string)\n print("Evaluating " + report.title, "(use --help for options)")\n print("")\n table_data = []\n nL = 80\n\n score = {}\n for n, (q, w) in enumerate(report.questions):\n q_hidden = issubclass(q.__class__, Hidden)\n if question is not None and n+1 != question:\n continue\n\n print(f"Question {n+1}: {q.title}")\n print("="*nL)\n q.possible = 0\n q.obtained = 0\n\n q_ = {} # Gather score in this class.\n for j, (item, iw) in enumerate(q.items):\n if qitem is not None and question is not None and item is not None and j+1 != qitem:\n continue\n\n ss = f"*** q{n+1}.{j+1}) {item.title}"\n el = nL-4\n if len(ss) < el:\n ss += \'.\'*(el-len(ss))\n hidden = issubclass(item.__class__, Hidden)\n if not hidden:\n print(ss, end="")\n (current, possible) = item.get_points()\n q_[j] = {\'w\': iw, \'possible\': possible, \'obtained\': current, \'hidden\': hidden}\n # q.possible += possible * iw\n # q.obtained += current * iw\n if not hidden:\n if current == possible:\n print(f"PASS")\n else:\n print(f"*** FAILED")\n\n ws, possible, obtained = upack(q_)\n possible = int(ws @ possible)\n obtained = int(ws @ obtained)\n obtained = myround(np.floor((w * obtained) / possible )) if possible > 0 else 0\n score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'ítems\': q_, \'hidden\': q_hidden}\n\n q.obtained = obtained\n q.possible = possible\n\n s1 = f"*** Question q{n+1}"\n s2 = f" {q.obtained}/{w}"\n print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n print(" ")\n table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n ws, possible, obtained = upack(score)\n possible = int( sum(possible) )\n obtained = int( sum(obtained) ) # Cast to python int\n report.possible = possible\n report.obtained = obtained\n now = datetime.now()\n dt_string = now.strftime("%H:%M:%S")\n print(f"Completed: "+ dt_string)\n table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n results = {\'total\': (obtained, possible), \'details\': score}\n return results, table_data\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n with getattr(bz2, \'open\')(token, "wt") as f:\n f.write(json_str)\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n n = 80\n results, table_data = evaluate_report(report)\n print(" ")\n print("="*n)\n print("Final evaluation")\n print(tabulate(table_data))\n # also load the source code of missing files...\n results[\'sources\'] = {}\n print("Gathering files...")\n for m in report.pack_imports:\n with open(m.__file__, \'r\') as f:\n results[\'sources\'][m.__name__] = f.read()\n print(f"*** {m.__name__}")\n\n results[\'sources\'] = {}\n json_str = json.dumps(results, indent=4)\n # now = datetime.now()\n # dname = os.path.dirname(inspect.getfile(report.__class__))\n # dname = os.getcwd()\n if output_dir is None:\n output_dir = os.getcwd()\n\n payload_out_base = report.__class__.__name__ + "_handin"\n\n obtain, possible = results[\'total\']\n token = "%s_%i_of_%i.token"%(payload_out_base, obtain, possible) # + str(obtained) +"_" + ".token"\n token = os.path.join(output_dir, token)\n bzwrite(json_str, token)\n\n print(" ")\n print("To get credit for your results, please upload the single file: ")\n print(">", token)\n print("To campusnet without any modifications.")\n\ndef source_instantiate(name, report1_source, payload):\n eval("exec")(report1_source, globals())\n report = eval(name)()\n pl = pickle.loads(payload)\n report.set_payload(pl)\n return report\n\n\n\nclass ListReversalQuestion(QuestionGroup):\n title = "Reversal of list"\n class ListReversalItem(QPrintItem):\n l = [1, 3, 5, 1, 610]\n def compute_answer_print(self):\n from cs101courseware.homework1 import reverse_list\n print(reverse_list(self.l))\n\n class ListReversalWordsItem(ListReversalItem):\n l = ["hello", "world", "summer", "dog"]\n\n class ListReversalWordsItemHidden(ListReversalItem, Hidden):\n l = ["hello", "world", "summer", "dog"]\n\n# class LinearRegressionQuestion(QuestionGroup):\n# title = "Linear regression and Boston dataset"\n# class CoefficientsItem(QPrintItem):\n# testfun = QPrintItem.assertL2\n# tol = 0.03\n#\n# def compute_answer_print(self):\n# from cs101courseware_example.homework1 import boston_linear\n# boston_linear()\n#\n# def process_output(self, res, txt, numbers):\n# return numbers[:-1]\n#\n# class RMSEItem(CoefficientsItem):\n# def process_output(self, res, txt, numbers):\n# return numbers[-1]\n\nclass Report2(Report):\n title = "CS 101 Report 2"\n questions = [(ListReversalQuestion, 5), ]\n pack_imports = [] # Include this file in .token file' +report1_payload = b"\x80\x03}q\x00X\x14\x00\x00\x00ListReversalQuestionq\x01}q\x02(X\x10\x00\x00\x00ListReversalItemq\x03}q\x04X\x07\x00\x00\x00payloadq\x05X \x00\x00\x00[610, 1, 5, 3, 1, 'hi', 'thasd']q\x06sX\x15\x00\x00\x00ListReversalWordsItemq\x07}q\x08h\x05X2\x00\x00\x00['dog', 'summer', 'world', 'hello', 'hi', 'thasd']q\tsX\x1b\x00\x00\x00ListReversalWordsItemHiddenq\n}q\x0bh\x05X2\x00\x00\x00['dog', 'summer', 'world', 'hello', 'hi', 'thasd']q\x0csus." +name="Report2" + +report = source_instantiate(name, report1_source, report1_payload) +output_dir = os.path.dirname(__file__) +gather_upload_to_campusnet(report, output_dir) \ No newline at end of file diff --git a/cs101courseware/deploy_cs101.py b/cs101courseware/deploy_cs101.py index 9416cad5602eaf1258639f856d03a3a4efa96112..ed5c4c0ceb14e2cdf3535a48fc6aa912e87fe7a5 100644 --- a/cs101courseware/deploy_cs101.py +++ b/cs101courseware/deploy_cs101.py @@ -1,15 +1,14 @@ import inspect, os import shutil import unitgrade as ug -from cs101courseware.cs101report1 import Report0 +from cs101courseware.cs101report1 import Report1 from unitgrade_private.hidden_create_files import setup_answers, setup_grade_file_report from unitgrade.unitgrade_helpers import evaluate_report_student if __name__ == "__main__": - report = Report0() - setup_answers(Report0()) # hidden for students; re-computes the answers file. - setup_grade_file_report(Report0, minify=True, bzip=True, obfuscate=True) - evaluate_report_student(Report0()) + setup_answers(Report1()) # hidden for students; re-computes the answers file. + setup_grade_file_report(Report1, minify=True, bzip=True, obfuscate=True) + evaluate_report_student(Report1()) # now copy scripts to public directory dest = os.path.join( os.path.dirname(inspect.getfile(ug)), "../cs101courseware_example") @@ -18,3 +17,12 @@ if __name__ == "__main__": if f == "__pycache__" or f.endswith(".token") or "deploy" in f: continue shutil.copy(f, dest) + + ## Report 2 + from cs101courseware.cs101report2 import Report2 + # from unitgrade_private.hidden_create_files import setup_answers, setup_grade_file_report + # from unitgrade.unitgrade_helpers import evaluate_report_student + setup_grade_file_report(Report2, minify=True, bzip=True, obfuscate=True) + # evaluate_report_student(Report2()) + + diff --git a/cs101courseware/deploy_report2.py b/cs101courseware/deploy_report2.py new file mode 100644 index 0000000000000000000000000000000000000000..5974ad50c561a7c665458b9c8276172fdab5af86 --- /dev/null +++ b/cs101courseware/deploy_report2.py @@ -0,0 +1,29 @@ +import inspect, os +import shutil +import unitgrade as ug +from cs101courseware.cs101report1 import Report1 +from unitgrade_private.hidden_create_files import setup_answers, setup_grade_file_report +from unitgrade.unitgrade_helpers import evaluate_report_student + +if __name__ == "__main__": + # setup_answers(Report1()) # hidden for students; re-computes the answers file. + # setup_grade_file_report(Report1, minify=True, bzip=True, obfuscate=True) + # evaluate_report_student(Report1()) + # + # # now copy scripts to public directory + # dest = os.path.join( os.path.dirname(inspect.getfile(ug)), "../cs101courseware_example") + # source = os.path.dirname(__file__) + # for f in os.listdir(source): + # if f == "__pycache__" or f.endswith(".token") or "deploy" in f: + # continue + # shutil.copy(f, dest) + + ## Report 2 + from cs101courseware.cs101report2 import Report2 + # from unitgrade_private.hidden_create_files import setup_answers, setup_grade_file_report + # from unitgrade.unitgrade_helpers import evaluate_report_student + + setup_grade_file_report(Report2, minify=False, bzip=False, obfuscate=False) + # evaluate_report_student(Report2()) + + diff --git a/cs101courseware/dist/Report2_handin_5_of_5.token b/cs101courseware/dist/Report2_handin_5_of_5.token new file mode 100644 index 0000000000000000000000000000000000000000..a08c292b62bbee54114687e86fb66adda3ebd983 Binary files /dev/null and b/cs101courseware/dist/Report2_handin_5_of_5.token differ diff --git a/cs101courseware/dist/__init__.py b/cs101courseware/dist/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a1ebbeccc271cb92b6db10e6bd006fb2787a84c7 --- /dev/null +++ b/cs101courseware/dist/__init__.py @@ -0,0 +1 @@ +__pyarmor__(__name__, __file__, b'\x50\x59\x41\x52\x4d\x4f\x52\x00\x00\x03\x06\x00\x33\x0d\x0d\x0a\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x40\x00\x00\x00\xad\x00\x00\x00\x00\x00\x00\x10\x78\x6a\x3a\xbd\xc0\x4b\x66\xe8\xda\xc8\xfc\x5a\x8e\x28\xf6\x1a\x00\x00\x00\x00\x00\x00\x00\x00\x65\x84\xea\x32\x96\xe5\xe1\x61\xe0\xed\xcc\x1f\x68\xe0\xa6\x19\xb5\x50\x43\x0d\x9b\x78\x18\x7a\x3d\xa1\x95\xec\xca\x3a\x9f\x55\xf2\x4d\x27\xc3\x91\xec\x37\x2c\xaf\x59\xa6\x5f\xec\xcc\x3f\x4e\x7a\x88\xa4\x0c\x08\xd0\x38\x85\x82\xe2\x96\x0e\xb2\xd7\x33\x78\xe3\x88\x20\x5a\xb5\x58\x58\x1b\x19\x89\xe7\xc4\xfd\xa9\x21\xf5\x3e\x6f\x67\xb8\x65\xed\x51\xa9\xb4\x6d\xf4\x27\x89\x03\x7a\xfc\x9c\xa6\xcb\x3d\x6c\xb6\x3c\x57\x7b\xb5\x17\x16\x3f\x36\x55\xb7\x74\xc1\xe4\xe1\xc2\x32\xf6\x6a\x43\x39\x28\xbe\x4d\x11\xd8\x65\x69\xdf\x73\x18\x7b\xe7\xbd\xd2\x9b\x90\x98\x1a\xb2\x57\x00\x54\x9a\x31\xed\x70\xb6\x77\x20\xef\xa4\x97\x8e\x36\x16\x21\x59\x23\xb3\x93\x15\x69\x16\x49\xf1\xb3\x1c\xa3\x5c\x00\x02', 2) \ No newline at end of file diff --git a/cs101courseware/dist/cs101report1.py b/cs101courseware/dist/cs101report1.py new file mode 100644 index 0000000000000000000000000000000000000000..faaaca396b58d83702fe5f6dca4593738afb4b0e --- /dev/null +++ b/cs101courseware/dist/cs101report1.py @@ -0,0 +1 @@ +__pyarmor__(__name__, __file__, b'\x50\x59\x41\x52\x4d\x4f\x52\x00\x00\x03\x06\x00\x33\x0d\x0d\x0a\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x40\x00\x00\x00\x1e\x0e\x00\x00\x00\x00\x00\x10\x77\x89\xb4\xc8\x07\xe3\xcc\xe2\xbb\xeb\xff\x91\x82\x41\xd1\xab\x00\x00\x00\x00\x00\x00\x00\x00\x57\xb9\x70\xe7\xdb\x7a\xc9\xf6\x5f\x71\x69\xed\xe2\xb5\x54\xf3\x40\x56\x41\xd8\x8f\x44\xbb\x6d\xc8\x85\xcc\x3b\x17\x1b\x0e\xbb\xc3\xc5\xc4\x52\x61\xd7\x4d\x60\xb3\x24\xf7\xee\xeb\xee\x4e\x0a\x94\x53\xba\xf7\x9b\x4e\x02\xee\x81\xe3\xc6\xa6\x92\x12\x11\x92\x3e\x43\xf0\x39\xd6\x91\x05\xbd\x00\xb7\xae\x6f\x53\xce\xdf\x0e\xe8\xe0\x36\xcc\x31\xb1\xfb\xc0\xab\x71\x5f\x74\x2e\x92\xf3\x6d\xb0\x66\xdd\xfa\x6b\x6b\xf8\xd5\x16\xeb\xae\x30\x39\xcb\x26\xaa\xb8\x1a\xea\x8f\x9e\xac\x16\x72\x51\xd3\x22\x80\xdd\xda\xcc\x55\xfe\xfc\x3d\xbd\x5c\x27\x8e\xb5\xc9\x77\xb5\x40\x18\xec\x2e\xfa\xd2\x19\x49\xdd\x47\x83\xe6\x1a\x05\x80\x59\x64\x79\x5b\x07\xdf\x73\x0e\x63\xdf\x38\x3c\x97\xc8\x7b\xd6\xae\x96\xf3\x05\x0a\xf5\x7e\xe2\x65\xfe\x61\x73\x0c\xa7\x3a\x28\xa4\xc2\xbe\x23\x2d\xdf\x75\x05\x71\xe5\x54\x21\x13\xf7\x87\x79\x62\xcc\xda\x1d\xd8\xc5\x6f\x45\x8f\x99\xeb\x3a\x05\x37\x1a\x4b\x87\x76\xa0\x4a\xf9\x0e\x55\xe7\x8e\xe5\x1c\x20\xbe\xbb\xcb\xd4\x6f\xd4\x0a\x72\x8f\xfc\x0a\xc4\x92\x89\xd2\xfd\x67\x85\xe8\x4e\x6f\x56\xe4\x63\xb6\x43\x28\x52\x9f\x05\x16\x7f\x35\xad\xcb\x6e\xee\x6e\x49\x4c\x44\xdf\xc0\x65\xc0\x38\xab\x46\xa6\x0a\x15\x79\xf0\x72\x02\xd7\xa2\x16\x43\xee\xa5\x72\x6f\x84\x0d\xfd\xe9\xfd\x85\xdc\x0d\x4c\xa2\x1f\x6c\xdc\x58\xc7\x6b\xd2\x6f\xdd\x33\xdf\x47\x76\x24\x08\xc7\xb2\x4f\x8b\x55\x15\xd4\x6d\xc8\xcd\xc7\x39\xff\x0a\x38\xd9\x8d\x23\xfa\x5e\x3a\xda\xd7\xea\xc1\x49\x3f\x54\x56\x20\x7b\x3d\x06\x79\xb0\x7f\x5e\xfe\x22\x14\x57\x81\x79\xea\xfb\xd7\x15\x1f\x05\x67\x8c\x7d\xf5\x30\x8b\x0c\x94\x51\x7d\x44\xb2\x36\x5c\x5e\xd1\x7e\x4f\xf5\x97\x13\x7d\xe3\x63\xd0\x06\x57\xcd\x43\x3c\xd0\xbe\x12\x12\x9f\x24\xf0\x4f\x00\x50\xc8\x17\x6f\x80\x7b\xc6\x75\x8f\x96\xd8\xa9\x23\x73\xe9\xc8\xe3\xae\xa3\x4f\x0e\xb0\x1e\x29\x0d\x06\x82\x32\x78\xf3\xbc\x6d\x50\x84\xe4\xe3\xff\xfe\xa5\xbf\xad\x5a\x08\xe7\xae\x84\x91\xac\x88\x0d\xad\xda\xb6\xc3\x59\x4d\xc2\x69\x03\xfe\x4d\xd5\xed\x59\xd9\xca\x89\x59\x2a\x50\xf5\x0f\x22\x81\xf9\x32\x90\x3d\xd8\x0d\x46\xce\x5d\x6f\xe4\x04\xf7\x5d\x76\x15\x5b\xb7\xa9\x1b\xea\xc4\x1a\xcf\x5c\x10\x23\xe0\xa2\x85\xa1\xbe\x06\x62\xeb\xf7\x36\x3f\xd4\xc8\x19\x4e\xa5\xc9\x01\x1c\xf5\x8e\xa3\x60\xef\xb9\xf5\x32\xdc\xd8\xb9\x21\x83\x34\x29\x1b\xbd\x2a\x4f\xac\xe5\x4b\xb1\xf7\xf0\xae\x02\xa2\x2e\xc9\xd9\x19\x91\x0b\x86\x99\x75\xf9\xef\x0c\xe4\x0a\x25\xd8\xde\x2e\x42\x7f\x8e\x6f\x35\xde\x72\x15\x87\xac\x1e\x96\x20\x4c\x94\x7a\x75\x87\xc2\x3b\x5d\x43\x0e\xa5\x5a\x1f\xdd\x2a\xf1\xf4\x27\xe5\x01\xea\xec\x4a\x17\xc3\x7c\xbf\xc4\xc5\xb8\x64\x5f\xbb\x11\x7d\x31\x2e\xdd\x66\x4d\x91\x7f\x4a\x0e\xf0\x56\x43\x4f\x70\xfb\x97\xcc\x11\x73\x8a\xf4\x71\xc4\xfc\xc7\x45\x10\x2b\xb0\x3f\xb5\x69\x6d\x41\x19\x83\x15\x5a\x31\x39\x68\xf4\x9a\xa1\x89\x62\x56\x91\xa7\xcd\x42\x12\x0b\xba\x1c\xc0\x30\xb8\xc4\xc2\xf2\x98\x17\x33\x59\x03\x21\x84\x1b\xf2\xf3\x7d\xac\xfb\x0f\x3b\x81\xeb\x05\xe7\xba\x8a\x59\xed\xdb\x2b\x27\x39\x5f\xd6\x0e\x4a\x36\x91\xe5\x34\x63\x7a\x27\xf6\x61\x13\xba\x06\x31\xef\x4e\x37\x15\xcb\x3c\x44\xd2\xd1\xa6\x66\xa2\x42\xa7\xdf\xc5\xe8\xf5\x64\xfc\x07\x44\x70\x3f\xac\x0e\x4a\x4c\x13\xea\x34\xb9\xec\x0a\xb3\x0e\x51\xc0\x2c\xe1\xc7\x43\xb3\x5f\x01\x69\xef\xef\x56\x80\xf9\x7a\x93\xdc\xfa\x48\x8e\x1d\x51\x3a\x29\xb6\x27\xe0\x5f\x9f\x8b\x14\xac\xb9\xa3\xaa\xb7\xdd\x64\x6d\x99\x75\xb0\xeb\x63\x98\x09\x8e\x27\x69\x65\x2a\x5c\x9b\xbf\xe6\x3b\x6f\xac\x28\x22\xb9\x6b\xb2\xd5\x1a\x1a\xe3\x3e\x08\xb4\xdc\xce\x7f\xcb\x26\xcf\x24\x59\xe9\x56\x4d\x65\xd8\x3b\x58\xca\xec\x21\x44\x5e\x65\xdd\x29\xe2\xc9\xe1\x88\x49\xdb\x90\x1b\x75\x2d\x63\x2f\xe0\x3a\xce\x1c\x3f\x5b\x2c\xba\x23\x9b\xc4\xf7\xb8\xf3\x25\x81\x47\x51\xad\xed\xa4\xae\xeb\x50\xc9\x2e\x87\x2b\xed\xf0\x40\xfc\x64\xae\x71\x03\xd8\x6a\x77\xe8\x59\xbb\x7d\x16\xb2\x95\xce\xaf\x4f\x1c\xe9\x97\x19\xe9\xf9\x3a\xe5\xc7\x8d\xb3\x60\xb2\x88\xff\xaa\x6c\x55\xa8\xe7\xb8\x1a\xf7\x4f\xc2\xf9\x83\xc0\x55\xc9\x2c\xed\x42\x5f\x67\x28\x1a\xf8\x82\x5c\x57\x58\x96\x96\xb8\xec\x34\xac\x0b\xc8\x0d\x86\x74\xe7\x0c\x3b\x8e\x1a\x26\x85\x8e\xd2\xaf\x49\x50\x66\x6e\xbe\x8b\x07\xea\x5d\x7f\xc3\x14\xb3\x12\x8e\x27\x3f\xc8\x57\x09\xd6\xe4\x21\x74\x48\x4b\x0c\x21\x5d\xde\x9c\x94\x7c\x53\x67\x09\x9f\xf6\x69\x83\x78\x8b\xbb\xc7\x79\x40\xef\xbd\x42\x96\xa3\x5f\x3f\xc4\xff\x67\x23\x20\x13\x3a\x5f\x8e\xe4\x3d\x29\xc3\xdd\xa9\x39\x44\x28\x0b\x6a\x89\x37\x4a\x1b\x78\x04\x98\x27\xfb\x1a\x88\x58\xa7\xae\xac\x35\x69\xbd\x42\x52\x76\x65\x1a\x97\xe4\x49\xcc\xb2\x7f\x9f\x2b\xd6\x3f\xdd\xba\xfd\x03\xea\x5f\x05\xaf\x03\x81\x52\xc1\x49\x6f\x6e\x25\x56\xcb\x86\x8d\x47\xa5\xb5\x67\x26\x62\xc7\x93\xd3\xc1\x55\xe5\x8d\x4c\xfa\xd2\xe4\xbd\xc8\x1b\xb0\x45\xfe\x9e\xa7\x91\x2f\x9e\xcf\x4f\x28\x53\xb9\xc8\x52\x06\xf9\x8a\x64\x1d\x23\xb4\x9f\xaa\x7c\x0e\xee\x4f\xb7\x70\x45\x2c\xaf\x40\xc0\xc8\xf6\x1e\xd9\x0d\xd8\xdf\x1e\x0b\x7b\x89\xb1\xeb\x4e\x5c\x0c\x08\xb2\xbe\x57\x60\x12\x4a\x9a\x37\xcb\xf0\xea\xe3\x81\x06\x0f\xc3\xb3\xc8\xb0\xc7\xa8\xa5\xbb\x36\xc0\x23\xfa\x07\xd8\x27\x63\xde\xa4\x6f\xb2\xc6\xe0\x96\xf8\x05\xa0\xca\x8d\x0f\x17\x57\x8c\xbc\x24\xd0\x39\x41\x6d\xa2\x64\x15\xf8\x1b\x2c\x86\x71\xe9\x6c\xb9\x6b\x13\xd8\x01\xc7\x48\x26\x3f\x2a\xde\xc9\xba\x00\x7a\x85\x3a\xdd\x43\x95\xe0\x2e\x12\x9e\x2c\xe4\x69\xab\x2e\x41\x76\xd7\xe4\x93\x6b\x96\xdf\xcc\xca\xb0\xd5\x66\xc5\x79\x21\x27\x51\x43\x0a\x76\xbd\x7f\x2d\xd4\x3d\xcf\xf7\x3a\x6c\x5d\xc0\xb8\x54\x56\x51\xd3\x84\xa4\xb7\xa7\xa6\xee\x4c\xf6\x69\x8a\xf9\x4f\x20\x86\x5c\x7c\x47\x1e\x89\xae\x56\x67\x2b\x44\xb7\xc7\xac\x06\xe2\x58\x2d\xe9\xe5\x55\xc5\xa8\x05\x63\x63\x2e\xd6\x45\x9f\xf0\xe0\xdb\x16\x9e\x4d\x13\xd3\x0a\x08\x5e\x8a\xb7\xae\x98\x74\x06\x3b\xee\x5b\x3c\x9d\xe1\xbb\xd5\x52\xe4\x31\xca\x9a\xf9\xcf\x6c\x75\xe0\x5e\x65\x18\xae\x3e\xf2\xcd\x7c\x40\x03\x27\x53\xc2\x1e\x60\x8b\x21\xf2\x00\x27\x0f\x0e\xe8\x9e\xdf\x40\xf7\xff\x20\xff\x33\x48\xdc\xa1\x80\xed\xa7\x1a\xc3\xe4\xf7\xcc\xf4\xd7\x41\x97\xbb\xde\xdc\x2c\xf0\x6e\x74\x3b\x34\x34\x94\x29\x5e\x07\x68\x76\xd0\xd3\x19\xdc\xfd\x05\x19\x18\x72\x82\x5a\x5d\xb0\xa1\xaa\x67\x58\x74\xcf\xa2\x2b\x3e\xdb\xd7\x3a\x19\x96\x75\xbb\x73\x90\xc5\x5f\x8b\xde\x06\xe5\x50\x8b\x70\x95\xa9\x24\x79\x14\xff\xef\x9a\x6b\x71\x51\xc6\xc3\xaf\x5c\xff\x4d\xe4\x95\x6f\x66\x05\xe1\x9e\x31\x0a\x45\x05\xa4\x3e\x72\x71\x81\x9d\x42\x5f\x6a\xf5\xad\xb0\xfe\x7c\x3b\xe0\xc5\x8f\xf3\xae\xfb\x0e\x5d\x0b\xe3\x2f\xa1\xb8\x53\xc0\x68\x7d\xe9\x8f\x5f\xe2\xd9\xe7\xe1\xb3\x18\xd9\xa9\x98\x6b\x16\x51\x5e\x51\xad\xce\xca\xdc\x71\x09\x54\xc2\xb0\x69\x58\x96\x04\x77\xb3\x63\x96\x82\x95\x7d\x6b\xf2\x3c\xac\xb3\x38\xbb\x09\xf7\xfe\x71\x71\xac\x90\x5c\x59\x0e\x00\x28\x8b\xd4\x6c\x0f\x80\xf4\xbe\x0a\x27\x1b\x26\x74\x6a\xc2\xa8\xa5\xc4\x3b\x80\x59\xc1\x40\xc7\xea\xdf\x7d\xad\x9d\xbb\x89\xed\x6c\xea\x5d\x50\x23\x21\x36\xc1\x54\x90\xba\x18\xff\x09\xd0\x31\xea\xbc\x37\x05\x20\x31\x20\xf6\x22\xe0\xf5\x72\x52\x1a\x91\x3f\x84\x41\x57\x6d\x47\x2d\x27\x88\x38\x3d\x96\xdc\x8e\xf3\xc6\x0c\xb5\xd4\x33\x86\xb3\xce\xdf\x9d\xe7\x8c\x2c\x4f\x72\x73\x7c\x73\xfe\x63\xfb\x98\x79\xd3\x81\xc9\xdc\x3a\x24\x63\x69\xa0\x37\x5b\x1a\x03\x38\x6d\x42\x83\xb6\x1c\x31\xf2\xea\xc7\x70\xb9\x3f\xe5\xb2\x23\x34\xdd\xc3\x9b\x0a\x15\xf5\xb8\x81\xc2\x5b\x27\x1f\xc0\xdc\xec\xf9\x2a\x92\x27\x34\x51\xa7\x2e\x43\xb1\x0e\xb5\xd4\xe7\x4e\xb0\xed\xd9\x5e\xac\x4e\x64\xde\x19\x74\x33\xcb\x7f\x0a\xb4\x72\x6f\x74\x1e\x35\x29\x71\x1a\xd0\xa0\x1d\x5b\xa8\x79\x0b\x34\xcc\x46\x8f\x91\x58\x91\x32\x2f\xed\xf0\x91\xc3\x3f\x7f\x26\x40\xa2\x4c\x50\x77\x9b\x11\x8f\x72\x25\x4c\xa8\x0f\x2b\x2f\x44\x36\xd4\x8b\x79\x73\x97\xea\x55\xb4\xff\xc0\x2f\xcd\x54\xc4\x90\xaa\x19\xc1\xce\x54\x98\xc7\xa6\x48\x48\xfc\xac\xf5\x4e\x48\xe1\x59\x98\x84\x51\xd3\x80\x51\x74\xb7\x58\xe7\x88\x0b\xd2\x48\x82\x58\xd4\xb6\x27\xeb\x60\xc5\xed\xc0\x2a\xe5\x06\xbc\xcd\xac\x65\xd4\x70\xd0\x86\x3c\x89\x90\xb6\x2f\x39\xd9\xad\x89\xca\xcb\x84\xe2\x11\x53\x03\x85\x98\x4b\x5c\xc4\x11\x40\x91\xe2\xef\x28\x78\xe1\x5e\x73\x48\x9b\xb4\x45\x4d\x87\xce\x63\x91\x62\x35\x0f\x45\x8a\x6f\xdb\xfd\x4a\xd1\x62\x54\x7d\x68\xf4\x71\x82\x58\x02\x84\x64\x24\x8a\xbb\x35\xe1\x42\x04\xa4\x65\x12\xca\xe0\x6e\x64\x25\xf0\x73\x96\x41\xbd\x3d\xa0\xea\x00\xc7\x98\xda\xbd\xc8\x4f\x0a\x4e\xa0\x3f\x69\xe9\xcd\x47\x54\xfb\x06\xc4\x62\x24\x58\x54\x9a\xfb\xb6\x8f\xe2\x2c\xa6\x7d\x6c\xa6\xf5\x43\x7d\x31\x5c\x9a\x96\x6d\xa4\xc3\x14\x06\x00\x54\x85\x27\x7f\x8f\x6b\xef\xe9\x98\x28\x93\xa1\x8f\xc2\x53\x4d\x11\x85\x97\xff\xee\x55\x6c\x3c\x57\x7a\x52\xc5\x1b\x58\xc6\xbd\x4c\xf5\xb6\x21\x89\x68\x80\xd4\x13\x6e\xee\xaf\xe4\xfa\x37\x48\x52\xde\x4a\x84\x4e\x53\x3b\x7a\x7c\x44\x5b\x9e\xab\xda\xd0\x4a\x2c\xf9\xf7\x69\xfd\xde\x0b\xb5\x9b\x97\xc6\x8f\xe1\xe0\xb7\x27\x37\xb7\xa6\xdc\xd1\x94\xe1\xee\xd1\xd3\x64\x49\x6a\xdf\x16\x5c\xca\x31\x37\x19\x64\x3d\x73\x51\xb5\xfc\xe4\x55\x61\xfc\xfb\x1b\xfe\x4a\x73\x69\xae\x9c\xd4\x39\xa9\x8a\xde\xbd\x11\xe5\xf5\x29\x74\xe2\xe5\x84\xa8\x5f\xc1\x2e\x81\xbc\x36\x08\x6d\x4d\x8e\x40\x6d\x76\x01\x70\xd5\x87\xd0\xcc\x3c\xc6\x45\xdb\x30\x08\x8a\x5e\xf7\x0a\xf6\x67\xa8\xfc\xed\x08\xe6\x37\x4f\x47\x8f\xd6\xed\x71\xce\xbd\x08\x8c\x13\x3d\x9c\xda\x6f\x83\x18\x7c\x11\x2c\x15\x0c\xfd\x95\xf5\x4a\x22\x72\xeb\xb5\xb6\xfd\x51\xc6\x69\x1b\xef\xa5\x2c\x59\x0d\xf2\xe6\x32\x5f\x5d\xb2\x47\xf4\x55\x52\xe7\x34\x39\x41\x10\x10\x72\xed\xf5\xa6\xb9\x11\x45\x28\xdb\x9d\x65\x19\x5b\x24\x5a\x68\x7e\xd1\xec\x62\x99\x4c\xa6\xb8\x34\xf6\x31\xab\x71\xbc\x6c\x1f\x9b\x02\xd0\x11\x98\xa3\x8f\x04\x95\x80\xd9\xdc\xcb\x7c\x7a\x6f\x38\x50\xd9\xd8\x87\xc5\x33\xa7\xc0\xcd\x8f\xf9\xdb\x56\x4b\x76\x96\x79\x77\x54\x0c\xdd\xf6\x75\x27\xf9\xce\x4a\x27\x1f\x48\x7d\xbb\x95\x89\xe3\x60\xc7\x4a\x70\x2a\x37\x6f\xe2\x65\x7f\x31\xd9\xc3\x71\x4b\x1a\x77\xdb\x06\x77\x6b\x14\xa6\x41\x56\x4f\x76\x64\x07\x04\x50\xe6\x1f\x5d\xa0\x5d\x94\x93\xd1\xf2\x2b\xf1\x7a\x3e\xdb\x1e\xd2\x0d\xd4\x48\x93\x55\x5f\xb0\xe1\xc4\xf7\xe3\x7d\xd0\xe9\x7c\x41\xd1\x94\x2a\x36\x78\x67\x9f\xa7\xbc\x7f\x11\xbf\x02\x3d\x84\xcb\x4d\xa0\x16\xcd\x2f\xc1\xda\x59\x44\x13\x3f\x83\xd9\x47\x21\x9b\x9b\xb2\xbb\x24\x7f\x0e\x76\x80\xff\x9e\xa5\x02\x41\xa3\xf2\x69\x2b\x0b\xb1\x69\xc3\x8b\xad\x3e\xa3\xc5\x25\xd3\xb3\x20\x8e\xd3\x65\x09\xf8\xf8\x6f\x44\x2e\xec\xc2\x17\xb3\xd9\xd3\x89\xce\xac\x3f\x36\xbe\x9f\x00\x6b\x5d\x7c\x66\xfd\xea\xb7\xdd\x1d\xf1\xf9\xc1\xa7\x59\x2a\x4b\x68\xe2\x97\xca\x30\x41\x94\x1b\x32\x5c\x19\x28\xa5\xee\x28\x6c\x27\xf1\x46\x20\x77\x44\x6c\x97\x30\x73\x01\xa6\xbb\x93\x0e\xad\xae\x7a\x83\x49\xc7\xd3\x28\x06\xcc\x13\x79\x12\xba\x42\xd6\xfd\x36\x8c\x3f\xb7\x82\x19\xbd\x0b\xa7\x43\xa1\x14\x70\x18\x02\x8d\xff\xc1\xec\x6d\x63\xe7\x6e\xbf\x5f\x61\x2e\x72\x6e\xb2\x78\xe9\x92\xb8\xca\xaa\x26\xd5\x84\x9e\xbd\xcf\x5a\x3d\x70\x03\xdb\xba\x67\xfe\x7d\x6e\x31\xdc\xa4\x2a\x0d\x59\x07\xdb\xad\x1d\xce\x28\x8d\x87\x3e\x7a\x01\x2d\x88\x9e\xc3\x87\x2b\x4c\xec\xeb\xac\x5e\x72\xc9\x0e\x2e\xbd\x42\x97\x71\x8f\x30\x41\x47\x42\x99\x5e\x63\xe1\x0a\x8e\xcc\x68\x1e\xc0\x0b\xef\x72\x90\x9e\xe7\x06\xba\x41\x3f\xb2\xbf\x99\x02\x1b\xc4\x73\xae\x40\xcb\x50\x68\x49\xfd\x90\x26\xc1\xda\x14\xa2\x9d\x59\x7b\x24\x3d\x3e\x95\xe9\x7a\x6c\xbb\x04\xff\x2e\x44\x68\xbe\xd1\x24\x4e\xf5\xfc\xee\x0a\x43\x2a\x4c\xfc\x35\xae\xb8\x33\xd4\x69\xf9\x4b\x2a\x71\x2e\x7d\x63\xb8\x09\xb9\xb9\x00\x95\x9f\x09\x35\xc1\x64\x9c\x6a\x2a\x05\x36\x81\x38\x02\x69\xdc\xf7\xc1\xd3\xde\xb6\x36\x3f\x26\x71\x84\xe3\xa6\x07\x8a\x49\xd8\x22\xe9\x40\x11\x44\x44\x3e\x91\x49\x60\xf2\xda\x11\xfe\xc0\x1b\xfc\xc2\x26\x0e\xa1\xee\x9e\x46\x6c\x59\x67\x23\x28\x89\x7f\x65\x0c\x94\x34\x06\x9d\x79\x7e\x02\x9c\x88\x18\x3b\x16\x8c\x5e\x8d\x60\x30\x5d\x9d\xbc\x6c\x06\x9c\x95\xc7\x9d\x48\xe9\x60\x1d\x38\xc1\x95\xbb\x1d\xa1\x94\x07\x4a\xf9\xb8\x62\xe7\x77\xfd\x66\xdc\x2e\xd6\xa8\xc1\xa0\x85\x11\xff\x18\xee\x59\xc4\xfe\x0a\x72\x61\xa1\x63\x92\xe0\x0d\x02\xbc\x78\x45\x68\xfa\x0a\x7b\x1c\xcc\xa8\x49\x72\xbd\xc9\x2c\xde\xac\xe1\x62\x92\xc4\xc7\x93\x72\x6e\x83\xe9\x9f\x41\x0a\x09\xff\xa0\x3a\xf3\x74\xa0\x29\xe3\x3c\xf0\xad\x77\xb4\xaa\x93\xf5\xb5\xd8\x0d\xe3\x9d\x2e\x93\x42\x60\x5d\xba\x3b\x2b\x59\x50\x30\x15\x47\xf2\x90\x5a\xfd\x2d\xcc\x04\x90\x9e\x11\x6c\x4b\xb2\x87\xf2\xd3\x02\x4d\x88\xc5\xba\x46\xbe\xc2\x32\x0c\x52\x67\xdf\x0c\x13\xbc\x87\xc7\x0f\x22\x36\xa3\xc1\x8f\x71\x6a\xa2\xec\x53\x36\xd1\x2a\xf1\xeb\x9b\xad\xc5\x95\xc9\xfa\x5e\x72\x96\x6a\x7d\x5c\x65\xf9\xd0\x54\xf4\xd8\x4f\x45\x00\xee\x96\xa5\x91\x9c\x16\xe9\x60\xd0\x72\x99\xdb\x61\x99\x8d\xc9\xaf\xb9\xc0\x2b\x2b\x35\xf7\x43\x40\xf6\x0a\xff\x8b\x40\x81\xde\xe1\xf5\xc9\x24\x32\x0f\x18\xb8\xd4\x22\xe7\x09\x52\x37\x66\x05\x2e\x24\xa2\x86\x72\x42\xf7\x81\x9f\xe5\xec\x23\x4e\x32\x9f\xd2\x30\x11\xd4\x74\x6e\x30\x0a\xe7\x04\x7a\x9b\xdb\xb2\xa2\x0f\x50\x2a\x44\xdc\x4e\xde\x9f\x34\x36\xf7\x8b\x92\x36\x39\x06\x8a\x24\xa5\xdd\x04\xfb\xe5\x74\xbd\x8a\xe1\xd7\x16\x4e\xae\x70\xd8\xb6\x80\x17\xd7\xa4\x4e\x4a\xae\xa4\x0f\x8a\x44\xe7\xa5\xee\x74\x21\x7b\x8f\xd7\xf7\x3e\xaa\xd2\x5d\xa5\xff\xae\x79\x6b\x69\xbd\x63\xac\x33\x50\xd5\x7b\x5f\xed\x58\xe1\xf9\xc8\x6b\xa0\x8f\x03\x29\x6c\x57\xad\xfd\x8d\x3b\x38\x76\x3f\x26\x2f\x41\x53\x54\xa0\x09\xb5\xa9\x35\x14\xcb\xe5\xa4\x9c\x1d\xf3\x77\x86\x67\x61\x70\xd2\xc6\x7e\xe5\x73\x1d\xcc\x05\xf6\xda\x72\x79\xd1\x84\xdf\x96\x15\x11\x1d\x49\x07\x2a\x53\x88\x1f\x1e\xf4\x49\xe8\xa0\x32\x29\x68\x5c\x6f\x3d\x64\x4d\x95\x67\x3e\xc9\x4c\x63\xfd\x58\xbb\x8a\x52\x87\x0c\xb2\x20\xd6\xf8\xa4\x70\x75\xe1\x2f\x4c\x9c\x82\xe6\xe9\x4d\x78\x14\x4e\xf0\xd8\xe8\xfc\x95\x78\xc1\x91\xfb\x24\x78\xd2\x99\x94\xeb\xb9\x4d\xdd\x3f\x40\xa6\x26\x6a\xcf\xc3\x8a\x62\x78\x10\x00\x28\x77\x1d\x63\xa5\xe7\xc3\x0c\x92\xc7\xda\xdc\xb0\xfc\x64\x7e\x26\xfd\x58\xfe\xe0\x6a\x60\x41\x63\x54\xbd\xff\x88\xa9\xa6\x90\xa6\xb3\xdd\x7f\xc1\x62\x59\x94\x0a\x99\x55\x77\xd3\xbc\xdc\xa6\x2f\xb8\xc6\x79\xf6\xcb\x8a\xf0\x99\x4f\xb1\x1b\xc9\x83\xb3\xf5\xf0\x25\x60\x57\xc8\x8c\x39\xb4\x72\xad\xaf\xa1\xde\x6e\x45\xdc\xdb\x1d\x5d\xcb\xf9\x6e\xc9\xa5\xb6\x62\x62\xc1\x91\xac\x23\xc0\xf3\x9a\x1c\x80\x33\xf4\x5b\x6d\xf9\x5a\x86\x37\xb6\x3d\xd7\x93\xe8\x08\xd6\x6c\x29\x95\x44\x1a\x47\xf7\xbc\xfb\xe9\x59\xeb\xec\xb6\x80\x34\x8f\xab\x81\x4a\x9d\x39\xe9\x07\x47\xa6\x22\x8a\x5f\xf5\x97\x3a\x39\xb9\xd1\xfd\x1d\xdf\xab\x5e\x69\xc8\x4a\x6b\x50\x4d\x90\xbf\x5c\x53\x47\x63\x82\x94\x85\x0d\xfe\xe4\xc2\xd3\xa7\x69\xb2\x4a\x11\x44\x7c\xed\x56\x90\x4a\x98\xba\x55\xdf\x68\xa7\xf1\x85\xdc\xcc\x99\x19\x89\x6b\x3d\x36\xa1\x79\x79\xcb\x16\x23\x4f\x5a\xa1\x0e\xca\x7b\x02\x9c\x3b\x64\x57\xe9\x7c\x2f\xd0\x61\xe4\x94\x42\x02\xd3\xd4\x6b\xc6\x51\x4b\x9d\x4b\x75\xbd\xf0\xad\x14\xa0\xac\xfc\xaf\xe0\xe3\x84\xe5\x70\x29\x65\x18\x25\xc0\xb6\x06\x5d\x74\x6a\x1d\x30\x4a\x29\x6b\x89\xcb\xec\x0f\xa4\xe4\x66\xc2\xc5\xe2\xb5\x2a\x11\x3c\x3a\xe5\xd5\x7a\x3b\x48\xeb\x8c\x69\xa4\x5b\x7c\x61\x82\xc3\xb0\x08\xa2\x39\xff\xc2\x89\xbe\xe3\x2b\xd9\x66\xda\xb3\x1f\xc5\x6f\xa2\x11\x11\xf8\xc6\xb5\x36\xbd\x44\xfb\xfe\x01\x58\x7a\x4c\x5c\x4f\x5d\x47', 2) \ No newline at end of file diff --git a/cs101courseware/dist/cs101report1_grade.py b/cs101courseware/dist/cs101report1_grade.py new file mode 100644 index 0000000000000000000000000000000000000000..2d77ac9bef42c73a3a4157a8ac9fdbb6cbdc4bcd --- /dev/null +++ b/cs101courseware/dist/cs101report1_grade.py @@ -0,0 +1 @@ +__pyarmor__(__name__, __file__, b'\x50\x59\x41\x52\x4d\x4f\x52\x00\x00\x03\x06\x00\x33\x0d\x0d\x0a\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x40\x00\x00\x00\x8c\x0a\x00\x00\x00\x00\x00\x10\x8e\x9b\x14\xed\xf6\x8b\xed\xa0\x07\xe4\xca\xe9\x03\xce\x6f\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x12\xdb\x9a\xf0\x9f\xfc\x5d\x10\x62\x42\x1c\x0e\x28\x15\xd7\x34\x95\x7f\x0c\x8b\xc3\x63\x53\x00\x3a\xbf\x40\x6f\x60\x06\xe5\x7a\xdf\x36\x75\x38\xd7\xa8\x25\xb2\xa8\xd2\x2c\x86\xee\x6e\xb2\xf2\xdf\x52\x5e\x79\x58\x3c\xfb\x3d\x56\x1a\x7e\x59\x9a\x4a\x1b\xdb\xe8\xaa\x3b\xbf\x2a\x22\xe0\xcf\x69\xe1\x03\x35\x8b\xa2\xf3\x28\x7b\x9a\xb2\x13\x28\x53\x81\x75\xda\xdc\x68\x8a\x2c\xf3\x64\xad\x88\x12\x5b\x09\xe1\xb3\x92\x28\xc7\x85\xd6\x11\x1b\x9c\xfc\xb6\x8a\x54\x45\x7b\x55\x65\x2f\x87\x56\xef\xfc\xd8\x0e\x38\x47\x12\xf1\x34\x29\x93\x2b\x5b\x1e\x85\x37\x8e\x33\xf7\x3d\x9b\x4f\xf6\x37\x55\x33\xb8\x28\xec\x87\x73\x15\xcb\xde\x5f\x85\x21\xca\xee\x0a\xa7\x4a\x2d\xa3\xf9\xce\xe1\xf1\xa4\x6a\x5c\x7b\x84\xb2\xee\x09\xf5\xde\x22\x69\xb1\x24\x9e\x65\x2f\x3d\x62\xfe\xb5\x44\xb7\x3b\x6b\x20\x36\x9b\x54\xe2\x4a\xb9\xfd\x43\x49\xf5\x64\xeb\x3e\x32\x5d\x36\xa5\xad\xef\x1d\xb3\xa2\xad\x29\x94\x7b\x35\xd3\xdf\x75\x83\x9f\x56\x86\x81\x97\xf9\xa7\x22\xff\x8d\x55\x26\x82\xdb\x82\xc8\x7b\x9f\xab\x3f\x22\x7f\xcc\x23\x41\x4d\x2e\xc2\x90\xd6\x0d\x86\xa0\x06\x8a\x9f\xa1\x02\xdd\xe5\xc6\xa9\x84\xe5\x0b\xec\x0a\xa3\xac\xe6\x86\x58\xc7\x2a\x0d\x5c\x1d\xd5\x10\x8a\xbd\x1a\x79\x5c\x87\x61\x06\x41\xd7\xe0\x0d\x79\x1b\x36\xd9\xf7\x27\x8d\x5b\xab\x7e\xc1\x33\x52\x7f\x00\xd5\xe4\xec\xe0\x28\xb7\x62\x53\x6f\xee\x4e\x5b\x5d\x25\x30\x46\xd8\x60\xf6\xe8\x5a\xa3\xb9\x44\xe4\xe4\x0f\x05\xd8\x86\xbf\xf7\x3e\x17\x0e\x1c\xae\x71\x88\xdf\x41\x0a\x23\xe2\x87\xa3\x5a\xa6\xc8\x8f\x89\x55\xd9\x57\x2d\x37\xf6\xfe\x4e\x9f\xc5\x0e\x1a\xd7\x82\xdb\x4c\x86\xae\x58\x6b\xb1\x80\x71\x92\x95\x47\x47\x61\x1e\x42\xb9\x22\x7a\xb1\xcc\xc0\xf4\x75\xf5\x16\x77\xd2\xab\x9a\x7e\xb0\x2d\x66\x91\x49\x37\xcf\xb3\xf9\xd2\x87\x1c\xea\xb0\x05\x23\x12\xf7\xb7\x5b\x6d\x76\xb8\x31\x0c\x77\xa7\x2f\xa9\xbe\x53\xb9\x65\x6f\x4a\x51\xaa\x9d\x85\x9e\x69\x14\x37\xca\x79\xdc\xe1\x41\x4e\x33\xc0\x54\xaf\xd9\x67\x1d\x7e\x15\x0d\x00\x72\xea\x68\x6c\x87\x1a\x91\xae\xdc\xa7\xff\x7d\x40\x16\x83\x1b\xc0\x8c\xbc\x69\xc8\x98\xed\x76\xb1\x4d\x69\x14\x83\x1e\x2e\xc9\xb3\xb6\x6f\x0f\x67\xaa\x4a\x2c\x33\x7f\xa7\x71\xc5\x1b\xed\xc2\x2c\x1d\x15\xdb\x0a\xb1\xa2\xed\x65\x33\x39\xc2\xf7\x80\xfe\x99\x9b\x66\x47\x19\x43\x41\xb6\xaf\xe8\xb2\x1d\xba\x1e\xb9\x48\xdb\x73\x5b\x83\x0f\x28\xce\x5a\x0c\xe3\x48\x9c\xdd\xb2\x61\x39\xb0\x04\x25\x26\x2c\xf6\x6a\x5c\x62\x70\xd6\x27\x21\x08\x6b\xd6\x7a\x4e\x7f\xfb\x8b\xd9\xcc\x80\x6e\xa3\xcf\xf3\x3d\xd0\xd4\xed\x30\x63\xce\xaf\x35\x1b\x69\xed\x27\x30\x8d\x95\x61\xef\x79\xd6\xe5\xdd\x72\x4e\x61\x4c\xbc\xc7\xa2\x05\x3b\xff\x96\x1c\x19\x32\xfe\x00\xa5\xf0\x92\xad\xc0\x70\x87\x24\xd8\xcf\xd6\x0f\x3e\x35\x24\x5b\x8c\xb2\x4a\xe1\x1d\xb7\xc7\x4b\xef\x8d\xe1\x33\x8f\x3d\x1e\x44\x46\x3e\xf6\x1c\x0f\x19\x8f\x61\x22\x1a\x46\x23\x5e\x13\x0a\xc3\xd3\xb0\xcd\x84\x35\xec\x2d\x0b\xd5\x08\x42\x9a\x38\xbb\x1a\x5b\x35\x91\xae\x62\x19\x49\xfd\xa5\x1c\xd1\xba\x35\x73\x7d\x95\x07\x8c\x47\xd4\x25\xce\xcf\x4b\xcf\x2b\x50\x60\xeb\x04\x54\x69\x5b\x9a\xd9\xce\x73\x07\xc6\x10\x47\x72\x09\xa2\xff\xd0\xdd\x2f\xc7\x37\x0d\x25\xef\xf3\xb9\x1b\xe5\x73\x4b\x1f\x89\xc2\xce\x97\x0c\xba\x51\xc5\x3a\x6e\xd3\x97\xa5\xbf\x4d\x54\x0d\x5a\x7f\x6a\xed\x0f\x4e\x99\x32\x9b\xcc\x47\xb3\x92\x93\x9e\x61\x42\x6a\x4c\x24\x29\xfa\x39\xaf\x91\xde\xb0\xea\xc3\x02\xfa\x73\x7c\x6b\xe1\x11\x60\x22\x5d\xaf\x91\xda\xf5\x33\x71\xa6\xc7\x93\xe5\xc3\x23\xb4\x4b\xa2\xff\xbc\xeb\xbe\xfe\xc1\x67\x55\x3c\xa2\x59\x9c\x21\x55\xc7\x81\xe7\x87\xbb\x0d\x6c\x8c\xb3\xf3\x97\xa4\x88\xca\x13\x87\xb3\xff\xb6\xab\xca\x03\xb4\x91\x92\xbc\xd6\x4c\x7c\xe8\x00\x41\x85\xe8\x43\xf4\x76\x3d\xe5\xf2\xf9\x2a\x38\x4a\x65\xf3\x5a\x4e\x1e\x04\x11\x76\x4d\x86\x07\x67\x28\x4f\x95\x59\xa5\x47\xce\x0a\xa6\x17\x55\xdd\xbe\x92\x4d\xc7\xeb\x27\xb5\x12\x93\x2e\xd9\x37\x53\xcc\x35\x86\x21\xaf\x3b\x9c\x3b\x1d\x50\x36\x43\x09\xb1\x51\x7e\xc8\x1c\xf8\x49\x30\x6d\xc2\x31\x91\x83\xfd\x28\x7c\x50\xcf\x77\x05\x5b\x64\x2a\x54\x44\xab\xc6\x34\x7d\xdd\x41\x8f\xaf\x20\x1f\x7f\xa8\xaf\x00\x79\x0b\x73\xb5\x08\xb8\xb3\x78\xa7\xc4\xb5\xcd\xb0\x44\xb5\x51\xbd\x9b\xdb\x3f\xb8\xb0\x3a\xac\xf4\x03\xb3\x06\x2a\xf3\xdf\x02\x01\xcd\xcd\x1c\xbb\x0d\xa9\x93\xf0\xde\xfe\x19\x39\xb4\xf6\x44\xb9\x68\x04\xb7\xc4\x07\xda\x01\x19\x2c\x98\x41\xe5\xdb\x62\xa0\x52\xdb\x6c\x02\x04\xcf\xbb\x23\xf1\x98\x3c\x09\x11\xe8\x89\x63\xa4\xed\x21\x1e\xb6\x48\x57\x32\xba\x53\xdd\x92\x40\xa6\x32\xec\xd4\xb8\x0f\xf0\x1f\xb6\xa0\xfb\xce\x24\x21\xe3\xfa\x35\xd4\xea\x1d\xe6\x4b\x27\x54\x16\xc9\x88\xdd\x9b\x92\x13\x0d\xbc\x66\x33\x7c\x32\x4a\x71\xa4\xaf\x4c\xb1\x4d\x21\xfd\xc7\xd5\xb5\xd9\x46\x61\xa3\x21\x02\x4c\xfe\x38\xbf\xcf\x63\x6b\x25\x28\xed\xf6\x6e\x87\x62\x09\x96\x2b\x82\xd5\x03\x2e\x85\x8b\x8c\x1a\x5d\x81\x0a\x40\xf9\x97\x59\xfd\x05\x73\xbb\x7d\xd6\x02\x59\xf5\x48\x9e\x3b\x72\x87\x30\x18\x0d\x02\xf4\x71\xd8\x41\xbe\x94\x43\xad\x0b\x10\x1a\xe0\x53\x1c\x9d\x2e\x2a\x1f\xf0\x45\x81\x39\x45\xec\xaa\xef\x57\x48\x22\xb7\xbe\x9a\x23\x51\x5d\x94\x40\xe2\xde\x52\xf0\xc3\x8c\x68\xa8\x7f\xdb\xd9\x5e\xe6\xcc\x06\x92\x42\x3e\x8c\xd4\x45\x89\xe7\xec\xd6\x6c\x18\xfe\x68\x0f\x61\x19\xc4\xc5\xb2\xf5\x7c\xd4\x58\x61\x9a\x6d\xe0\xc7\x9f\x07\xcf\x1b\xe3\xb0\xa9\xcd\x21\x99\x88\x07\xdc\x1e\x5b\x18\xdf\x9e\xee\x22\x53\x3e\x5d\x94\x6c\x2a\x28\x98\x1c\xf4\x7c\x44\x55\xfc\xa7\x0a\x46\x9c\xa4\xd2\x9b\x36\x94\x6f\x16\x85\x77\x1b\x5b\x97\xc5\xdd\x34\xc7\xf8\xac\x9d\x86\x1d\x26\xf6\x24\x8f\xda\x39\x2d\xeb\x72\xfa\xd3\xaa\xef\x08\x85\x9a\x0b\xf9\x20\x52\x0d\x38\x1d\x40\x0c\x4c\x99\xd8\x07\xe7\x35\xc5\x35\xda\xa7\x41\xc3\x6a\x3d\x98\x9d\xa9\xd4\xa1\xeb\xc3\x52\xfe\xcd\x6f\x04\x91\xcc\x4a\x7b\x66\xbf\x77\x96\x59\x0b\x02\xb7\xb0\x07\xf3\xb7\x3a\x2a\xcd\x17\x0e\x4e\x0a\xfb\x7d\xbc\xb4\x76\x00\xf5\x7c\x66\x83\xab\x49\x82\x75\x75\xc9\x67\xe2\xdf\xf6\x3c\x8a\xa4\x4f\x9e\xc5\x88\xf8\xb8\x21\xd2\x0f\x7b\x00\x8d\x11\xa9\x9a\x5c\xdd\xcb\x24\xd0\xde\x4f\xff\x08\x0f\xf5\xfe\x7c\xbe\x7a\x82\xdd\x31\xa0\xa1\x55\x0b\xf1\x97\x0c\x61\x6f\x38\x5f\xe2\x47\x05\x65\x30\x5a\x0b\x9b\xa6\xb2\x6f\xc3\xd2\xb5\x09\x16\x2f\x44\xd2\xc8\xd3\x80\x2e\x3b\xcf\x8d\xdf\x9e\x57\x5a\x31\x2b\xa0\x64\xd7\x76\x2f\x58\x33\x68\x02\x3b\x1c\x81\xf4\x96\xbe\x2d\x97\x15\xee\x31\xa8\xba\x86\x55\xb9\x26\xf0\xdb\xfe\x74\x8f\x74\x7d\x21\x79\xb6\xe2\xd9\xc4\xe4\x43\xd2\x9b\x26\xa6\x1d\x06\xc3\xa5\x71\x12\xd8\xc5\x52\x29\xfc\x84\xbb\x16\xcc\xe9\xb0\xbd\x5d\x95\x81\xa5\x4f\x48\x6e\xb3\xef\xdb\xb1\xdb\x0a\xcd\xc9\xc5\x09\xce\x4a\xd9\x06\x5c\x87\x3c\x9d\x42\xa4\x7d\x53\xba\x88\xa1\x4f\x35\x87\x14\x63\xa1\x81\xb4\xda\x0f\x6c\xec\xfb\xe2\xfd\xb8\x13\xb5\xeb\x79\x38\x3f\x06\xe6\x75\xb0\x6c\x4d\x7d\x36\x2b\x01\xa1\xa8\x79\xf3\x54\xa6\xc2\x5c\x5c\x62\x7a\x16\x85\x83\x1b\x1c\x9d\x68\x93\x10\xd0\x7a\xfc\xad\xf5\xab\x5a\xc3\xb4\x27\x92\x35\x10\x92\xee\x45\x04\x31\xd4\xa7\xb5\x28\xbe\x0c\x16\x82\x8f\xe9\x0d\xb5\xc7\xa2\x97\x7d\x14\xaa\x8e\x9e\x51\x3d\x06\x72\xe9\xd7\x70\x31\x26\x7b\xab\xa6\x86\x96\xf0\x1e\xb8\xee\xfb\xf8\xa2\x17\x60\x8b\x79\xe2\x3f\x8d\x5d\x93\x73\x2f\xb0\x00\x0a\x07\xd9\x20\x50\x60\x6e\xd1\x1d\xbc\xce\x0b\xb5\x3e\xc1\x22\x12\x66\x64\x41\xc0\xbe\x4e\x89\x89\xc9\xce\xe3\x40\x8e\xfd\x3f\x0e\xa4\x36\x6d\x42\xca\xac\x80\x9b\xde\x8a\xc7\x17\xa1\x91\x64\x23\xa0\x64\x4a\x0c\x28\xd5\x9b\x5b\xcf\xb8\x22\xc0\x3e\xac\xbc\x22\xfc\x0e\x18\x41\x11\xb3\xc7\xfb\x63\x9c\x43\x6d\xa8\x92\xae\x0d\xd8\x39\xa0\xe9\x33\xe1\xba\x95\x7f\xc4\x15\xdb\xd8\x0d\x5a\x3e\x3e\x23\xc4\xc8\xa5\x7e\x64\x0a\xc8\x97\xbb\xe5\xa2\xe2\x96\xcc\x55\x99\x18\x9b\x20\x26\x4e\xdd\x8e\x56\x33\xce\x62\x01\x62\x2d\x6a\xde\xd6\x50\xf4\xa6\x8d\xaa\x4b\x4d\xed\x56\x4a\xfb\xf6\xbb\x45\xda\x68\xd7\x40\x11\x1c\x2d\x90\xf6\x20\x9f\x1b\xb9\xa1\x4f\x42\x75\xda\xa4\x8d\xa9\x9f\xea\xe4\xb9\xb2\xa9\xfe\x35\x97\x0f\xbb\xa1\x51\x3e\x54\x08\xfb\xf2\xfc\x8b\x73\xca\x8a\x85\x01\x75\x98\x56\x7d\xfa\x6c\x08\x11\x05\xe5\x93\x7d\x43\x66\x22\x75\x0b\xb6\x8d\xa4\x09\x4b\xa7\x13\x45\x2b\x80\xc1\xb7\x66\xbe\x62\xce\xef\xf1\xa1\x57\xea\x39\x65\xf3\x05\x9d\x7f\xac\x17\x43\xf5\x20\x6b\xe8\x90\x08\xc1\x25\x08\x76\x6c\xbf\xbe\xea\x7c\xe7\xfb\x1e\x42\x63\xd8\x4c\x50\x4a\xb0\x5e\xbe\x2e\xd9\xeb\xef\x0e\x49\x22\x3e\x09\x4a\xd9\xa3\x15\xf9\x5a\x7d\xad\x0a\x51\x4f\x7c\x30\x3d\x6d\x3a\x91\x12\x0a\xc9\x66\x08\x06\x94\xad\xff\x7a\x6d\xfb\x70\x45\x22\x91\xe3\x15\x2f\x07\x45\x6b\xb8\x24\x38\x80\xb6\xdf\xe2\x05\x3b\x4b\xc1\x6b\x57\x11\x2d\xd3\x9d\x90\x9e\x4b\x4c\x7f\x2e\x44\x40\x8c\x50\x79\x0f\x97\x2d\x5d\xf0\xfd\xf6\xdb\x8a\x0e\x4e\x7a\xd3\xc8\x81\xe5\xa2\x0b\xbc\xce\xc9\xf0\xf6\x38\x87\x18\x08\x7d\x83\xfd\x6d\x70\x69\xac\x31\xc6\xdf\x56\x0f\xcd\xa0\xac\xb4\x0b\xeb\x8a\xdd\xbd\xbc\x39\x4e\x71\x3b\xf3\x56\x36\xb4\xd8\x60\x1f\xa5\xc6\xb3\xa1\x9e\xb5\xba\x92\x8a\xe2\x81\x5b\xa9\x09\x15\x46\x24\x84\x6a\xdf\x6f\xf7\x34\x60\xd4\xe2\xda\x92\x92\x64\x11\xa4\xfd\x84\xd6\xb7\xe5\x7a\xca\x2f\xdf\xa4\x47\xcb\xe1\x84\x6b\x0e\xe8\x2b\xda\xe3\xfc\x1a\x82\xaa\x46\x39\x7c\x85\x93\x35\xe3\xd4\x90\x4f\x8f\x2d\x51\x51\xf0\xcc\x1e\xd3\xfa\x5b\x97\x2c\x18\x1e\xcc\xd0\x7d\x29\x92\x33\x8a\x19\xc5\x70\x6d\xc3\xb6\xac\xda\x2e\xda\x28\x99\xb0\x76\x8e\x25\x86\x0d\x8e\x7c\xa5\x53\xa3\x44\x87\x7f\x4f\xbd\x79\x30\x29\xdc\x63\xca\xeb\x96\x29\x9f\x39\x3d\xc2\x75\x1f\x0b\x32\x69\x28\x41\x24\x99\x6b\x08\x4c\x46\xf3\xd5\xa1\x77\x33\x07\x3b\x43\x47\xd6\x75\xb2\x2d\xa5\x8b\xa5\xdc\x94\x71\x78\x40\x28\xc9\x8f\xca\x56\x23\xc3\x21\x58\x07\x4c\x25\x3f\xd3\xc9\x10\x47\xde\x2b\xf1\x23\x3f\x15\x70\xda\x2d\x0c\xb2\xd7\xa0\x13\xee\x6b\xd5\x0e\x45\x87\x19\x78\x17\x69\xb0\x59\xe7\x47\x09\x8c\xee\xf7\xd9\x3f\xd8\xb7\x4c\x52\x0f\x5e\x99\xe6\xdf\xb3\x1a\xd1\x6f\xbc\xe3\x73\x46\xbc\x81\xd6\xc8\x21\x87\x8e\xb3\xce\xec\x8b\x45\xea\x74\x0a\x1b\x24\x15\xa8\x7e\x24\x0a\x0f\x78\xe9\x39\x5b\x6b\x5f\x6b\xb2\x84\x1f\x3a\x00\x93\x61\x67\x51\xa0\x0d\xb7\x97\xdd\x8c\x42\x25\xfb\xce\x05\x58\x76\x1b\x87\xb1\x86\x17\x6e\xe6\x6b\x7d\x18\x93\x87\xf9\xe4\x20\xa8\x52\xc6\x40\xff\xfa\x8f\x60\x81\x69\xf4\x5d\xfe\x7c\x99\x07\x8a\x3d\x61\x65\x60\x4b\x37\x18\x15\x0c\xf0\x7f\x44\x4b\x32\x90\x03\xf4\xc0\x29\x0b\xfb\x26\xc4\xfa\x08\x01\x32\x68\x9b\xef\xc0\xcd\x58\x99\x84\x68\xb2\xf2\x0f\x22\xd8\xc6\x0f\xaa\xdc\x6e\x29\xe7\x84\x73\xe1\x2c\x58\x2c\xdd\x08\xd3\x3b\x89\xe3\x94\xa4\xe5\xaa\xe3\x8a\xde\xfb\x4d\x72\x74\xa6\x4b\xe2\xe8\x06\x72\xef\x69\xc9\x71\xa5\x8d\x35\xe7\x8e\x28\x12\x8a\x01\xff\x0d\x58\xe1\xb1\xaf\xd9\x99\xfc\x8a\x09\x87\x60\x73\xe7\x50\xfe\x8a\x7a\x1b\x2b\x14\x3d\xdf\x1f\x6f\xe4\x02\x4a\xfd\x64\x5a\x81\x8f\xcd\x13\x6c\x2a\xab\xbf\x3f\x15\x37\x95\x8d\xc9\x18\x67\x27\x09\x31\x34\x39\xda\x6d\x4b\x98\xca\xa4\xa0\x50\x88\x32\xe5\x8b\x22\x7c\x14\xdb\x8d\x60\x10\x29\x18\xd3\x1f\x37\x9f\x17\x7d\x34\x07\x5b\x84\x94\x14\x23\x85\x47\xde\x6d\x93\xda\xfc\xd1\x08\x85\x12\xe0\x66\xf5\x09\x04\x12\xe7\x83\x74\x72\xf4\x32\xa0\x66\x1a\xc5\x12\x40\x98\xa8\x90\xa1\x4f\x39\x16\xe2\xb6\x61\x52\x7f\x38\x19\xa6\xc2\xfb\xf0\x08\x0b\x5d\x40\x12\x8b\x1b\xb5\x48\x22\x04\x77\x7f\xf7\x42\xcd\xbe\x9e\xe8\x54\x4e\xb8\x99\xeb\x91\x0c\x93\xa0\x6c\x08\x5e\x98\x74\x5b\xce\x99\xbf\x01\x0f\xad\xc0\x49\xcd\x4c\x3b\x1f\x07\xc8\x54\x7d\xef\xf1\x4b\x29\x8f\xe9\xba\xef\xce', 2) \ No newline at end of file diff --git a/cs101courseware/dist/cs101report2.py b/cs101courseware/dist/cs101report2.py new file mode 100644 index 0000000000000000000000000000000000000000..f8c2ab91d9e5f17dcce19c5ce8cb006b28cae79a --- /dev/null +++ b/cs101courseware/dist/cs101report2.py @@ -0,0 +1 @@ +__pyarmor__(__name__, __file__, b'\x50\x59\x41\x52\x4d\x4f\x52\x00\x00\x03\x06\x00\x33\x0d\x0d\x0a\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x40\x00\x00\x00\x51\x09\x00\x00\x00\x00\x00\x10\xb2\x65\x2f\xdc\x18\xb6\x5f\xe6\xb1\x27\xb4\xd3\x2d\xd1\x68\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x30\x24\xbd\xb2\xb5\xb4\x43\x59\x7e\xa5\x88\xe6\x16\x47\x79\x7c\x72\xa8\x07\x8b\x95\x11\x07\xf7\x06\x22\x25\xfe\x10\x66\xd2\x0d\x6e\xe8\xff\x0b\x06\xc1\xb5\xa1\x58\xd1\xbe\x31\x79\x9d\xf3\x6c\x56\x85\x4c\x31\x8e\x5f\xe5\x36\x99\x70\x3c\xa8\xd8\xb1\xe5\x6f\xd4\xb9\x51\xfa\xa7\x94\xd5\x6d\xc2\x4b\xe2\x4f\x21\x6e\xfa\x3b\x65\xb7\x39\x7d\x8f\xe6\x6d\xd1\xaf\x03\xaa\xa8\x85\x57\xaf\x1c\xc5\x03\xfd\x67\x14\x53\x13\x95\x93\x20\x82\x22\x15\xc2\xbd\x1a\xe2\xe0\xdf\x8e\xb8\x28\xb7\xdd\x96\x6b\x81\xb4\x83\x19\x91\x9d\x96\x1d\xa7\x62\x94\x58\x96\x81\x18\x29\xea\x22\xd0\x73\xc7\x78\xcf\x03\x2e\x52\x6d\x0c\xdf\x09\x36\xdb\xfc\xfc\x22\x39\xcc\xac\x83\x3d\xa2\xd8\xf6\x2f\xf1\x3b\x9f\xf6\x6d\xb2\x49\x0c\x35\xf8\x23\x07\xc8\xad\xc3\x69\x95\xc4\x71\xf1\x26\x4b\xd8\x70\x05\x29\x4b\xe8\x64\xf4\xb0\xd7\xc9\x4a\x60\xea\xb4\xc2\xb4\x65\x8c\xcc\x28\x6d\x45\x62\x35\xa8\xd8\x03\xb1\x87\x89\x52\xd6\xcd\x39\x35\x73\xc7\x44\x8c\x8e\x69\xf1\xda\xe8\xda\xe1\x13\xf8\xe3\x83\xc2\xdd\xb9\x00\xf6\xfc\xf9\x6e\x9b\x10\x2c\xab\x74\xa5\xe8\x53\x3c\x64\x61\xf1\xd7\xed\x8a\x27\x2a\x1a\x22\x26\xc9\xce\xb4\x6e\x04\x27\xca\x89\xd5\x20\x18\x76\xa2\xa0\x81\x44\xd2\xac\x9f\x2d\xf1\x7b\x6e\x0d\xc4\xa3\x02\xcc\xa5\x0f\xb6\x19\x1b\x0d\x1a\x56\xa0\x50\x05\x54\xd7\xb7\xef\x7a\x75\xd0\x11\x3d\xaa\x4b\x07\x94\x0c\x4a\x11\xa4\xfb\xd0\x77\xd5\x0f\x6d\xd9\x5e\xd0\x08\xb1\xef\x15\xa4\xee\x53\x19\x33\x79\x8e\x1d\xa8\x35\xec\xd1\x69\x2a\xd6\x63\xd2\xd5\x09\x7f\x40\x19\x57\x52\xae\xc7\x93\x40\x01\x62\x17\xc9\x89\x75\x64\x02\xa0\xf9\xf3\x3a\xc1\x9e\x8d\x98\x44\x84\x6c\x95\x25\xf9\x61\x8d\xb9\x1c\x3d\x25\x7c\x7f\xa3\xa9\x33\x84\xee\xde\xaa\x3a\xd7\xfa\x3a\x47\xe9\xe8\xa2\x2a\xcf\x73\x84\x8f\xcf\x44\xb2\x29\x8e\x6a\x05\x43\x24\x66\xf3\x51\xd0\x0b\x69\xc8\xf6\xef\x59\x19\x9d\xc2\x48\xb8\x12\x14\xf3\xaf\x72\x44\x21\x48\x81\x78\x93\xa7\x08\x69\x02\xc7\x99\x1d\x6b\x44\xa1\xb0\x8c\xaa\x62\xd2\x2e\xd2\x2f\x00\xf1\x45\x5e\xb1\x56\x83\x0e\x2a\xb0\x86\x03\x8d\x83\x29\x30\x99\xa9\x03\xe6\x1c\x2e\x8d\xac\x95\x69\x8e\x1e\xcf\xb4\x0b\x40\xb7\x59\x7e\xec\xd3\x9a\x68\xf6\xd0\xa1\x73\x06\x72\xec\x1d\xe1\xde\xe9\x8a\xbd\x3a\xe6\xeb\x3b\x99\xc7\x2d\x6a\x3d\x92\xd9\x52\x45\xf7\x0c\x2c\x67\x63\xe5\xb1\xb4\x89\xeb\x9f\x33\x73\x12\x0a\xfb\x67\xc2\xe4\x61\x34\x7d\x44\xd6\x1b\xd0\x52\x3a\x9e\xa9\xb3\x3f\x5d\x4d\x02\x2e\xab\xab\x65\x5f\xce\xea\x25\x24\x20\xed\xbe\x21\xfa\x76\x3f\x47\x43\xb1\x62\x31\x0b\x91\x4e\x54\x82\x93\xcd\xe0\xc6\xb2\xf5\x86\x7e\xb1\x8c\x8a\xa6\x6d\x81\xa5\x2f\xc8\xec\x13\x53\xce\xdd\x76\xef\xab\x7c\x88\xeb\x84\xf6\x8e\x59\x7c\x0f\x9b\xab\x17\x8d\x9b\x91\x5b\x1f\x47\x88\x56\x8a\xba\xcb\x04\xa0\xe7\xf3\xa0\x2a\x63\xfb\x57\x9d\x17\xfb\xfe\x87\x34\x68\x48\x84\x71\x65\x77\x80\x13\x65\xb4\x2d\xb3\x74\x17\x93\x5a\x3b\x1b\x05\xe0\x85\xc6\xf2\xb1\xe1\x59\x76\x95\x19\x0b\x21\xfe\xa6\x2d\x39\x00\x6e\x19\xe7\xe2\xe3\x81\xde\xbb\x90\x3f\x90\x24\x9f\x19\x0c\xc9\x9e\xa5\x1c\x2a\x98\xb0\x86\x73\x17\x89\x7b\x4e\x55\x16\xe5\x75\x6b\x45\x15\xf3\x26\x33\xb0\x9a\x95\x9c\x47\x15\x71\x87\x04\xa3\xe0\xa3\xa0\x84\x84\x74\x3e\xf9\x8f\xcf\x2b\x38\x5b\xb6\x21\x80\xf4\x24\xb0\xb6\x41\x3d\x81\xeb\x1b\x41\x06\xb2\xb7\x99\xfc\x5d\xa3\xd4\x9f\xf2\x5b\x60\x72\xf5\x50\x09\x3f\xff\x06\xfc\x46\xbd\xa9\x96\x45\xa2\x37\xa6\xfc\xc9\x48\x52\x0b\x35\x73\xc3\xc0\x9d\xd6\xd6\xe0\x2f\x24\x47\x97\xb0\x56\xbe\x5b\xc6\x31\x90\x98\xd7\x6f\xc1\x03\x49\xfe\x76\x1e\xf8\x45\x6e\xfb\xae\xa0\xc0\xff\xcb\x70\x63\xd6\xe9\x2c\xe0\x5e\x8f\x0f\x58\xc2\x63\xa9\x67\xcb\xe8\x7e\xd6\xff\x89\x35\x35\xaa\x98\x96\x4e\xf9\x3b\xba\x03\xf7\xa4\xff\xdd\xd4\xe6\x7d\x7b\x52\x3a\x2b\xa1\x18\x91\x90\xc8\x05\xa1\x73\xd2\x06\x42\x7c\x32\xf5\x7c\x74\x39\xab\xa7\x2f\x27\x08\x98\x44\xfe\xfa\x1b\x2d\xf3\xf3\x89\xd7\x98\x27\x09\xb3\xaa\x2f\x20\x2e\xd5\xc8\xc0\x15\xeb\x92\x04\x5a\x09\x7f\x2e\x6b\xd7\xbb\x93\xe7\x02\x01\xe5\x12\x9d\xe0\x2b\x71\x7b\x19\x8d\xae\x9f\x21\x8c\xf5\xe7\x2e\xa0\x26\xc1\x4f\x26\x13\x66\xa3\x84\x8c\x6a\xa7\x7b\xd3\xd1\xda\xd5\x5b\x6c\xbb\xa4\xb6\x28\x2e\x6f\x33\x5e\x42\xd6\x76\xfa\x80\xd7\xd2\xf3\xcb\x8b\x6d\x48\x20\x7a\x21\xeb\x03\x73\x64\x2e\x7f\x5e\xae\x29\x41\x80\xbb\xcf\xd6\x18\x20\x94\x32\x05\x06\x3c\xba\x92\xcb\x28\xd0\x02\x00\x9c\x5b\xbd\x33\x47\x53\xd4\x01\xad\x69\xa7\xfd\x41\xce\x3f\xc3\x48\xf6\xd4\x82\x93\x97\x23\x99\xd4\x6f\x31\xb4\x8c\x63\xb8\xe2\x0b\x23\xed\x48\xf8\x18\x57\x89\x15\x8f\xc5\x1d\xd0\x04\x2b\x67\x96\xd1\x70\x79\xbe\x27\x79\xc0\xc4\x69\x86\xe6\x72\x6f\x32\x38\xc9\x37\x19\xa1\xc0\x0c\x43\x89\x17\xd3\xe0\x02\x98\x9a\x9d\x8f\xad\x1d\x96\x7b\x62\x8a\x4b\x74\xe8\xf1\xf3\xa7\x63\xbc\x7d\xea\x55\x77\x11\x92\xa8\x3a\x49\x9f\xc0\xbb\xea\x39\x7d\xdf\x3c\x72\x08\x18\x55\x83\xc5\x6d\x73\x3d\xcf\x1d\x9e\xe0\x24\x0e\xa1\xb9\xf6\x6d\x32\x11\xfc\xd4\x36\x59\x89\x11\x5d\xad\x1b\x5a\x44\x02\x17\x37\xcb\xde\x2c\xb2\x02\x1b\x57\x1f\xa3\x18\x36\xa8\xd0\x5f\x96\x5c\x5f\xcb\xe2\x55\x9c\x74\x67\xf5\x63\x6d\x07\xed\xdc\xaf\xca\x28\xa8\x76\x2a\xcf\xa8\x1f\xef\x9e\xbf\xad\xc1\x72\x66\x17\x51\xfe\x32\x1e\xbc\x0f\x38\x66\x05\x36\x7e\x29\x2f\x1e\x88\xdc\xec\x92\x16\x13\x3e\xde\xa5\x5e\x21\x3e\x6c\xd3\xdf\xa9\xeb\xbb\xb0\xc9\x33\x38\x92\xae\x1f\x72\x87\x63\x13\x6c\xde\xa5\x24\xbb\xf6\xb2\x65\x4f\xf1\x13\xc7\xb7\xf7\xbb\x15\x0a\xfd\x03\x19\xb1\x38\x2a\x91\xe0\x2c\x08\x39\xc7\x28\x00\x64\xc1\x17\xab\x6c\x0b\x92\xb7\x52\xbe\x25\x3b\x08\x87\x70\xa5\x2e\xb3\xfa\x33\xf1\x8d\x68\x04\x92\x43\xb8\xc4\x99\x1a\x56\xf1\xc7\x43\x21\x04\xfd\xaa\x42\xe9\x0d\x35\x94\x71\xd9\xf7\x1c\x46\x30\x95\xb1\xe7\xb9\x63\x8b\x32\x0d\x0e\x17\x3c\x67\xae\x83\xc7\x2d\x75\xfc\xf5\x9a\x43\x70\xfb\x38\x8b\x84\xd5\xd0\xd5\x05\x0d\x8b\x4d\xd3\x65\x03\x34\x42\xee\xde\x3c\xba\x07\xda\x7d\x49\xec\x62\x2c\x1f\x7b\xda\x96\xb3\x54\x2a\x9c\x15\xdd\xb6\x1a\x51\x86\x9d\xe4\xe5\x9c\x58\xcd\x26\x63\x1e\xe8\x6f\x22\x6e\x91\x9d\xb9\xfc\x55\xd8\xa4\x96\xb2\x35\x7b\x40\x49\x40\x89\xd9\x99\x13\x59\x83\x54\xe6\x68\xcf\xc2\x89\x77\x85\x11\xaa\xd9\xf5\x94\x34\x18\x60\xcb\x42\xb4\xcb\xdb\x5b\x69\xbf\x3b\x20\xd9\x6a\x8b\x61\x49\x9a\x1e\xad\xa2\xd4\x68\x5b\xa6\xdd\x81\x75\xd0\xbb\x5c\x77\xc2\xe8\xd6\x55\x97\x3a\xc7\x62\x00\x90\xf0\xd8\x7c\xd7\xfb\x3b\x4d\xdd\xbb\x6c\xdc\xb3\x76\x1b\x44\x3f\xa4\xec\xf6\xc2\x1f\x2d\xd3\x25\x85\xa5\x75\xe6\x29\xbf\xb1\xcf\x35\x22\x68\x2d\xf5\x3b\x98\xd5\xe2\x28\x16\x08\x79\x0f\xa5\x45\xd6\x88\xb2\xe4\x2c\xf6\xe9\x34\x9c\xd9\xfb\xdc\xb6\xdb\x14\xad\x74\xbc\xbb\x6a\x53\xa5\x65\x08\x11\x03\xc9\x83\x97\x5e\xbf\x98\xce\xba\x0e\x7d\xef\x40\x99\xe3\xc6\x67\x8a\xa0\xf1\x55\x95\x19\x5a\x41\x06\x1b\xfc\xe5\x73\x85\x68\x82\x15\xe2\xa8\x5d\xa2\x6b\x96\xcf\xea\x74\xff\x3f\x06\x1b\x35\x3b\x6d\xae\x4b\x15\x3a\x05\x02\x49\x34\x67\x46\xf1\x9a\xce\x2c\x9b\x73\xb5\x9d\xa9\xb9\x4c\xb4\x98\x57\xc8\xa0\x39\x6f\x16\xc3\xec\x98\x57\x92\x1f\x48\xdb\xe0\x13\x8d\x6e\x0e\x80\x69\x89\x36\x56\x9c\xab\xa7\x92\x12\x79\x64\xd0\xf7\x61\xb8\x29\xf9\x46\x8d\x02\x3d\x7d\x47\xc8\x4f\xe9\xb6\x0b\x42\xf1\xd3\x03\x68\xd4\x2d\x13\xe3\xc3\x8e\x59\x97\x61\xa8\x9d\x94\x90\x80\xd0\xea\x15\xfa\x4c\x74\xc3\x5e\x5e\x19\xc4\x4c\xc2\x10\x23\xc7\x0b\x92\x43\xe2\x0e\x97\x49\x5d\x01\xae\xad\xe6\x49\xa5\x3d\x04\xa9\xc6\x50\x7c\x8e\x09\x9d\x3c\x33\x49\x6b\x53\x76\x3f\x89\x12\x5b\xf5\xf2\xab\xda\x6b\x95\x6a\xd9\x63\xc1\x13\x30\x00\x92\xa2\x8c\x7e\x18\x45\xbc\xb8\xd5\x52\xf1\x48\x61\x44\x86\xac\x3e\xec\x1b\xc2\xa3\x91\x02\xc6\x8f\x3c\x28\xaa\xf2\x1c\x05\x9f\xa4\xeb\x32\x90\x68\x2f\xbf\x1f\xcb\x32\x71\xae\xbc\x4a\x83\x4e\x22\x8f\xdb\xdd\xd0\xbe\xcb\xe8\xf6\x71\x64\x4d\x09\xc6\xc2\xc8\xe1\x3e\xa6\x25\xa1\x87\xb4\x46\x43\x40\x50\x8a\x34\xf6\xef\x4c\xac\x55\x68\xe3\xee\x49\x3f\xea\xb3\x50\xa0\x1b\xb0\x7b\x7e\x0a\x22\xad\xcf\x83\x1b\x54\x28\x1f\xfd\x38\xa7\xf4\xe0\x77\x9a\xb5\x45\xac\x32\xb4\x6e\xd0\xfe\x5d\x86\xaa\xdf\x8e\xd9\x07\x73\x45\x4b\xc0\xcb\x54\x44\xe1\xf6\x7d\x4d\xe4\x1c\xc7\xfa\x50\xd3\xd5\x2f\x04\x12\x46\x7f\xf2\x46\x73\x86\x5e\xa4\x37\x63\x5a\xc0\xcd\x85\xff\x96\x40\xda\x35\xb4\x1f\xf9\x3b\x43\x4a\x37\x3d\xcf\x05\x05\x8d\xd1\x42\xad\xb0\x1a\xa9\x19\xb3\xfb\x16\xb5\x25\x4b\x21\x9b\x4b\xb6\x59\xf1\x7b\xed\xe8\x45\xf3\x62\x4b\x7b\xa9\x8d\x0b\xc1\x0a\xd8\x35\x09\xb5\x22\x7e\xc4\xac\x63\x22\x7c\xaf\x42\x91\x78\x1f\x25\x61\xeb\xf3\x64\xb1\xaa\xd3\x24\x78\xfc\x6b\xc1\x12\x91\x86\xce\x4e\xb7\x8f\xd7\x2b\xeb\xab\xc9\xa7\x33\xb1\x52\x35\xed\x19\x90\x26\xfd\x14\x3b\x9e\xfa\x92\x21\x64\xce\x96\x5b\x59\x56\xb4\xfb\xc3\x70\xcd\xbd\xd4\x7a\x80\x01\xba\x5e\x38\x8d\xf3\x07\x73\xfb\x26\x27\xba\xe9\x11\xb8\xda\x91\xb8\xc9\x4c\x84\x41\xa3\xae\xf4\x77\x72\xcf\x7d\xff\x24\xbf\xd0\x80\x5b\xbf\x23\x3e\x43\x2e\xd2\x8b\x05\x8e\xfc\x05\x04\xdb\x04\x41\xff\x2f\xea\xf9\xa7\x78\xa7\xee\xf8\x03\x3d\x9c\x7b\xe0\x5f\x9b\x8d\x3d\xfd\xf3\xf3\x54\x95\xf8\xd0\x9e\x9f\x71\x87\xc4\xb8\x91\xfe\xb0\xf4\xcd\x06\xa4\x9f\x06\x78\xbd\x28\xa6\xc9\x9f\x1e\xc5\xcf\x7b\x58\x52\xbb\x06\x7d\xbc\x7b\x14\xba\x69\xa9\x26\x3e\x88\xbc\xfb\x0e\x2a\x17\x46\x00\x5b\x0e\x56\xfe\x8d\x58\xca\x82\x82\x6f\x28\xc6\x0d\x32\x59\x95\x15\x73\x1c\xd8\x4d\x62\x48\x8f\x9b\x4e\xe1\x38\xb2\x3d\x28\xdf\x47\x5d\x17\x89\x9e\x73\x69\x30\xc6\x75\x11\xb3\xa5\x50\x90\xd0\xf6\x36\xe6\xf2\xf8\x54\x5f\xc2\x71\x59\xb8\xc0\x7d\x7f\x0c\x88\xb2\x9e\xa7\x46\xad\x22\x0e\x0f\xf6\xfb\xdf\x45\xb5\x2b\xff\xbc\x4b\xcd\x49\x7c\x1b\x72\x37\x6e\xec\xdd\x0f\xda\xf3\x92\xbe\x99\x3f\xa3\x87\x7d\x94\x6f\x86\x44\xf9\x3c\xd0\xab\x2a\x9e\xa6\xc3\x39\x24\xa7\xfe\x83\x04\xef\xf7\xb1\x95\x11\x6c\xd5\x96\x48\x54\x6b\x27\xdf\x3d\x34\xc9\x97\x8e\x21\xcc\x94\x39\xac\x99\x36\xdf\xe0\x9b\xac\xc4\x24\xe4\x21\xdf\x9d\xb5\x51\x80\x47\x36\x49\x0e\x84\x2b\x77\x3a\x36\x62\x36\xe4\x3d\x78\x62\x65\x43\xa7\x6c\x1c\xec\xf6\xec\x48\x31\xbe\x8b\x9c\xdc\x6b\xb4\x04\x48\x07\xd8\x94\x24\xe2\xd9\xeb\x1f\x9e\xea\x82\xc3\xd8\xb5\x4a\x49\x3b\x0a\x2d\x20\xd6\x95\xa8\x62\xaf\xd8\x14\x6e\xab\x55', 2) \ No newline at end of file diff --git a/cs101courseware/dist/cs101report2_grade.py b/cs101courseware/dist/cs101report2_grade.py new file mode 100644 index 0000000000000000000000000000000000000000..a70d6e1cfd4a1eab819b9547dd8d13a87ebde423 --- /dev/null +++ b/cs101courseware/dist/cs101report2_grade.py @@ -0,0 +1,3 @@ +from pytransform import pyarmor_runtime +pyarmor_runtime() +__pyarmor__(__name__, __file__, b'\x50\x59\x41\x52\x4d\x4f\x52\x00\x00\x03\x06\x00\x33\x0d\x0d\x0a\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x40\x00\x00\x00\x5e\x11\x00\x00\x00\x00\x00\x18\xd7\x18\x08\xba\x0d\xc9\xff\x2d\x72\xc4\x7b\x63\x27\x39\x52\x6c\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x62\x1b\x63\x8f\x73\x1e\x0d\x35\x9c\xe8\x12\xf8\x74\x24\xd0\x17\x14\x8e\x76\x67\xd8\xe1\x9e\xa8\x29\x65\x65\x91\x9e\xcc\x78\xdb\x26\xb5\x86\x51\xe2\x84\x14\x06\x2b\x08\x53\xeb\x11\xc5\x27\x95\x62\x38\xd9\x74\x45\x83\xdc\x09\xd5\xc8\x68\xdb\x30\x9b\x97\x27\xdf\xfe\xac\x67\xd6\x07\xa6\xe2\x09\xfd\x07\xf7\x8b\x63\xe8\x58\x36\x4e\x54\xd4\xac\xa4\x75\x94\x9b\xcf\xcc\xba\xe6\x3d\xaa\x6f\x60\x2f\x73\x5b\x57\xb4\x7c\x4d\x23\xa6\xcd\xa3\xf4\xd4\x21\x46\xaa\x13\x67\xe4\x0e\xa3\x21\x2e\xa5\x29\xac\xa1\xf1\x6a\x33\x89\x28\x16\xdd\xd1\x6a\xf8\xe4\x31\x43\x9f\xd5\x6d\xda\xeb\x30\x7c\x46\xda\xb8\x7e\xf3\x17\x43\x52\x8d\x1d\xcb\x9c\xd4\xbe\xbd\xae\xde\x6e\x55\x17\x56\x2d\xb7\x69\x1f\xc0\x36\xfd\x98\x9c\xb9\x1d\xbf\xb3\x8c\x95\xf2\x30\x13\xac\xfb\x31\x3c\x57\xb3\x71\xac\xe4\x5c\x33\x12\x83\x54\xca\x59\x83\x12\x34\xdf\x6c\x94\x46\x27\x56\x9d\x80\xaf\x54\x33\x83\x47\xc8\xc5\x5f\x1c\x5d\x4d\xa5\x3f\x8b\xc1\x57\x41\x5f\x26\x8b\xa9\xac\x3d\x11\xb0\x33\x31\x90\x37\xec\xdf\x9f\x65\x01\xfe\x57\x69\xe1\xde\xf8\x75\xc8\x1b\x36\x2b\xc8\xfd\x1f\x0f\x51\xed\x6b\x86\x7f\x65\xd0\x3f\x75\x02\x5b\x83\xeb\xda\xf6\xce\xd7\xe4\xc0\x85\x50\xf2\x33\x14\x75\x6b\x88\x07\x98\xb9\x8a\xa2\x37\x53\xad\x89\x1a\x99\x58\xb4\xcf\xaa\xbe\xd3\xfb\x34\x4e\xfc\xc2\x7e\x93\xbf\x3d\xf0\x5f\xbf\xbd\x26\x0a\x65\xd1\x52\xf4\xb4\xb7\x9f\xbd\x3c\xf2\x71\x62\x83\x43\x76\x39\x30\xb0\xe9\x26\x39\x3d\x35\x45\xe2\x5d\xe2\x90\xdf\x75\x7f\xc3\xe7\x23\xfe\x09\x63\xc4\xff\x72\x21\x5d\xf0\x0d\x6d\x82\xa4\xaf\xf5\x45\x1d\xec\xf9\x60\x92\x08\xdc\x8b\x59\xf7\xaa\xfe\x47\x70\x62\xaf\xa5\xbe\x0b\xef\xa9\xef\x42\x99\x14\x39\xc2\xc6\x8c\x36\x89\x20\xc1\x17\x5b\x87\xe5\xdb\xac\x70\x56\xd5\x50\x62\x6e\x6b\x3d\x61\x76\x26\xbd\x4c\xcb\x02\x2c\x76\xed\x4d\x74\x5e\xb0\xc8\x89\xd1\x55\x02\x9f\xff\x35\xb4\xe6\x98\x30\xa1\x91\xe8\xe3\x1b\x8f\x05\xa0\x5f\xe7\x6d\xc2\xf7\xe9\xcb\x7c\x75\x24\x5d\x15\xdb\xf1\xe8\x14\xaf\x0b\x77\x0b\x27\x7e\x98\xe5\xad\x91\xb0\x9e\x00\xc3\xd8\x91\xd1\x57\x2f\x81\x13\x8b\x91\xc7\x73\xe7\x4f\x5a\xff\x52\x7a\xfc\xea\x0d\x77\x5d\x40\xd3\xee\xe2\xde\x1b\x57\xda\x27\x9b\x3b\x02\xf7\x08\x3b\x5f\x05\xec\xf7\xc9\x23\xaa\xb9\xb1\xc1\x65\xf0\x3e\xb8\xda\xa5\x92\x7d\x64\xec\xa7\x1c\x43\x67\x18\x07\x8f\xa7\xf0\xe1\xf3\xd5\x9e\x23\x8a\x7f\xfc\xd4\x55\xf5\x3a\x8d\x74\x79\x70\xfc\xf8\xac\x04\xa1\x9f\x4a\x51\x61\x1a\x1a\x17\x3c\x46\xd1\xd6\xe2\x5a\xc9\xec\x6d\x4e\x10\x2e\x42\xae\x5f\x33\xc5\x75\x75\xec\x6e\xc5\x9e\xfc\x4e\x9c\x04\x20\x92\x51\xd4\x4a\x81\xdb\x53\x39\x43\x94\x5b\xa2\x24\x0f\xbf\x37\x68\x70\x95\xde\x29\x46\x0d\x90\x18\x16\x34\x10\xf1\x80\xb7\x63\xf8\x24\xab\x0f\x15\xfa\x40\x62\x08\x1f\xb1\xc3\x47\xd2\x4d\x76\x8d\x56\x13\xf9\xe0\x04\x28\x13\x86\x33\x77\x53\x0b\x2b\x49\x05\x69\x06\xee\x42\xa1\xae\xf5\x10\x31\x88\x64\xe0\x14\x66\xea\xb5\xb9\xe2\x8a\x10\xf0\xa6\x68\x5c\x7c\xcc\x20\x1a\xd9\xac\xda\x7e\xfa\x24\x3b\xed\x8e\x6a\x5e\xf1\x0f\x20\xe3\x51\x01\x92\x8b\xd1\xc2\x32\x49\xe6\xde\xee\x02\x18\xc7\xdd\xb1\x66\x41\xa5\x8e\x90\x87\xc1\x2e\x3b\xc1\x8c\x3f\xec\xe2\xf4\x24\x26\xc9\x03\xb1\x23\xa2\x27\xd7\xc2\xa9\xf7\x55\x54\x1d\x74\x20\x7d\x1c\x53\xa5\xd0\x5f\xb4\x31\xae\x80\xc6\xf1\x85\x79\x3f\x8f\xa3\xa1\x64\xc0\x4a\x5c\xad\x35\xcf\xce\x09\xaa\x8d\xe8\xfa\x24\x86\xfc\x85\xac\x8b\xee\x4c\xb7\x21\xa0\x61\xd5\x41\x00\x9e\x3b\x7c\x0e\xe3\x07\xd8\xec\xb7\xfb\x2d\xe8\x3c\x06\xcb\x4f\xd8\x19\xf2\xdf\xa1\x44\xbb\x5c\xb3\x83\x2f\x37\xff\xed\xd9\x88\xf7\xe4\x63\x7b\xc5\xef\x73\x0b\x77\x8f\xe9\x68\x67\x5d\x59\x3c\xcb\x59\x59\xdc\x1f\xae\x1d\x19\x82\x23\xd7\x8c\x3e\xc7\xae\xdb\x96\x78\xc6\x6d\x7f\xc6\x42\xf9\x0f\xcb\x95\x34\xf1\x12\xfe\xaa\x51\x99\x85\x74\xb7\x86\xf2\x12\x33\x4f\x70\xa1\x39\xfd\xa1\x32\x67\x84\x95\x16\x90\xe0\x72\xd7\xd3\xbf\xf0\xc1\x35\xe4\x26\x92\x09\xf0\x05\x6e\x11\xed\x68\x63\xb9\x61\x1a\x3d\xc1\xb1\x3a\x59\x90\x35\x69\xf3\x1d\xf1\xe2\xf0\x07\xc3\x71\x1f\x93\xe2\x9b\x18\xc7\xb6\xb3\x20\x6f\x4e\x16\xbc\xdc\x08\x55\xaa\x72\x30\x16\x07\x40\xfb\x9e\x0b\x87\x99\xe1\xae\xaa\x97\xdd\x73\xa5\x02\xad\x3e\xb7\x1c\xdf\xbe\x10\xbd\x57\x3e\xa8\xeb\xf3\x33\x47\x4e\x3f\xa1\x6d\xa6\x75\x88\xcc\xbd\x60\xb0\x32\x7f\x5c\xe3\x03\xed\x5b\x40\x06\x89\x2e\xe8\xba\x9f\x79\x48\xd8\xbc\xdf\xc5\x67\xcf\x2e\xa1\x84\x49\xe4\x19\xee\x3a\x81\xd8\xeb\x11\x8d\x50\xc7\xb7\x5c\xb6\x01\xed\x20\x54\x17\xeb\xeb\x58\xb6\xb1\xd4\x65\xf0\x2f\x6f\x2d\x25\xdd\x1d\xd3\x97\xbd\x93\x3a\x96\x57\xe6\xd8\xfb\x97\x53\xd4\x0f\x61\xcd\xc5\x94\xcf\xd2\x03\x0a\xe1\x40\x01\x6d\x3c\x9e\xb1\x6e\x8b\xcb\x73\xf7\x0a\xe8\x8d\x8f\x7c\xbc\x34\xdd\x79\x29\x15\x10\x14\x38\xd6\x80\x77\x75\xec\xfe\x90\x75\x81\x05\x34\xe9\x02\x35\x44\x0e\x2a\xa0\x70\xcc\x6e\x62\xd3\x5d\xa7\x69\x0b\x39\xe1\x9c\x64\x33\x6a\xca\x9d\xa7\xf4\x43\x8b\x4d\x33\x8e\xcc\x6d\xd8\x05\x8f\x43\x69\x21\x8c\x9e\xe8\x19\x10\xc3\xd9\x71\x47\x1c\x79\x80\x7c\x18\x30\xf0\xef\x56\xff\xdd\xea\x47\xd4\x87\xf8\x59\xbf\x04\x88\x5c\xf3\xc9\xef\xc2\x2e\x7c\x3c\xdd\xa0\x88\x52\x5f\x05\xeb\x87\xc1\xfa\xb9\xc7\x07\xce\xdc\xa4\x71\x2c\xca\x53\xf3\x73\x49\x26\x9e\x8a\x88\x12\x78\x03\xcb\xdb\x02\xf7\xf3\x12\x46\xe1\x2b\xf1\xa1\xeb\x1e\x6a\x94\x01\xe5\x7e\xaf\x26\xa6\xa6\x32\x74\xfd\xef\x0c\x43\x74\x11\x26\x46\xfc\x0a\xcb\xf6\x8c\x73\x0a\x17\xff\x27\xf8\x8a\x74\x60\x2d\xd2\x2a\x12\xbd\x0e\xb0\x30\x96\x2a\x05\xd8\xe3\x84\xfd\xd4\xdd\xfc\x46\x32\x3e\x44\xdd\xbb\x6f\x7b\xc8\xb8\xe1\x9d\x7c\xec\xb9\x30\x44\x14\xd6\xf2\xac\xce\x72\xf5\x01\x4d\xfe\x2a\x11\x13\x58\xe0\x1c\x0f\x4d\xcf\xf5\x60\x61\xa0\x2c\x1e\xee\x79\x7d\x73\x01\x13\x69\x4a\xa9\xac\x65\x35\x8b\x0a\xc0\xac\x36\x0b\x6a\x4d\x13\x93\xe8\x58\x4f\xf7\x57\x1c\x23\x3a\x2e\x3e\x11\x9f\x42\x85\x55\x3a\x57\xe2\xc9\x56\x76\xc8\xa9\x49\x63\x81\x3e\xf2\x81\xc5\xd4\xc9\x59\x95\xa5\xb5\x48\xac\xfe\x5d\xce\xd5\x0a\xea\x44\x67\x5f\x81\x17\x98\x4c\x6a\x43\x28\x2e\xdf\x4a\x77\x17\xfd\xd2\x76\x0c\x8e\xa5\xad\x45\x63\x73\x72\x2e\xa9\xaf\x68\x78\x77\x12\xbb\x03\xac\xb0\x8d\x7f\x4f\xe8\x81\x9f\xbc\x91\xf7\xaf\xdb\x24\x7c\x51\xa8\xa5\xf3\xaf\xc0\x95\x95\xff\x14\x6e\x41\xb0\xda\x73\xa4\x5a\xa0\x37\x6b\x59\x40\xd9\x11\xa5\xe9\x38\xde\x08\xd2\xee\xe6\x2e\x8b\x43\x3a\x35\xca\xa3\x6c\x2d\x19\x02\xcf\xaa\x72\x4f\xac\x8c\xe2\xdb\x44\x6d\x9d\x86\x08\xde\x50\x5f\xcc\x2a\x4c\x0e\xc2\x3c\x3b\xf8\x68\x5c\x9a\xf4\x3e\x72\xcc\x17\x3b\x77\xbf\x1b\x69\x94\xd2\x41\x9e\xb4\x3b\xa8\x72\xa6\x6a\x73\xa0\xff\x57\xff\xc4\x72\x0f\x02\xbc\xba\xe1\x55\x2e\x07\x23\xb8\xee\xfc\x42\x12\xa3\x0a\x73\xcd\x28\xe6\xcd\x47\x20\x36\xe1\xe9\x69\x63\x00\x97\xb6\x76\x90\x4e\x9f\x54\xea\x61\x5f\x93\x32\xea\xda\xc8\x26\xcc\xa7\x7f\x73\xe3\xc9\xff\x11\x6d\xe5\xa8\x34\x7b\x17\xc9\x42\xb5\xcc\x90\x0b\x6b\x38\xe6\x4c\x66\xc1\xfd\x2f\x0a\x8c\xe6\x50\x77\xfe\x83\x9a\x69\x77\x3e\x4c\x3a\x22\xbe\x0e\x1a\x8b\x4e\xae\x46\xaa\x04\x5e\xe6\xf4\xf1\xd9\xc5\x52\xaa\x7f\x82\x1b\x2a\x2c\xbf\x40\xae\xd4\x1e\xad\x44\xeb\x1c\x79\x05\xad\x95\x47\x75\xd1\x0c\x62\x90\x13\x6f\x61\x3e\xe3\xd1\xfc\xef\x6b\x5a\xc3\x1f\xfa\x12\x55\x82\x30\xf0\xf3\x20\x2f\x19\x6f\x8f\x07\xad\xca\x30\x97\xc6\x50\x96\x9b\x3f\x45\x8a\x5d\x54\x43\xcc\xb6\x7c\x19\x24\x6b\x19\xe9\x5b\xb4\x6e\xec\x6e\x0f\xf2\x17\x49\xa5\xb7\x98\x60\x76\xb0\x81\x36\x8d\xce\x15\x72\xa5\x26\x3c\xe8\x39\xa9\xc9\x94\x9f\xe7\xcb\xa0\x34\x57\xc2\xdc\x21\x57\xad\x00\xf6\xf9\x8c\xa7\xb0\x8e\xd3\x2a\x17\x3a\x7d\x68\x44\xad\x76\x61\xe6\x80\x40\xdf\xe7\xa5\x4f\x57\x0b\xce\xb6\x50\x66\x37\xfe\x08\x36\xf0\x32\x6b\x52\xc9\xbb\xad\xb0\xc5\x66\x36\xc8\xb6\x6d\x16\x57\x13\xab\x02\xf3\x2e\x35\x33\x76\xb1\x88\x41\x7b\x94\x60\x25\x01\x96\x92\x3a\x57\x66\x0d\xc8\x0f\x58\x03\xf6\xab\xbd\xcd\x76\xc4\x1d\xb1\x67\x04\x73\x24\x23\x83\xf4\x68\xbc\x7e\xab\x9f\xf9\x62\x7d\x67\x47\xfc\x03\xf0\x09\x67\xd4\x81\xdc\xe6\xdf\x9b\xce\x40\xef\x93\x9c\x25\x1a\x56\x0e\xdf\x50\x86\x3f\x57\xb5\xcf\x58\x00\xe6\xc9\x7b\x19\xd7\x38\xdd\x57\x40\x07\x96\xe0\xea\x29\x45\x9c\x8e\xba\x09\xbd\x9d\x0f\x75\x2e\x9e\xda\x27\x49\xbf\x29\x57\x6e\x9c\x9f\x0f\x44\x2a\x8c\x9a\x2a\xfc\x75\x02\x33\x73\xfc\x18\xb9\x0a\xe4\x5d\xfb\xea\xb7\x1a\xeb\xb4\xfd\x29\x04\x39\x22\x73\xc3\x45\xfd\x9d\x5f\xe6\x45\x68\x80\x08\x28\x58\x5e\xc9\x7b\xcc\x30\x00\x07\x94\x78\x22\x1d\x31\xf1\x9e\x90\x76\x0b\xd7\x5a\x54\x54\x57\xd6\x85\xd7\x7a\x77\xa4\x59\xf9\x93\xe5\xef\xe9\xb4\x8e\xd8\xed\xf1\x1a\xab\x77\xd4\x08\x40\x22\x45\x82\xe7\xc0\x1f\xc0\x12\x6f\xd5\x53\x59\xc5\x6f\x2a\x27\xcb\x89\x50\xc3\x84\x9e\x84\xa3\x73\x92\x70\x3c\xb6\x50\x9d\x6b\x30\x39\x44\xf3\xe7\x61\x1c\x7e\x82\x32\xb2\x08\xc1\xec\xae\x21\x7b\xd2\xed\x6c\x3b\x0e\x9a\xca\xf7\xea\x96\x30\xf8\x1c\x38\x41\x58\xa6\x41\xcc\x07\x70\x7a\xd5\x2d\xfc\x29\xe9\xe8\x31\x91\xb8\x19\x79\x1e\xb5\x8e\x2b\xc6\x11\x0d\x54\x08\x4e\x49\x66\x6d\xfd\xf4\x29\x41\x5e\x25\xcf\xea\xe3\xf4\x33\x64\x04\x37\x82\xc2\x4b\xef\xcc\xae\x16\x9a\x74\xa7\xf4\xf7\x6c\x4d\xa7\x61\xaa\xd8\x14\x33\xd9\xcc\x16\xc7\x96\xf8\x95\x3d\x91\x7b\x97\x9b\x2d\x36\x20\x66\xd5\xa2\x48\x11\x95\x39\xd1\x0c\x33\x86\x8e\x73\x2d\xfc\xd3\x05\x97\x61\xb0\x58\x64\x03\x4d\x23\xe0\x38\xd0\xaa\x36\x89\xa8\xd7\x37\x4c\x71\x71\x22\xcc\x0c\xf2\x1b\xe2\xd4\xc8\x21\x17\xbc\xc0\xf7\xda\x82\x4f\x55\x31\x85\x18\x9f\x21\x3d\x4f\x98\x43\x0f\x9a\x17\x1f\x11\xd2\x51\x54\xd0\x23\x8d\xc7\x14\x2c\x71\x9c\x3e\x22\x6d\x3d\xaa\xef\x0c\x15\x8a\x93\x51\x9f\x48\xc3\xed\xb6\x77\xe3\x69\xe1\xd2\x5e\x9e\x47\x3b\x15\x07\xf5\x8f\x4a\x56\x07\xb2\x07\x15\x1c\xdc\x05\x04\x01\x91\xab\x66\x73\xc8\x75\xe5\xc9\x96\x5e\x5f\x57\xa8\x15\x25\x36\xbc\x8a\x92\xcb\xc2\xc9\x5d\x3b\x95\xe3\xa0\x06\xb7\xbe\x22\x7f\x90\x7b\x64\x66\x9b\xc8\x88\x26\x00\x71\xf8\x6d\x41\xd7\xc8\x07\x9f\xb5\x63\x61\x5e\x58\x38\xb5\x35\x57\x3f\x12\x78\x9a\x00\xec\xc4\x18\x67\x83\xce\xd2\x44\x66\x5e\xa1\xb6\x43\x6b\xb3\xa7\x77\x18\x0f\x54\xbb\x8c\xa4\x58\x0e\x5a\xe3\xed\x68\x55\x00\x23\x93\x4b\x37\xe2\xe5\x4e\x13\x7e\x2a\x5d\x15\xb1\x3a\x04\x09\xe2\x21\xc4\x35\x75\x7f\x4f\x9a\xef\x5e\xe5\xa6\x1b\x0a\x8d\xc6\x1a\x2c\xae\x84\x82\x49\x24\xfd\x2e\x5a\x15\x4e\x8b\xa3\x28\x46\x49\x0c\xc5\x0f\xb5\x3e\xba\xf3\x4c\x43\xa0\xf1\xac\x78\x20\xeb\x55\x41\x7f\x2c\x3a\x39\x85\x73\xc9\x8f\xbb\x3d\x10\xc2\xe6\x37\xf0\x1d\x09\x85\x33\x47\xce\xbd\x99\xaf\x10\x11\xd9\xef\xd4\xad\x65\x3a\xc4\xcc\x39\x81\xa0\x01\x01\xba\x71\x0d\x9c\x5f\x9d\x2b\xed\x79\xde\x77\x65\x10\x94\x1e\x10\xa1\x23\x0f\xc6\x42\xd8\xb5\x9a\x51\x78\x4e\x4b\xb4\x66\x3a\x0b\x14\x94\x64\xf3\x3c\x67\xf7\x5c\xdd\x13\xb1\x8c\xf3\x01\x43\xdf\x22\x5a\xb7\xcf\x04\xc2\xfc\x14\x68\xda\x0b\x8b\x84\x09\x32\x49\x92\xb7\x82\x4e\x8f\xb7\xb7\x17\x52\x62\xed\xa9\xdb\x43\x01\xfe\x2f\xf3\x98\xdc\xc8\xf8\xc2\x18\x93\xda\x22\x85\x15\x12\x2c\xff\x0f\x0a\xd4\xb2\x23\x1a\x92\xcd\xc6\x01\xa5\xb8\x09\x64\xdf\xcf\x47\x36\xb1\x92\x69\xac\xb0\x4f\x01\x14\x02\x16\x25\xfc\xe0\xb3\x22\x9b\x52\x96\x64\x96\x1a\xf2\x43\x64\xa2\x7e\xc7\x6d\x9e\xeb\x5e\xea\x9e\x11\x08\x92\x48\x8f\x3e\xa7\x36\x86\x51\x4d\xc4\xbf\xbf\x98\x82\xf7\xe4\x94\x65\xf9\x20\x6e\x7c\x03\x8f\x38\x6e\xe8\x98\x54\x0b\xaa\x21\x39\x2b\x4e\x8d\x6e\xcb\xb8\x09\x2c\x3b\xa6\x8e\x8d\x96\x6d\xcd\xe0\xdb\x74\xd4\xb0\x20\xcc\x14\x47\x34\x26\x18\x6e\xdc\x0a\xa7\xe3\xcd\xa5\xd0\x78\xf0\xc3\x6d\x08\x9b\x0b\x1f\xa1\x44\xe7\xff\x59\x3b\xb9\x7f\xd4\x49\x4b\x65\xe6\xd6\x43\xa3\x98\x5f\xcc\xe4\x6d\x63\x42\x8e\xc0\xdf\x38\xf1\xf3\x4d\x09\xf9\x72\x68\x98\xce\x48\x0a\xfc\x3c\x64\xc1\xc4\x52\x2d\xe0\xdc\x81\x41\x7e\x2f\xe6\x3e\x43\xbf\x61\xb2\x38\xb1\x73\xd1\x20\xae\xb2\x5b\x3e\x7f\x46\x85\x2c\x52\x29\x02\x89\x31\x0b\x44\xc3\x64\x8b\x6a\xa8\x2e\x0f\x0d\x72\xe5\x14\x9e\xf4\xcb\xcc\x5c\x2e\x75\x86\xe3\xd3\x3b\x41\x0d\xe8\x2f\x25\x64\xd9\xf6\xe5\x13\x94\x87\xf6\xde\x24\x04\xc3\xb0\xe5\x75\x06\x71\xad\xbb\xed\x7d\x21\xe0\xcc\x0b\xea\x79\xcb\x6e\x4e\xa7\x0f\x6f\x9f\x45\x45\xf2\x30\x02\x98\x16\x61\xd5\xc4\x2c\x19\x73\x30\xfe\xeb\x77\x28\x5d\x86\xaa\xb3\x2a\x10\x70\x15\x99\x77\x3e\x4b\xb1\x54\x40\xaf\x3d\x35\xab\x3d\xd1\x92\x9d\x09\x4a\x99\x55\xae\xa4\xfb\xd9\x91\x6c\xf7\xdb\x55\x52\x67\xcc\x2a\xce\xeb\x92\x89\x17\x88\x1f\x66\xf0\xd3\x35\xd8\xfb\x67\x7b\x01\xf0\x6d\x95\x0e\x36\xa9\x68\x39\x19\x6f\x94\x86\x7e\xb8\xb3\x4b\x46\x83\xf2\xe2\x5b\x07\x01\x6b\x0c\x3b\xa8\x3e\xa5\x57\xc5\xdd\x5d\x6d\x56\xa7\xf5\x93\x2d\x8e\x4e\xd7\x2f\x14\xa5\x23\xcc\x4e\x90\x11\xda\xce\x1b\xf3\x32\x86\x01\xa0\x3c\xfb\x30\xea\x34\x16\x18\x22\xe0\xf0\x33\x76\x1c\xfe\x90\x1e\x93\xa3\x58\xf9\x79\x7a\xab\xf1\xf9\xfe\xa9\xe3\x1c\x59\x4a\xc0\xf2\xf9\x98\xdd\xe9\xb7\x71\xae\x11\x20\xc8\x04\x8c\x96\xeb\xc4\x31\xee\x37\x5d\x78\x27\x67\xf8\x6b\x5f\x12\xf4\x60\xa8\x8a\xa7\xa5\xf6\x2e\x6b\x2b\x8f\x04\x99\x32\x2a\x7c\x43\x02\x17\xf1\x53\x14\x8b\x76\x9e\x76\xca\xad\xac\x5e\xe2\x38\x24\x6c\x52\xe3\xe5\x4e\xdf\x3c\xe1\x0b\xb1\xab\x8f\x5e\x40\x9c\xff\xc6\x54\xcd\xed\x2d\x13\xc6\x13\xaf\xba\x51\xce\x05\x50\xa4\x53\xc7\x93\x6a\x25\x24\x6e\x04\x3d\xbe\xf3\x62\x9d\x0f\x98\x6c\x17\x48\xbe\x0d\x0a\x0a\x35\x53\x48\xa5\x87\xe6\x70\x9b\x66\x04\x2f\x2c\x1a\xac\xdb\xf4\xea\xb1\xfe\x11\x0f\x78\x86\xaf\xdd\xee\x92\xad\x49\x89\x72\x52\x46\xf6\x1b\x1e\x27\x9a\x0f\x88\x74\x8f\xff\xe1\xfd\x98\x85\xd3\x7f\xe4\x36\x43\x6a\x0a\xec\xee\x87\x84\x86\x2f\x6b\x1e\x22\x3c\xc3\x22\xfa\x65\xc7\x5b\x5a\xf2\x94\xdb\x82\x5d\x9f\xe9\x2c\xd3\x83\xd1\x9c\x1b\x73\xf6\x5d\x4d\x9a\xc1\x7b\xb8\x5a\x66\x6d\xa1\x99\xda\xd2\x52\xe4\xaa\x1e\x0a\x84\xf2\x46\x8e\xea\xa6\x0c\xe7\x19\xb1\x10\xf0\xd6\x39\x45\x69\x8c\x31\x1a\x06\xa9\xf1\x49\x89\x9f\x08\x95\x85\xe3\xe8\x7b\x70\x23\xb1\x5f\xd2\x9f\x00\x83\xf1\xb5\xa6\x1b\x89\x48\x08\xa5\xfd\x9f\x9d\x11\xb8\x00\xff\x71\xdb\x46\xf0\x0a\xa5\xb1\x87\x07\x24\x4c\x4f\x16\x5a\xa8\x28\xe8\xbf\x61\xc4\xfc\x0c\xca\xf2\x22\x6d\x42\x49\x6c\xf7\x95\x05\x81\x5f\x08\x8d\x4f\x2a\x9e\xcb\xb1\x38\x75\x65\x32\x9f\x98\x71\x93\x66\xa8\x2a\x2f\x4e\xc6\xdf\x76\x64\x3f\x6b\x68\x5b\x50\x67\x47\x09\xa9\x18\x74\x63\xb8\x25\xb2\x9c\x2c\xc2\xe0\x0a\xf4\xb0\x1f\xfe\x65\x47\x17\x69\x6b\xc4\xb1\x0a\x14\x3d\xcc\x1c\x0d\x95\xe2\x54\xc3\xff\x42\x52\xb5\xa9\x86\xba\x01\xee\x2a\x9e\x3e\x76\xad\x3f\xa6\xbe\x0e\x64\xfa\x46\x36\x8a\x45\xbf\x09\x15\xbe\xaf\x31\xed\x2e\xd5\x90\x85\xd0\x7a\xba\x2f\xb3\x28\x88\xc8\x94\x14\x7e\x82\x0a\x2f\x66\xd9\x74\xe6\x69\x09\x28\x45\xd7\xd0\xf5\x45\x51\x35\x43\x15\xc5\xef\x75\xa8\x9d\x57\x10\xf8\x56\xb1\x37\x02\x67\xfe\xb7\x01\xd6\x2f\xd3\x2a\x80\x78\x14\x90\xf6\x1f\x07\xcd\xe1\xa7\x29\xa2\xcb\xe4\xc1\x2a\xd4\x4d\xac\x46\xf2\x28\xd2\xfb\xf8\x42\xd8\xb0\xb6\xb5\xe6\x63\x26\x65\x10\x25\xb0\x17\xaf\x35\x6f\xa4\x06\x10\x3d\x8f\xcc\x5f\x81\xae\x41\x13\x00\x0d\x65\x7b\x2d\x37\xad\x8f\xbe\x8a\x9e\x3e\x6c\x47\xad\x50\xe3\x00\xfa\x77\x00\xb8\x52\x2b\x0c\xe7\x45\xfa\xfa\xda\xdd\xf3\x84\x6a\xeb\x34\xe0\xc0\xa6\x52\x87\x58\x1a\xd9\x55\xdb\x31\xee\x7b\x2d\x38\xcc\xf5\xf0\x03\x5f\xe0\x46\xc3\x0c\x34\xc3\x84\x7a\x0e\x15\x48\x10\x4a\xf2\xc7\x29\xd7\x1e\x84\xa9\x4f\xed\x30\x60\xf6\xa6\xd9\x7f\x0b\xc0\x7b\xc4\xc7\xd9\x46\x59\x98\xba\x1f\x8b\x34\x76\x34\xc7\xc5\x25\x0f\x42\xca\x97\xe4\x7b\xfb\x36\x9b\x48\x63\x7b\xb5\x4f\x31\x73\x01\xcd\x87\x8d\x08\xd1\x37\x73\x21\xd4\xe3\x25\xf4\x49\x0c\x38\x6d\x18\xda\x38\xb7\xb0\xaa\x05\x28\x35\xcc\x36\xac\xe2\xc8\xbb\xde\x18\x71\x4d\x98\x7a\x65\x8c\xa0\xff\x0e\x52\x8e\x83\x03\x6f\x56\x06\x44\x00\x66\x48\xe7\xfb\x20\x10\xac\x01\x70\x33\x20\x65\xdf\xbb\x92\x43\xdd\xaa\xe7\x89\xdd\x55\x65\xe8\x15\x5d\x84\x77\xe3\x68\xf7\x09\xda\x6f\xc3\x0b\xd8\x03\x16\x56\x05\xbb\xb6\x3a\x3d\xd9\x02\xce\x77\x02\x32\x86\x30\xee\x8b\xc5\x4b\x65\xf7\x17\xc3\xc0\x5d\xc2\xb3\xed\x3f\xca\x56\x36\x13\xfa\x8b\x71\x48\xe3\xa8\xa2\x9a\x3e\xc1\x96\x7c\x55\x82\xf6\x17\x25\x97\x5b\xdd\xb0\x52\x18\x58\x35\xc9\xb7\x49\x61\x88\x9e\x44\x7c\x71\xd8\x45\x86\x3c\x2b\xfc\x73\x90\x2d\x5e\xae\x0c\xca\xa1\x65\xc8\xa1\x31\xd0\x5b\xe5\xd9\x4f\xc4\x37\xfe\xc5\xee\x30\x3c\x57\xb4\xac\xbc\xae\x9b\x9e\xdc\x34\x12\xe0\x4d\x8f\x1c\x1f\x2a\x82\x80\xa2\xd7\x8e\x17\x20\x0f\xbf\x2b\xd4\x66\xe4\xab\x68\x20\x79\x6b\x76\x05\x65\x4a\x0e\x05\x6e\xaf\xaa\x3b\xfc\x41\xdf\x2c\xe8\x58\x7d\xdb\xd4\x01\x67\xe6\x4a\x68\x5b\x9a\x59\x49\x7e\x10\xac\xbb\xd4\x21\x78\xf5\x36\xa1\x9a\x64\x2c\x84\x24\xb7\x65\x81\xbe\x4a\x40\xc2\xd7\x45\x27\x4b\xec\x2e\x9a\x55\xb2\xba\xd0\x85\x1a\x85\xd9\xa5\x11\xe0\x68\xc3\x8b\x61\x34\xa1\x7f\x43\x2c\xaf\x06\xb7\x5f\x35\xd9\x92\xcb\xa5\x2b\xb3\xde\x4f\x1d\x76\xc1\x2d\xf8\xd5\xc6\xd0\x9b\x0f\xb4\x80\x60\x66\x97\x74\xfe\x82\x20\xc5\x46\xfb\xb6\x47\x29\x7f\x8b\x72\x49\x0e\xd5\xc7\xba\x5a\x65\xe5\xc7\xb6\x21\x8c\x85\x20\xd3\xb3\x21\x1b\x98\x03\x92\x0c\x40\x7a\x84\xa6\xa4\x0e\x8e\xb7\xb6\xf1\xac\x88\x8b\xb6\x61\x40\x36\x72\xa7\xe5\x44\xc2\x54\xd7\x92\xbe\x5c\x41\xfe\xc4\x15\xf2\x29\x85\xbf\x24\x9c\x5d\x10\x53\x9a\xb9\xde\xcc\x4e\xbe\xa5\xa9\xf1\x59\xa8\xc0\x52\x3f\x7d\x54\x88\x38\xdd\xbf\x0e\x6a\xe8\x1e\x48\x77\x63\x9c\x70\x44\x15\xd9\x26\x51\xc7\xfd\x73\x46\x3a\x45\x71\xb6\x59\xf0\x85\xcc\x9f\x4a\xb5\x31\xa0\xdc\x18\xe5\x57\x86\x04\x7b\xef\xb2\x14\x1d\xf3\x47\xa9\xff\x9a\x47\x69\x14\x1a\x99\x20\x00\xc2\xa5\x85\x87\x1b\x1d\x6a\xeb\xd9\x5c\x68\xd8\x8a\x57\xa9\xa2\x15\x5f\x8f\x54\x18\xb4\x71\x95\x41\x22\xb6\x6c\xca\xcf\x46\xd9\xb9\x13\x09\xec\x69\x6c\x1c\x89\xe2\x45\x0f\x66\x9c\xcb\x47\x2c\x6d\xae\xc6\xbd\x8f\xec\x78\x0a\x2b\x73\xe4\x64\xe4\xc8\x31\xd7\xa0\x1a\xe5\x8f\xca\xf9\x68\xde\x89\xad\x4d\x96\x18\xdc\x36\x3c\xaf\x83\x40\x34\x11\xc9\x48\x50\x94\x80\x67\xa4\x07\x6a\x78\x63\xe4\xe9\x8d\xb5\xe7\x1b\xb0\x55\xe7\xb3\x85\x3e\xbb\x26\x4b\x9b\x59\x63\x44\x09\x90\x4c\x3c\x55\x59\x92\x57\x76\xb0\x57\x92\xcd\x81\x11\xdd\x9a\xa5\x6d\x62\x44\x94\x2c\x75\xf0\x12\x26\x77\xdd\x6c\xba\x70\xe1\x8c\x8e\xd3\x15\x2c\x34\xec\x1b\xf7\x23\x89\xde\xcc\x6c\xe9\x36\xf0\x74\xf8\x2a\x41\xbf\x94\xad\xcf\x4e\x97\x40\xdf\x51\x35\x5c\xb3\x1d\x0d\x03\xa6\xe9\x5c\x79\xf5\x51\x65\x13\x88\x28\xe8\xec\x0b\x52\x9b\x2b\x58\xd6\x21\x54\x7f\x97\xf6\xe9\x50\x0f\x2d\x90\xde\x82\x5d\x34\x06\xe8\x76\xbc\x7e\x29\xe6\xd3\xa8\x98\x97\x01\xb2\x10\xce\xae\x9a\x91\xa8\x91\xac\x6c\x36\x6c\xa4\x06\x3c\xb8\xee\x20\x63\x12\xaf\x07\x03\xed\xc1\xd3\x7c\x25\xff\xa3\x54\x2a\xb5\x56\x15\x02\x8c\x4d\x9b\x67\x13\x8a\x27\xb6\x2f\xb8\x3d\x97\x3b\x8f\xc8\xe2\xfb\x11\x44\x78\x61\xec\xba\xea\x81\xc5\x48\x96\xff\xe5\xca\xc1\xcf\x50\x92\xcc\x71\x86\xa2\xa1\xaa\x8d\x36\x3c\x8d\x41\xad\x97\x18\x20\x9f\xf2\x62\xed\x02\x19\xba\xf3\xd5\xe8\xaf\xfd\xbd\xfa\x97\xa5\xc5\x08\x1d\x40\x7d\xac\x6a\xbf\x19\xc1\x85\xa2\x2e\x0a\xd0\x42', 2) \ No newline at end of file diff --git a/cs101courseware/dist/deploy_cs101.py b/cs101courseware/dist/deploy_cs101.py new file mode 100644 index 0000000000000000000000000000000000000000..e91aa9d11f0f6e027322a56e891313bf3ea8ce22 --- /dev/null +++ b/cs101courseware/dist/deploy_cs101.py @@ -0,0 +1 @@ +__pyarmor__(__name__, __file__, b'\x50\x59\x41\x52\x4d\x4f\x52\x00\x00\x03\x06\x00\x33\x0d\x0d\x0a\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x40\x00\x00\x00\x5a\x03\x00\x00\x00\x00\x00\x10\x01\x31\x7d\x8d\x12\x6f\xeb\x54\x4e\x3b\x00\xfe\xf2\x81\xbc\x33\x00\x00\x00\x00\x00\x00\x00\x00\x90\x71\x7e\x4b\xe3\xcb\x29\xa4\x3b\xc9\x84\x93\x60\x52\x24\x27\x5b\x3d\xc8\xac\xd2\x68\x02\x56\xd0\xfe\x5c\x48\x26\x94\x99\xa0\x3d\x35\x45\x9a\x9f\xfd\x3a\x9e\x17\xfd\x43\xd1\xec\x81\x6c\x71\xc5\xdb\x7b\xd7\x61\x5e\xb8\xa2\x77\xe3\x72\xc8\x87\x11\x6a\xb9\x8b\x51\xf9\xb2\xfa\x77\x49\x02\x9d\x7e\xec\x59\x4f\x8c\x77\x10\x1c\xda\xe4\x5e\xdc\xef\x50\x69\xf0\x94\x74\x57\x31\x91\xa9\x24\x0a\x43\xbc\xee\x41\x06\x6f\xec\x81\x38\x83\x4f\x45\x57\xc8\x68\x04\x88\xdd\x2e\xd9\x67\xda\xaa\xb1\xe4\x09\xae\xb0\x45\x9f\x62\x5f\x9c\xe8\x4d\x02\x93\x7f\xb2\x93\xd6\xe8\x4c\xc9\x09\xa8\x51\xbe\xde\xf6\xd1\x37\x9f\x37\x68\xbe\xbf\xe6\xd5\x29\xb7\x03\xd0\xfb\x29\x91\xce\xe2\xfe\xd7\xbb\x73\xbd\x09\x07\xfd\x43\x40\x0e\xd2\xb0\xc9\xd4\xc7\x22\x99\xb3\xf2\x92\x61\x30\x99\xe7\x90\xb3\x1f\xfe\x20\xd6\xf3\x9d\x18\xed\x06\x22\xd4\xe7\xc4\xb9\x9c\x3f\x34\x82\xe7\x85\xd6\x29\x56\xbc\x1b\x6b\xbd\x66\x22\x53\x83\xd9\x5d\x46\xf5\x8f\x42\x35\x25\x91\x71\x91\x7b\x04\x33\xea\xf3\x51\x1e\x26\x80\x84\xda\x61\xfb\x8a\x17\x84\xc5\x10\x5b\x20\xac\x72\xf6\xa2\x94\x44\x17\x27\x1a\xad\x04\x9b\x3a\x17\x08\x6a\x68\x1f\xc7\x26\x6c\xd9\x9b\x65\xde\x4e\x44\x2d\x62\xa8\x3f\x5c\xfc\xd5\xf4\xdb\x1d\x61\xca\xeb\x9e\xed\x8e\xb6\x63\xca\xf4\x67\x16\x8e\x44\xd1\xf0\xd5\xec\x42\x54\x88\x0d\x47\xe1\xe8\x3a\x78\xd9\x07\x91\x2d\x3d\x42\x24\xbf\x0f\x34\xa2\x53\x0b\xc2\x00\x3e\xd4\x04\x71\x0f\x45\xa1\x45\x13\x36\x6a\x54\xa4\x20\x5b\xa6\x49\x76\xaf\x47\xdf\x33\xb7\xeb\x42\x21\x3b\xab\x8f\xfb\x3a\x4e\x2e\x19\x1a\x17\x79\xf4\xc7\x62\x02\xb2\xee\xe6\x25\x95\x01\x57\x68\xcc\x54\x44\x1b\xd3\x85\x9b\x8d\x31\x75\xf3\xc3\x61\xd1\xb1\x13\x72\xcf\xde\x3c\xfb\xb6\x03\x89\xdd\x63\x26\xee\x57\x4c\x9d\x46\x35\x8a\xd4\x4f\xcb\x8b\xce\x79\xaa\x59\xaa\xf2\x6b\xab\xb4\x43\x63\x01\xd0\x43\xc1\x7d\xcd\xe2\xb5\x77\x17\xe7\x72\xf1\x7b\x54\xe6\x3e\x0d\x4c\x44\x21\x3d\xe6\xe8\xf6\x97\x40\xb1\x67\x6c\x66\x85\x33\xe5\xad\x99\x23\x07\x04\xb2\x83\xe9\xf4\xe0\x74\x31\xc3\x73\xbe\xce\x4b\xf5\x61\x16\x3e\xc1\x30\xad\x18\xe4\x30\xb1\x13\x9c\x0b\x2a\x58\xd5\xce\x99\xc4\x33\x1d\x9e\x58\x56\x3a\x46\xfa\x68\x5e\x8a\xe7\xa6\x27\xf9\xa4\x2f\x08\x51\xf1\x76\x4d\x03\x0e\x52\x99\x62\x13\x18\x2b\x2d\x8e\x08\x8e\xdb\x0b\xf8\xfc\x87\x82\xe1\x57\x84\x00\x38\x95\x8d\x4f\xa3\x2b\x87\xdd\x7f\x5d\x3c\x45\x18\xc5\xcc\xee\x48\x65\xdf\x21\x99\x74\x92\x42\xa3\xd8\xa0\x54\x99\x3e\xe4\x5c\x0d\x37\x4d\x4a\x2a\x8d\x1f\x0a\x4c\x7f\x6d\x9c\x65\x01\x1e\x52\x01\x26\x40\xc2\x66\xca\x5d\x5e\x92\xba\x1e\x2b\x86\x1a\xc2\xed\xbd\x25\xc2\x12\x7c\x00\x47\xc4\x28\xdc\xf8\x0a\x32\x51\x5a\x96\xdf\x79\xfe\xc1\x08\x8b\x15\x16\x09\x62\x37\x5a\x60\x13\x88\xdd\x9a\xeb\xb7\xd5\x0d\xf2\xbb\x76\x11\x16\x0f\xb1\x4c\x00\xd9\x22\x3c\x24\x0f\x59\x74\x75\x58\x23\x7f\x86\xaa\x84\xb1\x6b\x0d\x68\xc9\x67\xa8\x86\xc2\x3c\xaa\xa5\x86\xa5\x49\x62\xa0\xc0\xdf\x53\x79\x2f\x5c\x28\x36\x65\x30\x32\xe2\xfa\x8c\x26\x63\xbc\x11\x8f\xfc\x57\x68\x6a\xf9\x08\xb6\xc0\x55\x98\x59\x6e\x4e\xad\xff\xcb\xaf\x51\x6d\xb9\x00\x71\xba\x16\x46\x01\x9c\xbe\x25\x46\x40\x7f\x32\x51\xf1\xee\xe3\x39\x7f\x20\x65\x58\xb1\xc8\xff\xfd\xc4\x01\x8a\x59\x36\x81\xee\x78\x3f\xc5\x1b\x8c\x93\xdd\x60\x22\xf6\x02\x95\x28\xbd\x2a\xe6\x68\x87\x24\x4e\x7e\xa1\x7d\x67\xc9\xef\xb2\xeb\x82\x3e\x35\x36\x66\x62\xe1\xff\x5b\xcf\x43\x47\x36\x3b\xba\x49\xe4\xef\x58\xfd\xd1\x96\x9e\x15\x12\x52\x9c\xae\x78\x68\x6b\xa0\x11\x44\x1c\x19\xce\x80\xe2\x2a\xfd\x1e\x92\xf8\x69\xf9\x84\xc2\x22\x8e\xf1\xea\x55\x36\x8e\x3e\xd0\xf3\xdb\x1f\x00\x44\xcc\x77\x0c\xb8\x1a\x50\x7f\x1c\xb2', 2) \ No newline at end of file diff --git a/cs101courseware/dist/homework1.py b/cs101courseware/dist/homework1.py new file mode 100644 index 0000000000000000000000000000000000000000..8ed701c0f7870469469d51c0ae354474d8a880a6 --- /dev/null +++ b/cs101courseware/dist/homework1.py @@ -0,0 +1,40 @@ +import numpy as np +from sklearn.datasets import load_boston +from sklearn.linear_model import LinearRegression +from sklearn.metrics import mean_squared_error, r2_score + +#################### +# Question 1. Write a function reverse_list which accepts a list, and returns a new list +# with the same elements but in opposite order. +#################### +def reverse_list(mylist): + # TODO: Your solution here + result = [] + for k in mylist: + result = [k] + result + return result + +def simple_list_question(): + print("The reverse list function can reverse a list") + l = [1, 2, 3, 4] + print("List was:", l, "reversed version", reverse_list(l)) + + +#################### +# Question 2: Write a function which performs linear regression on the Boston housing dataset using scipy +#################### +def boston_linear(): + X,y = load_boston(return_X_y=True) # Load the dataset here + y += np.random.randn(y.size ) * 0.01 + # TODO: Fit a linear regression model and print the coefficients + lin_model = LinearRegression() + lin_model.fit(X, y) + print("Coefficients are", lin_model.coef_) + # TODO: Compute the RMSE here (on the training set) and print it + y_predict = lin_model.predict(X) + rmse = (np.sqrt(mean_squared_error(y, y_predict))) + print("RMSE is", rmse) + +if __name__ == "__main__": + simple_list_question() + boston_linear() diff --git a/cs101courseware/dist/pytransform/__init__.py b/cs101courseware/dist/pytransform/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f656a22d3028cd47aedc8a79e8c9607a847957ac --- /dev/null +++ b/cs101courseware/dist/pytransform/__init__.py @@ -0,0 +1,454 @@ +# These module alos are used by protection code, so that protection +# code needn't import anything +import os +import platform +import sys +import struct + +# Because ctypes is new from Python 2.5, so pytransform doesn't work +# before Python 2.5 +# +from ctypes import cdll, c_char, c_char_p, c_int, c_void_p, \ + pythonapi, py_object, PYFUNCTYPE, CFUNCTYPE +from fnmatch import fnmatch + +# +# Support Platforms +# +plat_path = 'platforms' + +plat_table = ( + ('windows', ('windows', 'cygwin-*')), + ('darwin', ('darwin', 'ios')), + ('linux', ('linux*',)), + ('freebsd', ('freebsd*', 'openbsd*')), + ('poky', ('poky',)), +) + +arch_table = ( + ('x86', ('i?86', )), + ('x86_64', ('x64', 'x86_64', 'amd64', 'intel')), + ('arm', ('armv5',)), + ('armv6', ('armv6l',)), + ('armv7', ('armv7l',)), + ('ppc64', ('ppc64le',)), + ('mips32', ('mips',)), + ('aarch32', ('aarch32',)), + ('aarch64', ('aarch64', 'arm64')) +) + +# +# Hardware type +# +HT_HARDDISK, HT_IFMAC, HT_IPV4, HT_IPV6, HT_DOMAIN = range(5) + +# +# Global +# +_pytransform = None + + +class PytransformError(Exception): + pass + + +def dllmethod(func): + def wrap(*args, **kwargs): + return func(*args, **kwargs) + return wrap + + +@dllmethod +def version_info(): + prototype = PYFUNCTYPE(py_object) + dlfunc = prototype(('version_info', _pytransform)) + return dlfunc() + + +@dllmethod +def init_pytransform(): + major, minor = sys.version_info[0:2] + # Python2.5 no sys.maxsize but sys.maxint + # bitness = 64 if sys.maxsize > 2**32 else 32 + prototype = PYFUNCTYPE(c_int, c_int, c_int, c_void_p) + init_module = prototype(('init_module', _pytransform)) + ret = init_module(major, minor, pythonapi._handle) + if (ret & 0xF000) == 0x1000: + raise PytransformError('Initialize python wrapper failed (%d)' + % (ret & 0xFFF)) + return ret + + +@dllmethod +def init_runtime(): + prototype = PYFUNCTYPE(c_int, c_int, c_int, c_int, c_int) + _init_runtime = prototype(('init_runtime', _pytransform)) + return _init_runtime(0, 0, 0, 0) + + +@dllmethod +def encrypt_code_object(pubkey, co, flags, suffix=''): + _pytransform.set_option(6, suffix.encode()) + prototype = PYFUNCTYPE(py_object, py_object, py_object, c_int) + dlfunc = prototype(('encrypt_code_object', _pytransform)) + return dlfunc(pubkey, co, flags) + + +@dllmethod +def generate_license_file(filename, priname, rcode, start=-1, count=1): + prototype = PYFUNCTYPE(c_int, c_char_p, c_char_p, c_char_p, c_int, c_int) + dlfunc = prototype(('generate_project_license_files', _pytransform)) + return dlfunc(filename.encode(), priname.encode(), rcode.encode(), + start, count) if sys.version_info[0] == 3 \ + else dlfunc(filename, priname, rcode, start, count) + + +@dllmethod +def generate_license_key(prikey, keysize, rcode): + prototype = PYFUNCTYPE(py_object, c_char_p, c_int, c_char_p) + dlfunc = prototype(('generate_license_key', _pytransform)) + return dlfunc(prikey, keysize, rcode) if sys.version_info[0] == 2 \ + else dlfunc(prikey, keysize, rcode.encode()) + + +@dllmethod +def get_registration_code(): + prototype = PYFUNCTYPE(py_object) + dlfunc = prototype(('get_registration_code', _pytransform)) + return dlfunc() + + +@dllmethod +def get_expired_days(): + prototype = PYFUNCTYPE(py_object) + dlfunc = prototype(('get_expired_days', _pytransform)) + return dlfunc() + + +@dllmethod +def clean_obj(obj, kind): + prototype = PYFUNCTYPE(c_int, py_object, c_int) + dlfunc = prototype(('clean_obj', _pytransform)) + return dlfunc(obj, kind) + + +def clean_str(*args): + tdict = { + 'str': 0, + 'bytearray': 1, + 'unicode': 2 + } + for obj in args: + k = tdict.get(type(obj).__name__) + if k is None: + raise RuntimeError('Can not clean object: %s' % obj) + clean_obj(obj, k) + + +def get_hd_info(hdtype, name=None): + if hdtype not in range(HT_DOMAIN + 1): + raise RuntimeError('Invalid parameter hdtype: %s' % hdtype) + size = 256 + t_buf = c_char * size + buf = t_buf() + cname = c_char_p(0 if name is None + else name.encode('utf-8') if hasattr('name', 'encode') + else name) + if (_pytransform.get_hd_info(hdtype, buf, size, cname) == -1): + raise PytransformError('Get hardware information failed') + return buf.value.decode() + + +def show_hd_info(): + return _pytransform.show_hd_info() + + +def assert_armored(*names): + prototype = PYFUNCTYPE(py_object, py_object) + dlfunc = prototype(('assert_armored', _pytransform)) + + def wrapper(func): + def wrap_execute(*args, **kwargs): + dlfunc(names) + return func(*args, **kwargs) + return wrap_execute + return wrapper + + +def get_license_info(): + info = { + 'ISSUER': None, + 'EXPIRED': None, + 'HARDDISK': None, + 'IFMAC': None, + 'IFIPV4': None, + 'DOMAIN': None, + 'DATA': None, + 'CODE': None, + } + rcode = get_registration_code().decode() + if rcode.startswith('*VERSION:'): + index = rcode.find('\n') + info['ISSUER'] = rcode[9:index].split('.')[0].replace('-sn-1.txt', '') + rcode = rcode[index+1:] + + index = 0 + if rcode.startswith('*TIME:'): + from time import ctime + index = rcode.find('\n') + info['EXPIRED'] = ctime(float(rcode[6:index])) + index += 1 + + if rcode[index:].startswith('*FLAGS:'): + index += len('*FLAGS:') + 1 + info['FLAGS'] = ord(rcode[index - 1]) + + prev = None + start = index + for k in ['HARDDISK', 'IFMAC', 'IFIPV4', 'DOMAIN', 'FIXKEY', 'CODE']: + index = rcode.find('*%s:' % k) + if index > -1: + if prev is not None: + info[prev] = rcode[start:index] + prev = k + start = index + len(k) + 2 + info['CODE'] = rcode[start:] + i = info['CODE'].find(';') + if i > 0: + info['DATA'] = info['CODE'][i+1:] + info['CODE'] = info['CODE'][:i] + return info + + +def get_license_code(): + return get_license_info()['CODE'] + + +def get_user_data(): + return get_license_info()['DATA'] + + +def _match_features(patterns, s): + for pat in patterns: + if fnmatch(s, pat): + return True + + +def _gnu_get_libc_version(): + try: + prototype = CFUNCTYPE(c_char_p) + ver = prototype(('gnu_get_libc_version', cdll.LoadLibrary('')))() + return ver.decode().split('.') + except Exception: + pass + + +def format_platform(platid=None): + if platid: + return os.path.normpath(platid) + + plat = platform.system().lower() + mach = platform.machine().lower() + + for alias, platlist in plat_table: + if _match_features(platlist, plat): + plat = alias + break + + if plat == 'linux': + cname, cver = platform.libc_ver() + if cname == 'musl': + plat = 'musl' + elif cname == 'libc': + plat = 'android' + elif cname == 'glibc': + v = _gnu_get_libc_version() + if v and len(v) >= 2 and (int(v[0]) * 100 + int(v[1])) < 214: + plat = 'centos6' + + for alias, archlist in arch_table: + if _match_features(archlist, mach): + mach = alias + break + + if plat == 'windows' and mach == 'x86_64': + bitness = struct.calcsize('P'.encode()) * 8 + if bitness == 32: + mach = 'x86' + + return os.path.join(plat, mach) + + +# Load _pytransform library +def _load_library(path=None, is_runtime=0, platid=None, suffix='', advanced=0): + path = os.path.dirname(__file__) if path is None \ + else os.path.normpath(path) + + plat = platform.system().lower() + name = '_pytransform' + suffix + if plat == 'linux': + filename = os.path.abspath(os.path.join(path, name + '.so')) + elif plat == 'darwin': + filename = os.path.join(path, name + '.dylib') + elif plat == 'windows': + filename = os.path.join(path, name + '.dll') + elif plat == 'freebsd': + filename = os.path.join(path, name + '.so') + else: + raise PytransformError('Platform %s not supported' % plat) + + if platid is not None and os.path.isfile(platid): + filename = platid + elif platid is not None or not os.path.exists(filename) or not is_runtime: + libpath = platid if platid is not None and os.path.isabs(platid) else \ + os.path.join(path, plat_path, format_platform(platid)) + filename = os.path.join(libpath, os.path.basename(filename)) + + if not os.path.exists(filename): + raise PytransformError('Could not find "%s"' % filename) + + try: + m = cdll.LoadLibrary(filename) + except Exception as e: + if sys.flags.debug: + print('Load %s failed:\n%s' % (filename, e)) + raise + + # Removed from v4.6.1 + # if plat == 'linux': + # m.set_option(-1, find_library('c').encode()) + + if not os.path.abspath('.') == os.path.abspath(path): + m.set_option(1, path.encode() if sys.version_info[0] == 3 else path) + + # Required from Python3.6 + m.set_option(2, sys.byteorder.encode()) + + if sys.flags.debug: + m.set_option(3, c_char_p(1)) + m.set_option(4, c_char_p(not is_runtime)) + + # Disable advanced mode by default + m.set_option(5, c_char_p(not advanced)) + + # Set suffix for private package + if suffix: + m.set_option(6, suffix.encode()) + + return m + + +def pyarmor_init(path=None, is_runtime=0, platid=None, suffix='', advanced=0): + global _pytransform + _pytransform = _load_library(path, is_runtime, platid, suffix, advanced) + return init_pytransform() + + +def pyarmor_runtime(path=None, suffix='', advanced=0): + if _pytransform is not None: + return + + try: + pyarmor_init(path, is_runtime=1, suffix=suffix, advanced=advanced) + init_runtime() + except Exception as e: + if sys.flags.debug or hasattr(sys, '_catch_pyarmor'): + raise + sys.stderr.write("%s\n" % str(e)) + sys.exit(1) + + +# ---------------------------------------------------------- +# End of pytransform +# ---------------------------------------------------------- + +# +# Not available from v5.6 +# + + +def generate_capsule(licfile): + prikey, pubkey, prolic = _generate_project_capsule() + capkey, newkey = _generate_pytransform_key(licfile, pubkey) + return prikey, pubkey, capkey, newkey, prolic + + +@dllmethod +def _generate_project_capsule(): + prototype = PYFUNCTYPE(py_object) + dlfunc = prototype(('generate_project_capsule', _pytransform)) + return dlfunc() + + +@dllmethod +def _generate_pytransform_key(licfile, pubkey): + prototype = PYFUNCTYPE(py_object, c_char_p, py_object) + dlfunc = prototype(('generate_pytransform_key', _pytransform)) + return dlfunc(licfile.encode() if sys.version_info[0] == 3 else licfile, + pubkey) + + +# +# Deprecated functions from v5.1 +# +@dllmethod +def encrypt_project_files(proname, filelist, mode=0): + prototype = PYFUNCTYPE(c_int, c_char_p, py_object, c_int) + dlfunc = prototype(('encrypt_project_files', _pytransform)) + return dlfunc(proname.encode(), filelist, mode) + + +def generate_project_capsule(licfile): + prikey, pubkey, prolic = _generate_project_capsule() + capkey = _encode_capsule_key_file(licfile) + return prikey, pubkey, capkey, prolic + + +@dllmethod +def _encode_capsule_key_file(licfile): + prototype = PYFUNCTYPE(py_object, c_char_p, c_char_p) + dlfunc = prototype(('encode_capsule_key_file', _pytransform)) + return dlfunc(licfile.encode(), None) + + +@dllmethod +def encrypt_files(key, filelist, mode=0): + t_key = c_char * 32 + prototype = PYFUNCTYPE(c_int, t_key, py_object, c_int) + dlfunc = prototype(('encrypt_files', _pytransform)) + return dlfunc(t_key(*key), filelist, mode) + + +@dllmethod +def generate_module_key(pubname, key): + t_key = c_char * 32 + prototype = PYFUNCTYPE(py_object, c_char_p, t_key, c_char_p) + dlfunc = prototype(('generate_module_key', _pytransform)) + return dlfunc(pubname.encode(), t_key(*key), None) + +# +# Compatible for PyArmor v3.0 +# +@dllmethod +def old_init_runtime(systrace=0, sysprofile=1, threadtrace=0, threadprofile=1): + '''Only for old version, before PyArmor 3''' + pyarmor_init(is_runtime=1) + prototype = PYFUNCTYPE(c_int, c_int, c_int, c_int, c_int) + _init_runtime = prototype(('init_runtime', _pytransform)) + return _init_runtime(systrace, sysprofile, threadtrace, threadprofile) + + +@dllmethod +def import_module(modname, filename): + '''Only for old version, before PyArmor 3''' + prototype = PYFUNCTYPE(py_object, c_char_p, c_char_p) + _import_module = prototype(('import_module', _pytransform)) + return _import_module(modname.encode(), filename.encode()) + + +@dllmethod +def exec_file(filename): + '''Only for old version, before PyArmor 3''' + prototype = PYFUNCTYPE(c_int, c_char_p) + _exec_file = prototype(('exec_file', _pytransform)) + return _exec_file(filename.encode()) diff --git a/cs101courseware/dist/pytransform/__pycache__/__init__.cpython-36.pyc b/cs101courseware/dist/pytransform/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75bb2539be65062814dda6c5080880056d0f8c70 Binary files /dev/null and b/cs101courseware/dist/pytransform/__pycache__/__init__.cpython-36.pyc differ diff --git a/cs101courseware/dist/pytransform/_pytransform.dll b/cs101courseware/dist/pytransform/_pytransform.dll new file mode 100644 index 0000000000000000000000000000000000000000..c121a4aa51ac71974b6ca0997006bfec3edb2673 Binary files /dev/null and b/cs101courseware/dist/pytransform/_pytransform.dll differ diff --git a/cs101courseware/homework1.py b/cs101courseware/homework1.py index 8ed701c0f7870469469d51c0ae354474d8a880a6..15620b0ffb74d843b1215536d7a9d2660d7617f0 100644 --- a/cs101courseware/homework1.py +++ b/cs101courseware/homework1.py @@ -12,6 +12,7 @@ def reverse_list(mylist): result = [] for k in mylist: result = [k] + result + return result def simple_list_question(): diff --git a/cs101courseware/pytransform/__init__.py b/cs101courseware/pytransform/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f656a22d3028cd47aedc8a79e8c9607a847957ac --- /dev/null +++ b/cs101courseware/pytransform/__init__.py @@ -0,0 +1,454 @@ +# These module alos are used by protection code, so that protection +# code needn't import anything +import os +import platform +import sys +import struct + +# Because ctypes is new from Python 2.5, so pytransform doesn't work +# before Python 2.5 +# +from ctypes import cdll, c_char, c_char_p, c_int, c_void_p, \ + pythonapi, py_object, PYFUNCTYPE, CFUNCTYPE +from fnmatch import fnmatch + +# +# Support Platforms +# +plat_path = 'platforms' + +plat_table = ( + ('windows', ('windows', 'cygwin-*')), + ('darwin', ('darwin', 'ios')), + ('linux', ('linux*',)), + ('freebsd', ('freebsd*', 'openbsd*')), + ('poky', ('poky',)), +) + +arch_table = ( + ('x86', ('i?86', )), + ('x86_64', ('x64', 'x86_64', 'amd64', 'intel')), + ('arm', ('armv5',)), + ('armv6', ('armv6l',)), + ('armv7', ('armv7l',)), + ('ppc64', ('ppc64le',)), + ('mips32', ('mips',)), + ('aarch32', ('aarch32',)), + ('aarch64', ('aarch64', 'arm64')) +) + +# +# Hardware type +# +HT_HARDDISK, HT_IFMAC, HT_IPV4, HT_IPV6, HT_DOMAIN = range(5) + +# +# Global +# +_pytransform = None + + +class PytransformError(Exception): + pass + + +def dllmethod(func): + def wrap(*args, **kwargs): + return func(*args, **kwargs) + return wrap + + +@dllmethod +def version_info(): + prototype = PYFUNCTYPE(py_object) + dlfunc = prototype(('version_info', _pytransform)) + return dlfunc() + + +@dllmethod +def init_pytransform(): + major, minor = sys.version_info[0:2] + # Python2.5 no sys.maxsize but sys.maxint + # bitness = 64 if sys.maxsize > 2**32 else 32 + prototype = PYFUNCTYPE(c_int, c_int, c_int, c_void_p) + init_module = prototype(('init_module', _pytransform)) + ret = init_module(major, minor, pythonapi._handle) + if (ret & 0xF000) == 0x1000: + raise PytransformError('Initialize python wrapper failed (%d)' + % (ret & 0xFFF)) + return ret + + +@dllmethod +def init_runtime(): + prototype = PYFUNCTYPE(c_int, c_int, c_int, c_int, c_int) + _init_runtime = prototype(('init_runtime', _pytransform)) + return _init_runtime(0, 0, 0, 0) + + +@dllmethod +def encrypt_code_object(pubkey, co, flags, suffix=''): + _pytransform.set_option(6, suffix.encode()) + prototype = PYFUNCTYPE(py_object, py_object, py_object, c_int) + dlfunc = prototype(('encrypt_code_object', _pytransform)) + return dlfunc(pubkey, co, flags) + + +@dllmethod +def generate_license_file(filename, priname, rcode, start=-1, count=1): + prototype = PYFUNCTYPE(c_int, c_char_p, c_char_p, c_char_p, c_int, c_int) + dlfunc = prototype(('generate_project_license_files', _pytransform)) + return dlfunc(filename.encode(), priname.encode(), rcode.encode(), + start, count) if sys.version_info[0] == 3 \ + else dlfunc(filename, priname, rcode, start, count) + + +@dllmethod +def generate_license_key(prikey, keysize, rcode): + prototype = PYFUNCTYPE(py_object, c_char_p, c_int, c_char_p) + dlfunc = prototype(('generate_license_key', _pytransform)) + return dlfunc(prikey, keysize, rcode) if sys.version_info[0] == 2 \ + else dlfunc(prikey, keysize, rcode.encode()) + + +@dllmethod +def get_registration_code(): + prototype = PYFUNCTYPE(py_object) + dlfunc = prototype(('get_registration_code', _pytransform)) + return dlfunc() + + +@dllmethod +def get_expired_days(): + prototype = PYFUNCTYPE(py_object) + dlfunc = prototype(('get_expired_days', _pytransform)) + return dlfunc() + + +@dllmethod +def clean_obj(obj, kind): + prototype = PYFUNCTYPE(c_int, py_object, c_int) + dlfunc = prototype(('clean_obj', _pytransform)) + return dlfunc(obj, kind) + + +def clean_str(*args): + tdict = { + 'str': 0, + 'bytearray': 1, + 'unicode': 2 + } + for obj in args: + k = tdict.get(type(obj).__name__) + if k is None: + raise RuntimeError('Can not clean object: %s' % obj) + clean_obj(obj, k) + + +def get_hd_info(hdtype, name=None): + if hdtype not in range(HT_DOMAIN + 1): + raise RuntimeError('Invalid parameter hdtype: %s' % hdtype) + size = 256 + t_buf = c_char * size + buf = t_buf() + cname = c_char_p(0 if name is None + else name.encode('utf-8') if hasattr('name', 'encode') + else name) + if (_pytransform.get_hd_info(hdtype, buf, size, cname) == -1): + raise PytransformError('Get hardware information failed') + return buf.value.decode() + + +def show_hd_info(): + return _pytransform.show_hd_info() + + +def assert_armored(*names): + prototype = PYFUNCTYPE(py_object, py_object) + dlfunc = prototype(('assert_armored', _pytransform)) + + def wrapper(func): + def wrap_execute(*args, **kwargs): + dlfunc(names) + return func(*args, **kwargs) + return wrap_execute + return wrapper + + +def get_license_info(): + info = { + 'ISSUER': None, + 'EXPIRED': None, + 'HARDDISK': None, + 'IFMAC': None, + 'IFIPV4': None, + 'DOMAIN': None, + 'DATA': None, + 'CODE': None, + } + rcode = get_registration_code().decode() + if rcode.startswith('*VERSION:'): + index = rcode.find('\n') + info['ISSUER'] = rcode[9:index].split('.')[0].replace('-sn-1.txt', '') + rcode = rcode[index+1:] + + index = 0 + if rcode.startswith('*TIME:'): + from time import ctime + index = rcode.find('\n') + info['EXPIRED'] = ctime(float(rcode[6:index])) + index += 1 + + if rcode[index:].startswith('*FLAGS:'): + index += len('*FLAGS:') + 1 + info['FLAGS'] = ord(rcode[index - 1]) + + prev = None + start = index + for k in ['HARDDISK', 'IFMAC', 'IFIPV4', 'DOMAIN', 'FIXKEY', 'CODE']: + index = rcode.find('*%s:' % k) + if index > -1: + if prev is not None: + info[prev] = rcode[start:index] + prev = k + start = index + len(k) + 2 + info['CODE'] = rcode[start:] + i = info['CODE'].find(';') + if i > 0: + info['DATA'] = info['CODE'][i+1:] + info['CODE'] = info['CODE'][:i] + return info + + +def get_license_code(): + return get_license_info()['CODE'] + + +def get_user_data(): + return get_license_info()['DATA'] + + +def _match_features(patterns, s): + for pat in patterns: + if fnmatch(s, pat): + return True + + +def _gnu_get_libc_version(): + try: + prototype = CFUNCTYPE(c_char_p) + ver = prototype(('gnu_get_libc_version', cdll.LoadLibrary('')))() + return ver.decode().split('.') + except Exception: + pass + + +def format_platform(platid=None): + if platid: + return os.path.normpath(platid) + + plat = platform.system().lower() + mach = platform.machine().lower() + + for alias, platlist in plat_table: + if _match_features(platlist, plat): + plat = alias + break + + if plat == 'linux': + cname, cver = platform.libc_ver() + if cname == 'musl': + plat = 'musl' + elif cname == 'libc': + plat = 'android' + elif cname == 'glibc': + v = _gnu_get_libc_version() + if v and len(v) >= 2 and (int(v[0]) * 100 + int(v[1])) < 214: + plat = 'centos6' + + for alias, archlist in arch_table: + if _match_features(archlist, mach): + mach = alias + break + + if plat == 'windows' and mach == 'x86_64': + bitness = struct.calcsize('P'.encode()) * 8 + if bitness == 32: + mach = 'x86' + + return os.path.join(plat, mach) + + +# Load _pytransform library +def _load_library(path=None, is_runtime=0, platid=None, suffix='', advanced=0): + path = os.path.dirname(__file__) if path is None \ + else os.path.normpath(path) + + plat = platform.system().lower() + name = '_pytransform' + suffix + if plat == 'linux': + filename = os.path.abspath(os.path.join(path, name + '.so')) + elif plat == 'darwin': + filename = os.path.join(path, name + '.dylib') + elif plat == 'windows': + filename = os.path.join(path, name + '.dll') + elif plat == 'freebsd': + filename = os.path.join(path, name + '.so') + else: + raise PytransformError('Platform %s not supported' % plat) + + if platid is not None and os.path.isfile(platid): + filename = platid + elif platid is not None or not os.path.exists(filename) or not is_runtime: + libpath = platid if platid is not None and os.path.isabs(platid) else \ + os.path.join(path, plat_path, format_platform(platid)) + filename = os.path.join(libpath, os.path.basename(filename)) + + if not os.path.exists(filename): + raise PytransformError('Could not find "%s"' % filename) + + try: + m = cdll.LoadLibrary(filename) + except Exception as e: + if sys.flags.debug: + print('Load %s failed:\n%s' % (filename, e)) + raise + + # Removed from v4.6.1 + # if plat == 'linux': + # m.set_option(-1, find_library('c').encode()) + + if not os.path.abspath('.') == os.path.abspath(path): + m.set_option(1, path.encode() if sys.version_info[0] == 3 else path) + + # Required from Python3.6 + m.set_option(2, sys.byteorder.encode()) + + if sys.flags.debug: + m.set_option(3, c_char_p(1)) + m.set_option(4, c_char_p(not is_runtime)) + + # Disable advanced mode by default + m.set_option(5, c_char_p(not advanced)) + + # Set suffix for private package + if suffix: + m.set_option(6, suffix.encode()) + + return m + + +def pyarmor_init(path=None, is_runtime=0, platid=None, suffix='', advanced=0): + global _pytransform + _pytransform = _load_library(path, is_runtime, platid, suffix, advanced) + return init_pytransform() + + +def pyarmor_runtime(path=None, suffix='', advanced=0): + if _pytransform is not None: + return + + try: + pyarmor_init(path, is_runtime=1, suffix=suffix, advanced=advanced) + init_runtime() + except Exception as e: + if sys.flags.debug or hasattr(sys, '_catch_pyarmor'): + raise + sys.stderr.write("%s\n" % str(e)) + sys.exit(1) + + +# ---------------------------------------------------------- +# End of pytransform +# ---------------------------------------------------------- + +# +# Not available from v5.6 +# + + +def generate_capsule(licfile): + prikey, pubkey, prolic = _generate_project_capsule() + capkey, newkey = _generate_pytransform_key(licfile, pubkey) + return prikey, pubkey, capkey, newkey, prolic + + +@dllmethod +def _generate_project_capsule(): + prototype = PYFUNCTYPE(py_object) + dlfunc = prototype(('generate_project_capsule', _pytransform)) + return dlfunc() + + +@dllmethod +def _generate_pytransform_key(licfile, pubkey): + prototype = PYFUNCTYPE(py_object, c_char_p, py_object) + dlfunc = prototype(('generate_pytransform_key', _pytransform)) + return dlfunc(licfile.encode() if sys.version_info[0] == 3 else licfile, + pubkey) + + +# +# Deprecated functions from v5.1 +# +@dllmethod +def encrypt_project_files(proname, filelist, mode=0): + prototype = PYFUNCTYPE(c_int, c_char_p, py_object, c_int) + dlfunc = prototype(('encrypt_project_files', _pytransform)) + return dlfunc(proname.encode(), filelist, mode) + + +def generate_project_capsule(licfile): + prikey, pubkey, prolic = _generate_project_capsule() + capkey = _encode_capsule_key_file(licfile) + return prikey, pubkey, capkey, prolic + + +@dllmethod +def _encode_capsule_key_file(licfile): + prototype = PYFUNCTYPE(py_object, c_char_p, c_char_p) + dlfunc = prototype(('encode_capsule_key_file', _pytransform)) + return dlfunc(licfile.encode(), None) + + +@dllmethod +def encrypt_files(key, filelist, mode=0): + t_key = c_char * 32 + prototype = PYFUNCTYPE(c_int, t_key, py_object, c_int) + dlfunc = prototype(('encrypt_files', _pytransform)) + return dlfunc(t_key(*key), filelist, mode) + + +@dllmethod +def generate_module_key(pubname, key): + t_key = c_char * 32 + prototype = PYFUNCTYPE(py_object, c_char_p, t_key, c_char_p) + dlfunc = prototype(('generate_module_key', _pytransform)) + return dlfunc(pubname.encode(), t_key(*key), None) + +# +# Compatible for PyArmor v3.0 +# +@dllmethod +def old_init_runtime(systrace=0, sysprofile=1, threadtrace=0, threadprofile=1): + '''Only for old version, before PyArmor 3''' + pyarmor_init(is_runtime=1) + prototype = PYFUNCTYPE(c_int, c_int, c_int, c_int, c_int) + _init_runtime = prototype(('init_runtime', _pytransform)) + return _init_runtime(systrace, sysprofile, threadtrace, threadprofile) + + +@dllmethod +def import_module(modname, filename): + '''Only for old version, before PyArmor 3''' + prototype = PYFUNCTYPE(py_object, c_char_p, c_char_p) + _import_module = prototype(('import_module', _pytransform)) + return _import_module(modname.encode(), filename.encode()) + + +@dllmethod +def exec_file(filename): + '''Only for old version, before PyArmor 3''' + prototype = PYFUNCTYPE(c_int, c_char_p) + _exec_file = prototype(('exec_file', _pytransform)) + return _exec_file(filename.encode()) diff --git a/cs101courseware/pytransform/__pycache__/__init__.cpython-36.pyc b/cs101courseware/pytransform/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75bb2539be65062814dda6c5080880056d0f8c70 Binary files /dev/null and b/cs101courseware/pytransform/__pycache__/__init__.cpython-36.pyc differ diff --git a/cs101courseware/pytransform/_pytransform.dll b/cs101courseware/pytransform/_pytransform.dll new file mode 100644 index 0000000000000000000000000000000000000000..c121a4aa51ac71974b6ca0997006bfec3edb2673 Binary files /dev/null and b/cs101courseware/pytransform/_pytransform.dll differ diff --git a/pytransform/__init__.py b/pytransform/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f656a22d3028cd47aedc8a79e8c9607a847957ac --- /dev/null +++ b/pytransform/__init__.py @@ -0,0 +1,454 @@ +# These module alos are used by protection code, so that protection +# code needn't import anything +import os +import platform +import sys +import struct + +# Because ctypes is new from Python 2.5, so pytransform doesn't work +# before Python 2.5 +# +from ctypes import cdll, c_char, c_char_p, c_int, c_void_p, \ + pythonapi, py_object, PYFUNCTYPE, CFUNCTYPE +from fnmatch import fnmatch + +# +# Support Platforms +# +plat_path = 'platforms' + +plat_table = ( + ('windows', ('windows', 'cygwin-*')), + ('darwin', ('darwin', 'ios')), + ('linux', ('linux*',)), + ('freebsd', ('freebsd*', 'openbsd*')), + ('poky', ('poky',)), +) + +arch_table = ( + ('x86', ('i?86', )), + ('x86_64', ('x64', 'x86_64', 'amd64', 'intel')), + ('arm', ('armv5',)), + ('armv6', ('armv6l',)), + ('armv7', ('armv7l',)), + ('ppc64', ('ppc64le',)), + ('mips32', ('mips',)), + ('aarch32', ('aarch32',)), + ('aarch64', ('aarch64', 'arm64')) +) + +# +# Hardware type +# +HT_HARDDISK, HT_IFMAC, HT_IPV4, HT_IPV6, HT_DOMAIN = range(5) + +# +# Global +# +_pytransform = None + + +class PytransformError(Exception): + pass + + +def dllmethod(func): + def wrap(*args, **kwargs): + return func(*args, **kwargs) + return wrap + + +@dllmethod +def version_info(): + prototype = PYFUNCTYPE(py_object) + dlfunc = prototype(('version_info', _pytransform)) + return dlfunc() + + +@dllmethod +def init_pytransform(): + major, minor = sys.version_info[0:2] + # Python2.5 no sys.maxsize but sys.maxint + # bitness = 64 if sys.maxsize > 2**32 else 32 + prototype = PYFUNCTYPE(c_int, c_int, c_int, c_void_p) + init_module = prototype(('init_module', _pytransform)) + ret = init_module(major, minor, pythonapi._handle) + if (ret & 0xF000) == 0x1000: + raise PytransformError('Initialize python wrapper failed (%d)' + % (ret & 0xFFF)) + return ret + + +@dllmethod +def init_runtime(): + prototype = PYFUNCTYPE(c_int, c_int, c_int, c_int, c_int) + _init_runtime = prototype(('init_runtime', _pytransform)) + return _init_runtime(0, 0, 0, 0) + + +@dllmethod +def encrypt_code_object(pubkey, co, flags, suffix=''): + _pytransform.set_option(6, suffix.encode()) + prototype = PYFUNCTYPE(py_object, py_object, py_object, c_int) + dlfunc = prototype(('encrypt_code_object', _pytransform)) + return dlfunc(pubkey, co, flags) + + +@dllmethod +def generate_license_file(filename, priname, rcode, start=-1, count=1): + prototype = PYFUNCTYPE(c_int, c_char_p, c_char_p, c_char_p, c_int, c_int) + dlfunc = prototype(('generate_project_license_files', _pytransform)) + return dlfunc(filename.encode(), priname.encode(), rcode.encode(), + start, count) if sys.version_info[0] == 3 \ + else dlfunc(filename, priname, rcode, start, count) + + +@dllmethod +def generate_license_key(prikey, keysize, rcode): + prototype = PYFUNCTYPE(py_object, c_char_p, c_int, c_char_p) + dlfunc = prototype(('generate_license_key', _pytransform)) + return dlfunc(prikey, keysize, rcode) if sys.version_info[0] == 2 \ + else dlfunc(prikey, keysize, rcode.encode()) + + +@dllmethod +def get_registration_code(): + prototype = PYFUNCTYPE(py_object) + dlfunc = prototype(('get_registration_code', _pytransform)) + return dlfunc() + + +@dllmethod +def get_expired_days(): + prototype = PYFUNCTYPE(py_object) + dlfunc = prototype(('get_expired_days', _pytransform)) + return dlfunc() + + +@dllmethod +def clean_obj(obj, kind): + prototype = PYFUNCTYPE(c_int, py_object, c_int) + dlfunc = prototype(('clean_obj', _pytransform)) + return dlfunc(obj, kind) + + +def clean_str(*args): + tdict = { + 'str': 0, + 'bytearray': 1, + 'unicode': 2 + } + for obj in args: + k = tdict.get(type(obj).__name__) + if k is None: + raise RuntimeError('Can not clean object: %s' % obj) + clean_obj(obj, k) + + +def get_hd_info(hdtype, name=None): + if hdtype not in range(HT_DOMAIN + 1): + raise RuntimeError('Invalid parameter hdtype: %s' % hdtype) + size = 256 + t_buf = c_char * size + buf = t_buf() + cname = c_char_p(0 if name is None + else name.encode('utf-8') if hasattr('name', 'encode') + else name) + if (_pytransform.get_hd_info(hdtype, buf, size, cname) == -1): + raise PytransformError('Get hardware information failed') + return buf.value.decode() + + +def show_hd_info(): + return _pytransform.show_hd_info() + + +def assert_armored(*names): + prototype = PYFUNCTYPE(py_object, py_object) + dlfunc = prototype(('assert_armored', _pytransform)) + + def wrapper(func): + def wrap_execute(*args, **kwargs): + dlfunc(names) + return func(*args, **kwargs) + return wrap_execute + return wrapper + + +def get_license_info(): + info = { + 'ISSUER': None, + 'EXPIRED': None, + 'HARDDISK': None, + 'IFMAC': None, + 'IFIPV4': None, + 'DOMAIN': None, + 'DATA': None, + 'CODE': None, + } + rcode = get_registration_code().decode() + if rcode.startswith('*VERSION:'): + index = rcode.find('\n') + info['ISSUER'] = rcode[9:index].split('.')[0].replace('-sn-1.txt', '') + rcode = rcode[index+1:] + + index = 0 + if rcode.startswith('*TIME:'): + from time import ctime + index = rcode.find('\n') + info['EXPIRED'] = ctime(float(rcode[6:index])) + index += 1 + + if rcode[index:].startswith('*FLAGS:'): + index += len('*FLAGS:') + 1 + info['FLAGS'] = ord(rcode[index - 1]) + + prev = None + start = index + for k in ['HARDDISK', 'IFMAC', 'IFIPV4', 'DOMAIN', 'FIXKEY', 'CODE']: + index = rcode.find('*%s:' % k) + if index > -1: + if prev is not None: + info[prev] = rcode[start:index] + prev = k + start = index + len(k) + 2 + info['CODE'] = rcode[start:] + i = info['CODE'].find(';') + if i > 0: + info['DATA'] = info['CODE'][i+1:] + info['CODE'] = info['CODE'][:i] + return info + + +def get_license_code(): + return get_license_info()['CODE'] + + +def get_user_data(): + return get_license_info()['DATA'] + + +def _match_features(patterns, s): + for pat in patterns: + if fnmatch(s, pat): + return True + + +def _gnu_get_libc_version(): + try: + prototype = CFUNCTYPE(c_char_p) + ver = prototype(('gnu_get_libc_version', cdll.LoadLibrary('')))() + return ver.decode().split('.') + except Exception: + pass + + +def format_platform(platid=None): + if platid: + return os.path.normpath(platid) + + plat = platform.system().lower() + mach = platform.machine().lower() + + for alias, platlist in plat_table: + if _match_features(platlist, plat): + plat = alias + break + + if plat == 'linux': + cname, cver = platform.libc_ver() + if cname == 'musl': + plat = 'musl' + elif cname == 'libc': + plat = 'android' + elif cname == 'glibc': + v = _gnu_get_libc_version() + if v and len(v) >= 2 and (int(v[0]) * 100 + int(v[1])) < 214: + plat = 'centos6' + + for alias, archlist in arch_table: + if _match_features(archlist, mach): + mach = alias + break + + if plat == 'windows' and mach == 'x86_64': + bitness = struct.calcsize('P'.encode()) * 8 + if bitness == 32: + mach = 'x86' + + return os.path.join(plat, mach) + + +# Load _pytransform library +def _load_library(path=None, is_runtime=0, platid=None, suffix='', advanced=0): + path = os.path.dirname(__file__) if path is None \ + else os.path.normpath(path) + + plat = platform.system().lower() + name = '_pytransform' + suffix + if plat == 'linux': + filename = os.path.abspath(os.path.join(path, name + '.so')) + elif plat == 'darwin': + filename = os.path.join(path, name + '.dylib') + elif plat == 'windows': + filename = os.path.join(path, name + '.dll') + elif plat == 'freebsd': + filename = os.path.join(path, name + '.so') + else: + raise PytransformError('Platform %s not supported' % plat) + + if platid is not None and os.path.isfile(platid): + filename = platid + elif platid is not None or not os.path.exists(filename) or not is_runtime: + libpath = platid if platid is not None and os.path.isabs(platid) else \ + os.path.join(path, plat_path, format_platform(platid)) + filename = os.path.join(libpath, os.path.basename(filename)) + + if not os.path.exists(filename): + raise PytransformError('Could not find "%s"' % filename) + + try: + m = cdll.LoadLibrary(filename) + except Exception as e: + if sys.flags.debug: + print('Load %s failed:\n%s' % (filename, e)) + raise + + # Removed from v4.6.1 + # if plat == 'linux': + # m.set_option(-1, find_library('c').encode()) + + if not os.path.abspath('.') == os.path.abspath(path): + m.set_option(1, path.encode() if sys.version_info[0] == 3 else path) + + # Required from Python3.6 + m.set_option(2, sys.byteorder.encode()) + + if sys.flags.debug: + m.set_option(3, c_char_p(1)) + m.set_option(4, c_char_p(not is_runtime)) + + # Disable advanced mode by default + m.set_option(5, c_char_p(not advanced)) + + # Set suffix for private package + if suffix: + m.set_option(6, suffix.encode()) + + return m + + +def pyarmor_init(path=None, is_runtime=0, platid=None, suffix='', advanced=0): + global _pytransform + _pytransform = _load_library(path, is_runtime, platid, suffix, advanced) + return init_pytransform() + + +def pyarmor_runtime(path=None, suffix='', advanced=0): + if _pytransform is not None: + return + + try: + pyarmor_init(path, is_runtime=1, suffix=suffix, advanced=advanced) + init_runtime() + except Exception as e: + if sys.flags.debug or hasattr(sys, '_catch_pyarmor'): + raise + sys.stderr.write("%s\n" % str(e)) + sys.exit(1) + + +# ---------------------------------------------------------- +# End of pytransform +# ---------------------------------------------------------- + +# +# Not available from v5.6 +# + + +def generate_capsule(licfile): + prikey, pubkey, prolic = _generate_project_capsule() + capkey, newkey = _generate_pytransform_key(licfile, pubkey) + return prikey, pubkey, capkey, newkey, prolic + + +@dllmethod +def _generate_project_capsule(): + prototype = PYFUNCTYPE(py_object) + dlfunc = prototype(('generate_project_capsule', _pytransform)) + return dlfunc() + + +@dllmethod +def _generate_pytransform_key(licfile, pubkey): + prototype = PYFUNCTYPE(py_object, c_char_p, py_object) + dlfunc = prototype(('generate_pytransform_key', _pytransform)) + return dlfunc(licfile.encode() if sys.version_info[0] == 3 else licfile, + pubkey) + + +# +# Deprecated functions from v5.1 +# +@dllmethod +def encrypt_project_files(proname, filelist, mode=0): + prototype = PYFUNCTYPE(c_int, c_char_p, py_object, c_int) + dlfunc = prototype(('encrypt_project_files', _pytransform)) + return dlfunc(proname.encode(), filelist, mode) + + +def generate_project_capsule(licfile): + prikey, pubkey, prolic = _generate_project_capsule() + capkey = _encode_capsule_key_file(licfile) + return prikey, pubkey, capkey, prolic + + +@dllmethod +def _encode_capsule_key_file(licfile): + prototype = PYFUNCTYPE(py_object, c_char_p, c_char_p) + dlfunc = prototype(('encode_capsule_key_file', _pytransform)) + return dlfunc(licfile.encode(), None) + + +@dllmethod +def encrypt_files(key, filelist, mode=0): + t_key = c_char * 32 + prototype = PYFUNCTYPE(c_int, t_key, py_object, c_int) + dlfunc = prototype(('encrypt_files', _pytransform)) + return dlfunc(t_key(*key), filelist, mode) + + +@dllmethod +def generate_module_key(pubname, key): + t_key = c_char * 32 + prototype = PYFUNCTYPE(py_object, c_char_p, t_key, c_char_p) + dlfunc = prototype(('generate_module_key', _pytransform)) + return dlfunc(pubname.encode(), t_key(*key), None) + +# +# Compatible for PyArmor v3.0 +# +@dllmethod +def old_init_runtime(systrace=0, sysprofile=1, threadtrace=0, threadprofile=1): + '''Only for old version, before PyArmor 3''' + pyarmor_init(is_runtime=1) + prototype = PYFUNCTYPE(c_int, c_int, c_int, c_int, c_int) + _init_runtime = prototype(('init_runtime', _pytransform)) + return _init_runtime(systrace, sysprofile, threadtrace, threadprofile) + + +@dllmethod +def import_module(modname, filename): + '''Only for old version, before PyArmor 3''' + prototype = PYFUNCTYPE(py_object, c_char_p, c_char_p) + _import_module = prototype(('import_module', _pytransform)) + return _import_module(modname.encode(), filename.encode()) + + +@dllmethod +def exec_file(filename): + '''Only for old version, before PyArmor 3''' + prototype = PYFUNCTYPE(c_int, c_char_p) + _exec_file = prototype(('exec_file', _pytransform)) + return _exec_file(filename.encode()) diff --git a/pytransform/__pycache__/__init__.cpython-36.pyc b/pytransform/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75bb2539be65062814dda6c5080880056d0f8c70 Binary files /dev/null and b/pytransform/__pycache__/__init__.cpython-36.pyc differ diff --git a/pytransform/_pytransform.dll b/pytransform/_pytransform.dll new file mode 100644 index 0000000000000000000000000000000000000000..b1af3263115ebab5c213656cf1daf87b3510116b Binary files /dev/null and b/pytransform/_pytransform.dll differ diff --git a/tutorial/FruitReport_resources_do_not_hand_in.dat b/tutorial/FruitReport_resources_do_not_hand_in.dat new file mode 100644 index 0000000000000000000000000000000000000000..892b881307df3758dcc4db25f6cf0343a234a721 Binary files /dev/null and b/tutorial/FruitReport_resources_do_not_hand_in.dat differ diff --git a/tutorial/VERSION b/tutorial/VERSION new file mode 100644 index 0000000000000000000000000000000000000000..6af849e5dafeb2afad013892d13e1e5eccb7156e --- /dev/null +++ b/tutorial/VERSION @@ -0,0 +1 @@ +v1.002 diff --git a/tutorial/__pycache__/addition.cpython-36.pyc b/tutorial/__pycache__/addition.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..32acf71145f4e5425fd4852c6079b18c5c6243a1 Binary files /dev/null and b/tutorial/__pycache__/addition.cpython-36.pyc differ diff --git a/tutorial/__pycache__/buyLotsOfFruit.cpython-36.pyc b/tutorial/__pycache__/buyLotsOfFruit.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f2c869210e333cd4ddfd077781f8c0ee44b02495 Binary files /dev/null and b/tutorial/__pycache__/buyLotsOfFruit.cpython-36.pyc differ diff --git a/tutorial/__pycache__/fruit_project.cpython-36.pyc b/tutorial/__pycache__/fruit_project.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..de630b05a908efd4b636bb042df5b7f733a4d635 Binary files /dev/null and b/tutorial/__pycache__/fruit_project.cpython-36.pyc differ diff --git a/tutorial/__pycache__/fruit_project_grade.cpython-36.pyc b/tutorial/__pycache__/fruit_project_grade.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7b9d759e978d3f49c894b14c87d51d748b75ac1b Binary files /dev/null and b/tutorial/__pycache__/fruit_project_grade.cpython-36.pyc differ diff --git a/tutorial/__pycache__/grading.cpython-36.pyc b/tutorial/__pycache__/grading.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ab7122c7d868411c3a47d92b5e2cd9a881672135 Binary files /dev/null and b/tutorial/__pycache__/grading.cpython-36.pyc differ diff --git a/tutorial/__pycache__/projectParams.cpython-36.pyc b/tutorial/__pycache__/projectParams.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..84ec121081d18e05d64b51de0d64cf4eb2cf7c25 Binary files /dev/null and b/tutorial/__pycache__/projectParams.cpython-36.pyc differ diff --git a/tutorial/__pycache__/shop.cpython-36.pyc b/tutorial/__pycache__/shop.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..24f4a625f61b6d3f1d2ca6b23db63ed98716bdf2 Binary files /dev/null and b/tutorial/__pycache__/shop.cpython-36.pyc differ diff --git a/tutorial/__pycache__/shopAroundTown.cpython-36.pyc b/tutorial/__pycache__/shopAroundTown.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b6748bc9a4ddfc7faa6ea466e06a45124bd9ed8a Binary files /dev/null and b/tutorial/__pycache__/shopAroundTown.cpython-36.pyc differ diff --git a/tutorial/__pycache__/shopSmart.cpython-36.pyc b/tutorial/__pycache__/shopSmart.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c14ba34925d8dba50bcc4195cd01aacd75c33fd Binary files /dev/null and b/tutorial/__pycache__/shopSmart.cpython-36.pyc differ diff --git a/tutorial/__pycache__/testClasses.cpython-36.pyc b/tutorial/__pycache__/testClasses.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a31cbd47a4375b82158f06bd1628d7b533647024 Binary files /dev/null and b/tutorial/__pycache__/testClasses.cpython-36.pyc differ diff --git a/tutorial/__pycache__/testParser.cpython-36.pyc b/tutorial/__pycache__/testParser.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0ce3ffd502b30cf1edd7cb1d50ecb316d2e9ba7f Binary files /dev/null and b/tutorial/__pycache__/testParser.cpython-36.pyc differ diff --git a/tutorial/__pycache__/textDisplay.cpython-36.pyc b/tutorial/__pycache__/textDisplay.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..02c57770bfa5d8e5cb757f861e7f7d6abe2a32e7 Binary files /dev/null and b/tutorial/__pycache__/textDisplay.cpython-36.pyc differ diff --git a/tutorial/__pycache__/town.cpython-36.pyc b/tutorial/__pycache__/town.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dfd4a94382d01bde5a14cc7b4eb444046f8c49e2 Binary files /dev/null and b/tutorial/__pycache__/town.cpython-36.pyc differ diff --git a/tutorial/__pycache__/tutorialTestClasses.cpython-36.pyc b/tutorial/__pycache__/tutorialTestClasses.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..36840c873bb96d59f3c28416dadb61c4013527db Binary files /dev/null and b/tutorial/__pycache__/tutorialTestClasses.cpython-36.pyc differ diff --git a/tutorial/__pycache__/util.cpython-36.pyc b/tutorial/__pycache__/util.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b4246739203d4fd2c21885657deaf16883e55d90 Binary files /dev/null and b/tutorial/__pycache__/util.cpython-36.pyc differ diff --git a/tutorial/addition.py b/tutorial/addition.py new file mode 100644 index 0000000000000000000000000000000000000000..8d593d2643590a749d2c93b993b6491370c56542 --- /dev/null +++ b/tutorial/addition.py @@ -0,0 +1,23 @@ +# addition.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). + + +""" +Run python autograder.py +""" + + +def add(a, b): + "Return the sum of a and b" + "*** YOUR CODE HERE ***" + return a + b diff --git a/tutorial/autograder.py b/tutorial/autograder.py new file mode 100644 index 0000000000000000000000000000000000000000..0f339a8982dbfd2f8b420b921df864eccfb1a567 --- /dev/null +++ b/tutorial/autograder.py @@ -0,0 +1,359 @@ +# autograder.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). + + +# imports from python standard library +from __future__ import print_function +import grading +import imp +import optparse +import os +import re +import sys +import projectParams +import random +random.seed(0) +try: + from pacman import GameState +except: + pass + +# register arguments and set default values +def readCommand(argv): + parser = optparse.OptionParser(description = 'Run public tests on student code') + parser.set_defaults(generateSolutions=False, edxOutput=False, gsOutput=False, muteOutput=False, printTestCase=False, noGraphics=False) + parser.add_option('--test-directory', + dest = 'testRoot', + default = 'test_cases', + help = 'Root test directory which contains subdirectories corresponding to each question') + parser.add_option('--student-code', + dest = 'studentCode', + default = projectParams.STUDENT_CODE_DEFAULT, + help = 'comma separated list of student code files') + parser.add_option('--code-directory', + dest = 'codeRoot', + default = "", + help = 'Root directory containing the student and testClass code') + parser.add_option('--test-case-code', + dest = 'testCaseCode', + default = projectParams.PROJECT_TEST_CLASSES, + help = 'class containing testClass classes for this project') + parser.add_option('--generate-solutions', + dest = 'generateSolutions', + action = 'store_true', + help = 'Write solutions generated to .solution file') + parser.add_option('--edx-output', + dest = 'edxOutput', + action = 'store_true', + help = 'Generate edX output files') + parser.add_option('--gradescope-output', + dest = 'gsOutput', + action = 'store_true', + help = 'Generate GradeScope output files') + parser.add_option('--mute', + dest = 'muteOutput', + action = 'store_true', + help = 'Mute output from executing tests') + parser.add_option('--print-tests', '-p', + dest = 'printTestCase', + action = 'store_true', + help = 'Print each test case before running them.') + parser.add_option('--test', '-t', + dest = 'runTest', + default = None, + help = 'Run one particular test. Relative to test root.') + parser.add_option('--question', '-q', + dest = 'gradeQuestion', + default = None, + help = 'Grade one particular question.') + parser.add_option('--no-graphics', + dest = 'noGraphics', + action = 'store_true', + help = 'No graphics display for pacman games.') + (options, args) = parser.parse_args(argv) + return options + + +# confirm we should author solution files +def confirmGenerate(): + print('WARNING: this action will overwrite any solution files.') + print('Are you sure you want to proceed? (yes/no)') + while True: + ans = sys.stdin.readline().strip() + if ans == 'yes': + break + elif ans == 'no': + sys.exit(0) + else: + print('please answer either "yes" or "no"') + + +# TODO: Fix this so that it tracebacks work correctly +# Looking at source of the traceback module, presuming it works +# the same as the intepreters, it uses co_filename. This is, +# however, a readonly attribute. +def setModuleName(module, filename): + functionType = type(confirmGenerate) + classType = type(optparse.Option) + + for i in dir(module): + o = getattr(module, i) + if hasattr(o, '__file__'): continue + + if type(o) == functionType: + setattr(o, '__file__', filename) + elif type(o) == classType: + setattr(o, '__file__', filename) + # TODO: assign member __file__'s? + #print i, type(o) + + +#from cStringIO import StringIO + +def loadModuleString(moduleSource): + # Below broken, imp doesn't believe its being passed a file: + # ValueError: load_module arg#2 should be a file or None + # + #f = StringIO(moduleCodeDict[k]) + #tmp = imp.load_module(k, f, k, (".py", "r", imp.PY_SOURCE)) + tmp = imp.new_module(k) + #exec moduleCodeDict[k] in tmp.__dict__ + setModuleName(tmp, k) + return tmp + +import py_compile + +def loadModuleFile(moduleName, filePath): + with open(filePath, 'r') as f: + return imp.load_module(moduleName, f, "%s.py" % moduleName, (".py", "r", imp.PY_SOURCE)) + + +def readFile(path, root=""): + "Read file from disk at specified path and return as string" + with open(os.path.join(root, path), 'r') as handle: + return handle.read() + + +####################################################################### +# Error Hint Map +####################################################################### + +# TODO: use these +ERROR_HINT_MAP = { + 'q1': { + "<type 'exceptions.IndexError'>": """ + 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': { + "<type 'exceptions.AttributeError'>": """ + 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 0000000000000000000000000000000000000000..997fc9f489395e54b44cf998526df6cf88bb3206 --- /dev/null +++ b/tutorial/buyLotsOfFruit.py @@ -0,0 +1,48 @@ +# 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 + for f, c in orderList: + totalCost += fruitPrices[f] * c + "*** YOUR CODE HERE ***" + 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/fruit_project.py b/tutorial/fruit_project.py new file mode 100644 index 0000000000000000000000000000000000000000..e9ef46f82d75b893280ae2aa4153b2fee48d40ef --- /dev/null +++ b/tutorial/fruit_project.py @@ -0,0 +1,69 @@ +from unitgrade.unitgrade import QuestionGroup, Report, QPrintItem, Hidden +from tutorial import shop + +class AdditionQuestion(QuestionGroup): + title = "Addition Test" + class AdditionItem1(QPrintItem): + a, b = 2,2 + def compute_answer_print(self): + from tutorial.addition import add + v = add(self.a, self.b) + print(add(self.a, self.b)) + + class AdditionItem2(AdditionItem1): + a,b = -4, 2 + + class AdditionItem3(AdditionItem1): + a,b = -10, 2 + +class FruitListQuestion(QuestionGroup): + title = "buy lots of fruit.py" + class FruitListItem1(QPrintItem): + fruit_list = [ ('apples', 2.0), ('pears',3.0), ('limes',4.0) ] + def compute_answer_print(self): + from tutorial.buyLotsOfFruit import buyLotsOfFruit + print(buyLotsOfFruit(self.fruit_list)) + + class FruitListItem2(FruitListItem1): + fruit_list = [('apples', 4.0), ('pears', 3.0), ('limes', 2.0)] + + class FruitListItem3(FruitListItem1): + fruit_list = [('apples', 1.25), ('pears', 1.50), ('limes', 1.75)] + + +class FruitShops(QuestionGroup): + title = "Find best shop for fruit shopping (shopSmart.py)" + + class FruitShopItem1(QPrintItem): + shops = [shop.FruitShop('shop1', {'apples': 2.0, 'oranges': 1.0}), + shop.FruitShop('shop2', {'apples': 1.0, 'oranges': 5.0})] + order = [('apples', 1.0), ('oranges', 3.0)] + + def compute_answer_print(self): + from tutorial.shopSmart import shopSmart + print(shopSmart(self.order, self.shops)) + + class FruitShopItem2(FruitShopItem1): + order = [('apples', 3.0)] + + class FruitShopItem3(FruitShopItem1): + shops = [shop.FruitShop('shop1', {'apples': 2.0, 'oranges': 1.0}), + shop.FruitShop('shop2', {'apples': 1.0, 'oranges': 5.0}), + shop.FruitShop('shop3', {'apples': 1.5, 'oranges': 2.0})] + order = [('apples',10.0), ('oranges',3.0)] + + +class FruitReport(Report): + title = "Fruit and addition report" + questions = [(AdditionQuestion, 1), (FruitListQuestion, 3), (FruitShops, 2) ] + pack_imports = [] # Include this file in .token file + +if __name__ == "__main__": + # from unitgrade_private.hidden_create_files import setup_answers, setup_grade_file_report + from unitgrade.unitgrade_helpers import evaluate_report_student + from unitgrade_private.hidden_create_files import setup_grade_file_report + # setup_grade_file_report(FruitReport, minify=True, bzip=False, obfuscate=True) + # evaluate_report_student(Report2()) + evaluate_report_student(FruitReport()) + + diff --git a/tutorial/fruit_project_grade.py b/tutorial/fruit_project_grade.py new file mode 100644 index 0000000000000000000000000000000000000000..50c6a0bd6b6704ceb174631957f85564b860cfa3 --- /dev/null +++ b/tutorial/fruit_project_grade.py @@ -0,0 +1,403 @@ +'''WARNING: Modifying, decompiling or otherwise tampering with this script, it's data or the resulting .token file will be investigated as a cheating attempt.''' +__version__="0.0.4" +mkUjQcFvadVsEefODhlo=object +mkUjQcFvadVsEefODhli=True +mkUjQcFvadVsEefODhlR=print +mkUjQcFvadVsEefODhlz=open +mkUjQcFvadVsEefODhlG=None +mkUjQcFvadVsEefODhlt=sum +mkUjQcFvadVsEefODhlK=list +mkUjQcFvadVsEefODhlq=super +mkUjQcFvadVsEefODhlM=NotImplementedError +mkUjQcFvadVsEefODhlu=False +mkUjQcFvadVsEefODhlp=Exception +mkUjQcFvadVsEefODhlJ=type +mkUjQcFvadVsEefODhlN=classmethod +mkUjQcFvadVsEefODhlw=getattr +mkUjQcFvadVsEefODhlI=issubclass +mkUjQcFvadVsEefODhlC=float +mkUjQcFvadVsEefODhlW=int +mkUjQcFvadVsEefODhlP=all +mkUjQcFvadVsEefODhlS=str +mkUjQcFvadVsEefODhHn=bool +mkUjQcFvadVsEefODhHL=len +mkUjQcFvadVsEefODhHb=enumerate +mkUjQcFvadVsEefODhHl=eval +mkUjQcFvadVsEefODhHg=globals +import os +mkUjQcFvadVsEefODhbq=os.getcwd +mkUjQcFvadVsEefODhbK=os.mkdir +mkUjQcFvadVsEefODhbt=os.path +import compress_pickle +mkUjQcFvadVsEefODhbu=compress_pickle.load +mkUjQcFvadVsEefODhbM=compress_pickle.dump +def mkUjQcFvadVsEefODhLu(mkUjQcFvadVsEefODhlo,file_name,verbose=mkUjQcFvadVsEefODhli): + dn=mkUjQcFvadVsEefODhbt.dirname(file_name) + if not mkUjQcFvadVsEefODhbt.exists(dn): + mkUjQcFvadVsEefODhbK(dn) + if verbose:mkUjQcFvadVsEefODhlR("Writing cache...",file_name) + with mkUjQcFvadVsEefODhlz(file_name,'wb',)as f: + mkUjQcFvadVsEefODhbM(mkUjQcFvadVsEefODhlo,f,compression="lzma") + if verbose:mkUjQcFvadVsEefODhlR("Done!") +def mkUjQcFvadVsEefODhLp(file_name): + return mkUjQcFvadVsEefODhbt.exists(file_name) +def mkUjQcFvadVsEefODhLJ(file_name): + if mkUjQcFvadVsEefODhbt.exists(file_name): + with mkUjQcFvadVsEefODhlz(file_name,'rb')as f: + return mkUjQcFvadVsEefODhbu(f,compression="lzma") + else: + return mkUjQcFvadVsEefODhlG +""" +git add . && git commit -m "Options" && git push && pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade +""" +import unittest +mkUjQcFvadVsEefODhbp=unittest.TestCase +import numpy as np +mkUjQcFvadVsEefODhbW=np.mkUjQcFvadVsEefODhlt +mkUjQcFvadVsEefODhbC=np.max +mkUjQcFvadVsEefODhbI=np.asarray +mkUjQcFvadVsEefODhbw=np.abs +mkUjQcFvadVsEefODhbN=np.floor +mkUjQcFvadVsEefODhbJ=np.round +import os +mkUjQcFvadVsEefODhbq=os.getcwd +mkUjQcFvadVsEefODhbK=os.mkdir +mkUjQcFvadVsEefODhbt=os.path +from io import StringIO +import sys +mkUjQcFvadVsEefODhln=sys.sterr +mkUjQcFvadVsEefODhbS=sys.stderr +mkUjQcFvadVsEefODhbP=sys.stdout +import collections +mkUjQcFvadVsEefODhlL=collections.OrderedDict +import inspect +mkUjQcFvadVsEefODhlr=inspect.currentframe +mkUjQcFvadVsEefODhlg=inspect.getouterframes +mkUjQcFvadVsEefODhlH=inspect.getfile +mkUjQcFvadVsEefODhlb=inspect.isclass +mkUjQcFvadVsEefODhnb=lambda x:mkUjQcFvadVsEefODhbJ(x) +mkUjQcFvadVsEefODhnl=lambda x:mkUjQcFvadVsEefODhlt(x) +mkUjQcFvadVsEefODhnH=lambda x:mkUjQcFvadVsEefODhbN(x) +def mkUjQcFvadVsEefODhLN(C,base_dir): + mkUjQcFvadVsEefODhng=C.__class__.__name__ + return base_dir,mkUjQcFvadVsEefODhng +class mkUjQcFvadVsEefODhbT: + def mkUjQcFvadVsEefODhLw(mkUjQcFvadVsEefODhnr): + return mkUjQcFvadVsEefODhli +class mkUjQcFvadVsEefODhbA(mkUjQcFvadVsEefODhlK): + def __enter__(mkUjQcFvadVsEefODhnr,capture_errors=mkUjQcFvadVsEefODhli): + mkUjQcFvadVsEefODhnr._stdout=mkUjQcFvadVsEefODhbP + mkUjQcFvadVsEefODhbP=mkUjQcFvadVsEefODhnr._stringio=mkUjQcFvadVsEefODhnB() + if capture_errors: + mkUjQcFvadVsEefODhnr._sterr=mkUjQcFvadVsEefODhbS + mkUjQcFvadVsEefODhln=mkUjQcFvadVsEefODhnB() + mkUjQcFvadVsEefODhnr.capture_errors=capture_errors + return mkUjQcFvadVsEefODhnr + def __exit__(mkUjQcFvadVsEefODhnr,*mkUjQcFvadVsEefODhLn): + mkUjQcFvadVsEefODhnr.extend(mkUjQcFvadVsEefODhnr._stringio.getvalue().splitlines()) + del mkUjQcFvadVsEefODhnr._stringio + mkUjQcFvadVsEefODhbP=mkUjQcFvadVsEefODhnr._stdout + if mkUjQcFvadVsEefODhnr.capture_errors: + mkUjQcFvadVsEefODhln=mkUjQcFvadVsEefODhnr._sterr +class mkUjQcFvadVsEefODhbo(mkUjQcFvadVsEefODhbp): + mkUjQcFvadVsEefODhny=mkUjQcFvadVsEefODhlG + mkUjQcFvadVsEefODhnY=mkUjQcFvadVsEefODhbp.assertEqual + mkUjQcFvadVsEefODhnX=0 + def __init__(mkUjQcFvadVsEefODhnr,working_directory=mkUjQcFvadVsEefODhlG,correct_answer_payload=mkUjQcFvadVsEefODhlG,*mkUjQcFvadVsEefODhLn,**kwargs): + mkUjQcFvadVsEefODhnr.name=mkUjQcFvadVsEefODhnr.__class__.__name__ + mkUjQcFvadVsEefODhnr._correct_answer_payload=mkUjQcFvadVsEefODhnT + mkUjQcFvadVsEefODhlq().__init__(*mkUjQcFvadVsEefODhLn,**kwargs) + if mkUjQcFvadVsEefODhnr.title is mkUjQcFvadVsEefODhlG: + mkUjQcFvadVsEefODhnr.title=mkUjQcFvadVsEefODhnr.name + def mkUjQcFvadVsEefODhLI(mkUjQcFvadVsEefODhnr,mkUjQcFvadVsEefODhno,mkUjQcFvadVsEefODhni,tol=mkUjQcFvadVsEefODhlG): + if tol==mkUjQcFvadVsEefODhlG: + tol=mkUjQcFvadVsEefODhnr.tol + mkUjQcFvadVsEefODhnA=mkUjQcFvadVsEefODhbw((mkUjQcFvadVsEefODhbI(mkUjQcFvadVsEefODhno)-mkUjQcFvadVsEefODhbI(mkUjQcFvadVsEefODhni))) + if mkUjQcFvadVsEefODhbC(mkUjQcFvadVsEefODhnA)>tol: + mkUjQcFvadVsEefODhlR("Not equal within tolerance {tol}") + mkUjQcFvadVsEefODhlR(f"Element-wise differences {diff.tolist()}") + mkUjQcFvadVsEefODhnr.assertEqual(mkUjQcFvadVsEefODhno,mkUjQcFvadVsEefODhni,msg=f"Not equal within tolerance {tol}") + def mkUjQcFvadVsEefODhLC(mkUjQcFvadVsEefODhnr,mkUjQcFvadVsEefODhno,mkUjQcFvadVsEefODhni,tol=mkUjQcFvadVsEefODhlG): + if tol==mkUjQcFvadVsEefODhlG: + tol=mkUjQcFvadVsEefODhnr.tol + mkUjQcFvadVsEefODhnA=mkUjQcFvadVsEefODhbw((mkUjQcFvadVsEefODhbI(mkUjQcFvadVsEefODhno)-mkUjQcFvadVsEefODhbI(mkUjQcFvadVsEefODhni))) + mkUjQcFvadVsEefODhnA=mkUjQcFvadVsEefODhnA/(1e-8+mkUjQcFvadVsEefODhbw((mkUjQcFvadVsEefODhbI(mkUjQcFvadVsEefODhno)+mkUjQcFvadVsEefODhbI(mkUjQcFvadVsEefODhni)))) + if mkUjQcFvadVsEefODhbW(mkUjQcFvadVsEefODhnA>tol)>0: + mkUjQcFvadVsEefODhlR(f"Not equal within tolerance {tol}") + mkUjQcFvadVsEefODhlR(f"Element-wise differences {diff.tolist()}") + mkUjQcFvadVsEefODhnr.assertEqual(mkUjQcFvadVsEefODhno,mkUjQcFvadVsEefODhni,msg=f"Not equal within tolerance {tol}") + def mkUjQcFvadVsEefODhLW(mkUjQcFvadVsEefODhnr): + raise mkUjQcFvadVsEefODhlM("test code here") + def mkUjQcFvadVsEefODhLP(mkUjQcFvadVsEefODhnr,mkUjQcFvadVsEefODhno,mkUjQcFvadVsEefODhni): + mkUjQcFvadVsEefODhnr.testfun(mkUjQcFvadVsEefODhno,mkUjQcFvadVsEefODhni) + def mkUjQcFvadVsEefODhLS(mkUjQcFvadVsEefODhnr,verbose=mkUjQcFvadVsEefODhlu): + mkUjQcFvadVsEefODhnz=1 + try: + mkUjQcFvadVsEefODhno=mkUjQcFvadVsEefODhnr.mkUjQcFvadVsEefODhLW() + except mkUjQcFvadVsEefODhlp as e: + mkUjQcFvadVsEefODhlR("\n=================================================================================") + mkUjQcFvadVsEefODhlR(f"When trying to run test class '{self.name}' your code threw an error:") + mkUjQcFvadVsEefODhlR(e) + mkUjQcFvadVsEefODhlR("=================================================================================") + import traceback + traceback.print_exc() + return(0,mkUjQcFvadVsEefODhnz) + mkUjQcFvadVsEefODhnG=mkUjQcFvadVsEefODhnr._correct_answer_payload + try: + mkUjQcFvadVsEefODhnr.mkUjQcFvadVsEefODhLP(computed=mkUjQcFvadVsEefODhno,expected=mkUjQcFvadVsEefODhnG) + except mkUjQcFvadVsEefODhlp as e: + mkUjQcFvadVsEefODhlR("=================================================================================") + mkUjQcFvadVsEefODhlR(f"Test output from test class '{self.name}' does not match expected result. Test error:") + mkUjQcFvadVsEefODhlR(e) + mkUjQcFvadVsEefODhlR("-------------------------Your output:--------------------------------------------") + mkUjQcFvadVsEefODhlR(mkUjQcFvadVsEefODhno) + mkUjQcFvadVsEefODhlR("-------------------------Expected output:----------------------------------------") + mkUjQcFvadVsEefODhlR(mkUjQcFvadVsEefODhnG) + mkUjQcFvadVsEefODhlR("=================================================================================") + return(0,mkUjQcFvadVsEefODhnz) + return(1,mkUjQcFvadVsEefODhnz) + def mkUjQcFvadVsEefODhbn(mkUjQcFvadVsEefODhnr): + try: + mkUjQcFvadVsEefODhnr.mkUjQcFvadVsEefODhLP() + except mkUjQcFvadVsEefODhlp as e: + return 0 + return 1 +class mkUjQcFvadVsEefODhbi(mkUjQcFvadVsEefODhbo): + def mkUjQcFvadVsEefODhbL(mkUjQcFvadVsEefODhnr): + raise mkUjQcFvadVsEefODhlp("Generate output here") + def mkUjQcFvadVsEefODhbl(mkUjQcFvadVsEefODhnr,mkUjQcFvadVsEefODhnt,txt,mkUjQcFvadVsEefODhnK): + return txt + def mkUjQcFvadVsEefODhLW(mkUjQcFvadVsEefODhnr): + with mkUjQcFvadVsEefODhbA()as output: + mkUjQcFvadVsEefODhnt=mkUjQcFvadVsEefODhnr.mkUjQcFvadVsEefODhbL() + s="\n".join(output) + mkUjQcFvadVsEefODhnK=mkUjQcFvadVsEefODhbg(s) + return mkUjQcFvadVsEefODhnr.mkUjQcFvadVsEefODhbl(mkUjQcFvadVsEefODhnt,s,mkUjQcFvadVsEefODhnK) +class mkUjQcFvadVsEefODhbR(mkUjQcFvadVsEefODhlJ): + @mkUjQcFvadVsEefODhlN + def __prepare__(mkUjQcFvadVsEefODhnr,mkUjQcFvadVsEefODhng,bases): + return mkUjQcFvadVsEefODhlL() + def __new__(mkUjQcFvadVsEefODhnr,mkUjQcFvadVsEefODhng,bases,mkUjQcFvadVsEefODhnq): + mkUjQcFvadVsEefODhnq['__ordered__']=[key for key in mkUjQcFvadVsEefODhnq.keys()if key not in('__module__','__qualname__')] + return mkUjQcFvadVsEefODhlJ.__new__(mkUjQcFvadVsEefODhnr,mkUjQcFvadVsEefODhng,bases,mkUjQcFvadVsEefODhnq) +class mkUjQcFvadVsEefODhbz(metaclass=mkUjQcFvadVsEefODhbR): + mkUjQcFvadVsEefODhny="Graph search" + mkUjQcFvadVsEefODhnM=mkUjQcFvadVsEefODhlG + mkUjQcFvadVsEefODhnu=mkUjQcFvadVsEefODhlu + def __init__(mkUjQcFvadVsEefODhnr,*mkUjQcFvadVsEefODhLn,**kwargs): + mkUjQcFvadVsEefODhnr.name=mkUjQcFvadVsEefODhnr.__class__.__name__ + if mkUjQcFvadVsEefODhnr.items is mkUjQcFvadVsEefODhlG: + mkUjQcFvadVsEefODhnr.items=[] + mkUjQcFvadVsEefODhnp=[gt for gt in[mkUjQcFvadVsEefODhlw(mkUjQcFvadVsEefODhnr,gt)for gt in mkUjQcFvadVsEefODhnr.__ordered__]if mkUjQcFvadVsEefODhlb(gt)and mkUjQcFvadVsEefODhlI(gt,mkUjQcFvadVsEefODhbo)] + for gt in mkUjQcFvadVsEefODhnp: + mkUjQcFvadVsEefODhnr.items.append((gt,1)) + mkUjQcFvadVsEefODhnr.items=[(I(),w)for I,w in mkUjQcFvadVsEefODhnr.items] +class mkUjQcFvadVsEefODhbG(): + mkUjQcFvadVsEefODhny="report title" + mkUjQcFvadVsEefODhnJ=[] + mkUjQcFvadVsEefODhnN=[] + def __init__(mkUjQcFvadVsEefODhnr): + mkUjQcFvadVsEefODhnw=mkUjQcFvadVsEefODhbt.abspath(mkUjQcFvadVsEefODhbt.dirname(mkUjQcFvadVsEefODhlH(mkUjQcFvadVsEefODhlJ(mkUjQcFvadVsEefODhnr)))) + mkUjQcFvadVsEefODhnr.wdir,mkUjQcFvadVsEefODhnr.name=mkUjQcFvadVsEefODhLN(mkUjQcFvadVsEefODhnr,mkUjQcFvadVsEefODhnw) + mkUjQcFvadVsEefODhnr.computed_answers_file=mkUjQcFvadVsEefODhbt.join(mkUjQcFvadVsEefODhnr.wdir,mkUjQcFvadVsEefODhnr.name+"_resources_do_not_hand_in.dat") + mkUjQcFvadVsEefODhnr.questions=[(Q(working_directory=mkUjQcFvadVsEefODhnr.wdir),w)for Q,w in mkUjQcFvadVsEefODhnr.questions] + if mkUjQcFvadVsEefODhbt.isfile(mkUjQcFvadVsEefODhnr.computed_answers_file): + mkUjQcFvadVsEefODhnr.mkUjQcFvadVsEefODhbH(mkUjQcFvadVsEefODhLJ(mkUjQcFvadVsEefODhnr.computed_answers_file)) + def mkUjQcFvadVsEefODhbH(mkUjQcFvadVsEefODhnr,mkUjQcFvadVsEefODhnC): + for q,_ in mkUjQcFvadVsEefODhnr.questions: + for mkUjQcFvadVsEefODhnI,_ in q.items: + if q.name not in mkUjQcFvadVsEefODhnC or mkUjQcFvadVsEefODhnI.name not in mkUjQcFvadVsEefODhnC[q.name]: + continue + mkUjQcFvadVsEefODhnI._correct_answer_payload=mkUjQcFvadVsEefODhnC[q.name][mkUjQcFvadVsEefODhnI.name]['payload'] +def mkUjQcFvadVsEefODhbg(txt): + import re + mkUjQcFvadVsEefODhlx=re.VERBOSE + mkUjQcFvadVsEefODhlB=re.compile + mkUjQcFvadVsEefODhnW='[-+]? (?: (?: \d* \. \d+ ) | (?: \d+ \.? ) )(?: [Ee] [+-]? \d+ ) ?' + rx=mkUjQcFvadVsEefODhlB(mkUjQcFvadVsEefODhnW,mkUjQcFvadVsEefODhlx) + mkUjQcFvadVsEefODhlP=rx.findall(txt) + mkUjQcFvadVsEefODhlP=[mkUjQcFvadVsEefODhlC(a)if '.' in a else mkUjQcFvadVsEefODhlW(a)for a in mkUjQcFvadVsEefODhlP] + return mkUjQcFvadVsEefODhlP +import numpy as np +mkUjQcFvadVsEefODhbW=np.mkUjQcFvadVsEefODhlt +mkUjQcFvadVsEefODhbC=np.max +mkUjQcFvadVsEefODhbI=np.asarray +mkUjQcFvadVsEefODhbw=np.abs +mkUjQcFvadVsEefODhbN=np.floor +mkUjQcFvadVsEefODhbJ=np.round +from tabulate import tabulate +from datetime import datetime +mkUjQcFvadVsEefODhly=datetime.now +import pickle +mkUjQcFvadVsEefODhlY=pickle.loads +import pyfiglet +mkUjQcFvadVsEefODhlX=pyfiglet.figlet_format +import inspect +mkUjQcFvadVsEefODhlr=inspect.currentframe +mkUjQcFvadVsEefODhlg=inspect.getouterframes +mkUjQcFvadVsEefODhlH=inspect.getfile +mkUjQcFvadVsEefODhlb=inspect.isclass +mkUjQcFvadVsEefODhbt=os.path +import os +mkUjQcFvadVsEefODhbq=os.getcwd +mkUjQcFvadVsEefODhbK=os.mkdir +import argparse +mkUjQcFvadVsEefODhlT=argparse.ArgumentParser +mkUjQcFvadVsEefODhnP=mkUjQcFvadVsEefODhlT(description='Evaluate your report.',epilog="""Example: +To run all tests in a report: +> python report1.py +To run only question 2 or question 2.1 +> python report1.py -q 2 +> python report1.py -q 2.1 +Note this scripts does not grade your report. To grade your report, use: +> python report1_grade.py +Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful. +As an example, suppose the report file is Documents/course_package/report1.py, and `course_package` is a python package. Change directory to 'Documents/` and execute: +> python -m course_package.report1 +see https://docs.python.org/3.9/using/cmdline.html +""") +mkUjQcFvadVsEefODhnP.add_argument('-q',nargs='?',mkUjQcFvadVsEefODhlJ=mkUjQcFvadVsEefODhlS,default=mkUjQcFvadVsEefODhlG,help='Only evaluate this question (example: -q 2)') +mkUjQcFvadVsEefODhnP.add_argument('--verbose',nargs='?',mkUjQcFvadVsEefODhlJ=mkUjQcFvadVsEefODhHn,default=mkUjQcFvadVsEefODhlu,help='Show expected output') +def mkUjQcFvadVsEefODhbr(mkUjQcFvadVsEefODhLi,question=mkUjQcFvadVsEefODhlG,qitem=mkUjQcFvadVsEefODhlG): + mkUjQcFvadVsEefODhLn=mkUjQcFvadVsEefODhnP.parse_args() + if question is mkUjQcFvadVsEefODhlG and mkUjQcFvadVsEefODhLn.q is not mkUjQcFvadVsEefODhlG: + question=mkUjQcFvadVsEefODhLn.q + if "." in question: + question,qitem=[mkUjQcFvadVsEefODhlW(v)for v in question.split(".")] + else: + question=mkUjQcFvadVsEefODhlW(question) + mkUjQcFvadVsEefODhLH,mkUjQcFvadVsEefODhLg=mkUjQcFvadVsEefODhbx(mkUjQcFvadVsEefODhLi,question=question,qitem=qitem,verbose=mkUjQcFvadVsEefODhLn.verbose) + if question is mkUjQcFvadVsEefODhlG: + mkUjQcFvadVsEefODhlR("Provisional evaluation") + tabulate(mkUjQcFvadVsEefODhLg) + mkUjQcFvadVsEefODhLr=mkUjQcFvadVsEefODhLg + mkUjQcFvadVsEefODhlR(tabulate(mkUjQcFvadVsEefODhLr)) + mkUjQcFvadVsEefODhlR(" ") + mkUjQcFvadVsEefODhlR("Note your results have not yet been registered. \nTo register your results, please run the file:") + fr=mkUjQcFvadVsEefODhlg(mkUjQcFvadVsEefODhlr())[1].filename + mkUjQcFvadVsEefODhlR(">>>",mkUjQcFvadVsEefODhbt.basename(fr)[:-3]+"_grade.py") + mkUjQcFvadVsEefODhlR("In the same manner as you ran this file.") + return mkUjQcFvadVsEefODhLH +def mkUjQcFvadVsEefODhbB(q): + h=[(i['w'],i['possible'],i['obtained'])for i in q.values()] + h=mkUjQcFvadVsEefODhbI(h) + return h[:,0],h[:,1],h[:,2], +def mkUjQcFvadVsEefODhbx(mkUjQcFvadVsEefODhLi,question=mkUjQcFvadVsEefODhlG,qitem=mkUjQcFvadVsEefODhlG,verbose=mkUjQcFvadVsEefODhlu): + mkUjQcFvadVsEefODhLB=mkUjQcFvadVsEefODhly() + mkUjQcFvadVsEefODhLx=mkUjQcFvadVsEefODhlX("UnitGrade",font="doom") + b="\n".join([l for l in mkUjQcFvadVsEefODhLx.splitlines()if mkUjQcFvadVsEefODhHL(l.strip())>0]) + mkUjQcFvadVsEefODhlR(b+" v"+__version__) + mkUjQcFvadVsEefODhLy=mkUjQcFvadVsEefODhLB.strftime("%d/%m/%Y %H:%M:%S") + mkUjQcFvadVsEefODhlR("Started: "+mkUjQcFvadVsEefODhLy) + mkUjQcFvadVsEefODhlR("Evaluating "+mkUjQcFvadVsEefODhLi.title,"(use --help for options)") + mkUjQcFvadVsEefODhlR("") + mkUjQcFvadVsEefODhLg=[] + nL=80 + mkUjQcFvadVsEefODhbn={} + for n,(q,w)in mkUjQcFvadVsEefODhHb(mkUjQcFvadVsEefODhLi.questions): + mkUjQcFvadVsEefODhLY=mkUjQcFvadVsEefODhlI(q.__class__,mkUjQcFvadVsEefODhbT) + if question is not mkUjQcFvadVsEefODhlG and n+1!=question: + continue + mkUjQcFvadVsEefODhlR(f"Question {n+1}: {q.title}") + mkUjQcFvadVsEefODhlR("="*nL) + q.possible=0 + q.obtained=0 + q_={} + for j,(mkUjQcFvadVsEefODhnI,iw)in mkUjQcFvadVsEefODhHb(q.items): + if qitem is not mkUjQcFvadVsEefODhlG and question is not mkUjQcFvadVsEefODhlG and mkUjQcFvadVsEefODhnI is not mkUjQcFvadVsEefODhlG and j+1!=qitem: + continue + ss=f"*** q{n+1}.{j+1}) {item.title}" + el=nL-4 + if mkUjQcFvadVsEefODhHL(ss)<el: + ss+='.'*(el-mkUjQcFvadVsEefODhHL(ss)) + mkUjQcFvadVsEefODhLX=mkUjQcFvadVsEefODhlI(mkUjQcFvadVsEefODhnI.__class__,mkUjQcFvadVsEefODhbT) + if not mkUjQcFvadVsEefODhLX: + mkUjQcFvadVsEefODhlR(ss,end="") + (mkUjQcFvadVsEefODhLA,mkUjQcFvadVsEefODhnz)=mkUjQcFvadVsEefODhnI.mkUjQcFvadVsEefODhLS() + q_[j]={'w':iw,'possible':mkUjQcFvadVsEefODhnz,'obtained':mkUjQcFvadVsEefODhLA,'hidden':mkUjQcFvadVsEefODhLX} + if not mkUjQcFvadVsEefODhLX: + if mkUjQcFvadVsEefODhLA==mkUjQcFvadVsEefODhnz: + mkUjQcFvadVsEefODhlR(f"PASS") + else: + mkUjQcFvadVsEefODhlR(f"*** FAILED") + ws,mkUjQcFvadVsEefODhnz,mkUjQcFvadVsEefODhLo=mkUjQcFvadVsEefODhbB(q_) + mkUjQcFvadVsEefODhnz=mkUjQcFvadVsEefODhlW(ws@mkUjQcFvadVsEefODhnz) + mkUjQcFvadVsEefODhLo=mkUjQcFvadVsEefODhlW(ws@mkUjQcFvadVsEefODhLo) + mkUjQcFvadVsEefODhLo=mkUjQcFvadVsEefODhnb(mkUjQcFvadVsEefODhlW((w*mkUjQcFvadVsEefODhLo)/mkUjQcFvadVsEefODhnz))if mkUjQcFvadVsEefODhnz>0 else 0 + mkUjQcFvadVsEefODhbn[n]={'w':w,'possible':w,'obtained':mkUjQcFvadVsEefODhLo,'Ãtems':q_,'hidden':mkUjQcFvadVsEefODhLY} + q.obtained=mkUjQcFvadVsEefODhLo + q.possible=mkUjQcFvadVsEefODhnz + s1=f"*** Question q{n+1}" + s2=f" {q.obtained}/{w}" + mkUjQcFvadVsEefODhlR(s1+("."*(nL-mkUjQcFvadVsEefODhHL(s1)-mkUjQcFvadVsEefODhHL(s2)))+s2) + mkUjQcFvadVsEefODhlR(" ") + mkUjQcFvadVsEefODhLg.append([f"Question q{n+1}",f"{q.obtained}/{w}"]) + ws,mkUjQcFvadVsEefODhnz,mkUjQcFvadVsEefODhLo=mkUjQcFvadVsEefODhbB(mkUjQcFvadVsEefODhbn) + mkUjQcFvadVsEefODhnz=mkUjQcFvadVsEefODhlW(mkUjQcFvadVsEefODhnl(mkUjQcFvadVsEefODhnz)) + mkUjQcFvadVsEefODhLo=mkUjQcFvadVsEefODhlW(mkUjQcFvadVsEefODhnl(mkUjQcFvadVsEefODhLo)) + mkUjQcFvadVsEefODhLi.possible=mkUjQcFvadVsEefODhnz + mkUjQcFvadVsEefODhLi.obtained=mkUjQcFvadVsEefODhLo + mkUjQcFvadVsEefODhLB=mkUjQcFvadVsEefODhly() + mkUjQcFvadVsEefODhLy=mkUjQcFvadVsEefODhLB.strftime("%H:%M:%S") + mkUjQcFvadVsEefODhlR(f"Completed: "+mkUjQcFvadVsEefODhLy) + mkUjQcFvadVsEefODhLg.append(["Total",""+mkUjQcFvadVsEefODhlS(mkUjQcFvadVsEefODhLi.obtained)+"/"+mkUjQcFvadVsEefODhlS(mkUjQcFvadVsEefODhLi.possible)]) + mkUjQcFvadVsEefODhLH={'total':(mkUjQcFvadVsEefODhLo,mkUjQcFvadVsEefODhnz),'details':mkUjQcFvadVsEefODhbn} + return mkUjQcFvadVsEefODhLH,mkUjQcFvadVsEefODhLg +from tabulate import tabulate +from datetime import datetime +mkUjQcFvadVsEefODhly=datetime.now +mkUjQcFvadVsEefODhlb=inspect.isclass +import inspect +mkUjQcFvadVsEefODhlr=inspect.currentframe +mkUjQcFvadVsEefODhlg=inspect.getouterframes +mkUjQcFvadVsEefODhlH=inspect.getfile +import json +mkUjQcFvadVsEefODhlA=json.dumps +mkUjQcFvadVsEefODhbt=os.path +mkUjQcFvadVsEefODhbK=os.mkdir +import os +mkUjQcFvadVsEefODhbq=os.getcwd +import bz2 +import pickle +mkUjQcFvadVsEefODhlY=pickle.loads +def mkUjQcFvadVsEefODhby(mkUjQcFvadVsEefODhLR,mkUjQcFvadVsEefODhLK): + with mkUjQcFvadVsEefODhlw(bz2,'open')(mkUjQcFvadVsEefODhLK,"wt")as f: + f.write(mkUjQcFvadVsEefODhLR) +def mkUjQcFvadVsEefODhbY(mkUjQcFvadVsEefODhLi,output_dir=mkUjQcFvadVsEefODhlG): + n=80 + mkUjQcFvadVsEefODhLH,mkUjQcFvadVsEefODhLg=mkUjQcFvadVsEefODhbx(mkUjQcFvadVsEefODhLi) + mkUjQcFvadVsEefODhlR(" ") + mkUjQcFvadVsEefODhlR("="*n) + mkUjQcFvadVsEefODhlR("Final evaluation") + mkUjQcFvadVsEefODhlR(tabulate(mkUjQcFvadVsEefODhLg)) + mkUjQcFvadVsEefODhLH['sources']={} + mkUjQcFvadVsEefODhlR("Gathering files...") + for m in mkUjQcFvadVsEefODhLi.pack_imports: + with mkUjQcFvadVsEefODhlz(m.__file__,'r')as f: + mkUjQcFvadVsEefODhLH['sources'][m.__name__]=f.read() + mkUjQcFvadVsEefODhlR(f"*** {m.__name__}") + mkUjQcFvadVsEefODhLH['sources']={} + mkUjQcFvadVsEefODhLR=mkUjQcFvadVsEefODhlA(mkUjQcFvadVsEefODhLH,indent=4) + if output_dir is mkUjQcFvadVsEefODhlG: + output_dir=mkUjQcFvadVsEefODhbq() + mkUjQcFvadVsEefODhLG=mkUjQcFvadVsEefODhLi.__class__.__name__+"_handin" + mkUjQcFvadVsEefODhLt,mkUjQcFvadVsEefODhnz=mkUjQcFvadVsEefODhLH['total'] + mkUjQcFvadVsEefODhLK="%s_%i_of_%i.token"%(mkUjQcFvadVsEefODhLG,mkUjQcFvadVsEefODhLt,mkUjQcFvadVsEefODhnz) + mkUjQcFvadVsEefODhLK=mkUjQcFvadVsEefODhbt.join(output_dir,mkUjQcFvadVsEefODhLK) + mkUjQcFvadVsEefODhby(mkUjQcFvadVsEefODhLR,mkUjQcFvadVsEefODhLK) + mkUjQcFvadVsEefODhlR(" ") + mkUjQcFvadVsEefODhlR("To get credit for your results, please upload the single file: ") + mkUjQcFvadVsEefODhlR(">",mkUjQcFvadVsEefODhLK) + mkUjQcFvadVsEefODhlR("To campusnet without any modifications.") +def mkUjQcFvadVsEefODhbX(mkUjQcFvadVsEefODhng,mkUjQcFvadVsEefODhLq,payload): + mkUjQcFvadVsEefODhHl("exec")(mkUjQcFvadVsEefODhLq,mkUjQcFvadVsEefODhHg()) + mkUjQcFvadVsEefODhLi=mkUjQcFvadVsEefODhHl(mkUjQcFvadVsEefODhng)() + pl=mkUjQcFvadVsEefODhlY(payload) + mkUjQcFvadVsEefODhLi.mkUjQcFvadVsEefODhbH(pl) + return mkUjQcFvadVsEefODhLi +mkUjQcFvadVsEefODhLq='__version__ = "0.0.4"\nimport os\nimport compress_pickle\n\ndef cache_write(object, file_name, verbose=True):\n dn = os.path.dirname(file_name)\n if not os.path.exists(dn):\n os.mkdir(dn)\n if verbose: print("Writing cache...", file_name)\n with open(file_name, \'wb\', ) as f:\n compress_pickle.dump(object, f, compression="lzma")\n if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n # file_name = cn_(file_name) if cache_prefix else file_name\n return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n # file_name = cn_(file_name) if cache_prefix else file_name\n if os.path.exists(file_name):\n with open(file_name, \'rb\') as f:\n return compress_pickle.load(f, compression="lzma")\n # return pickle.load(f)\n else:\n return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push && pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\nimport unittest\nimport numpy as np\nimport os\nfrom io import StringIO\nimport sys\nimport collections\nimport inspect\n\nmyround = lambda x: np.round(x) # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n name = C.__class__.__name__\n # base_dir = os.path.join(base_dir, name)\n # if not os.path.isdir(base_dir):\n # os.makedirs(base_dir)\n return base_dir, name\n\nclass Hidden:\n def hide(self):\n return True\n\nclass Capturing(list):\n def __enter__(self, capture_errors=True):\n self._stdout = sys.stdout\n sys.stdout = self._stringio = StringIO()\n if capture_errors:\n self._sterr = sys.stderr\n sys.sterr = StringIO() # memory hole it\n self.capture_errors = capture_errors\n return self\n\n def __exit__(self, *args):\n self.extend(self._stringio.getvalue().splitlines())\n del self._stringio # free up some memory\n sys.stdout = self._stdout\n if self.capture_errors:\n sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n title = None\n testfun = unittest.TestCase.assertEqual\n tol = 0\n\n def __init__(self, working_directory=None, correct_answer_payload=None, *args, **kwargs):\n self.name = self.__class__.__name__\n self._correct_answer_payload = correct_answer_payload\n super().__init__(*args, **kwargs)\n if self.title is None:\n self.title = self.name\n\n def assertL2(self, computed, expected, tol=None):\n if tol == None:\n tol = self.tol\n diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n if np.max(diff) > tol:\n print("Not equal within tolerance {tol}")\n print(f"Element-wise differences {diff.tolist()}")\n self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n def assertL2Relative(self, computed, expected, tol=None):\n if tol == None:\n tol = self.tol\n diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n if np.sum(diff > tol) > 0:\n print(f"Not equal within tolerance {tol}")\n print(f"Element-wise differences {diff.tolist()}")\n self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n def compute_answer(self):\n raise NotImplementedError("test code here")\n\n def test(self, computed, expected):\n self.testfun(computed, expected)\n\n def get_points(self, verbose=False):\n possible = 1\n try:\n computed = self.compute_answer()\n except Exception as e:\n print("\\n=================================================================================")\n print(f"When trying to run test class \'{self.name}\' your code threw an error:")\n print(e)\n print("=================================================================================")\n import traceback\n traceback.print_exc()\n return (0, possible)\n correct = self._correct_answer_payload\n try:\n # print(computed, correct)\n self.test(computed=computed, expected=correct)\n except Exception as e:\n print("=================================================================================")\n print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n print(e)\n print("-------------------------Your output:--------------------------------------------")\n print(computed)\n print("-------------------------Expected output:----------------------------------------")\n print(correct)\n print("=================================================================================")\n return (0, possible)\n return (1, possible)\n\n def score(self):\n try:\n self.test()\n except Exception as e:\n return 0\n return 1\n\nclass QPrintItem(QItem):\n def compute_answer_print(self):\n raise Exception("Generate output here")\n\n def process_output(self, res, txt, numbers):\n return txt\n\n def compute_answer(self):\n with Capturing() as output:\n res = self.compute_answer_print()\n s = "\\n".join(output)\n numbers = extract_numbers(s)\n return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n @classmethod\n def __prepare__(self, name, bases):\n return collections.OrderedDict()\n def __new__(self, name, bases, classdict):\n classdict[\'__ordered__\'] = [key for key in classdict.keys() if key not in (\'__module__\', \'__qualname__\')]\n return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n title = "Graph search"\n items = None\n partially_scored = False\n\n def __init__(self, *args, **kwargs):\n self.name = self.__class__.__name__\n if self.items is None:\n self.items = []\n members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__] if inspect.isclass(gt) and issubclass(gt, QItem)]\n for gt in members:\n self.items.append( (gt, 1) )\n self.items = [(I(), w) for I, w in self.items]\n\nclass Report():\n title = "report title"\n questions = []\n pack_imports = []\n\n def __init__(self):\n working_directory = os.path.abspath(os.path.dirname(inspect.getfile(type(self))))\n # working_directory = os.path.join(pathlib.Path(__file__).parent.absolute(), "payloads/")\n self.wdir, self.name = setup_dir_by_class(self, working_directory)\n self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n # print(self.computed_answers_file)\n self.questions = [(Q(working_directory=self.wdir),w) for Q,w in self.questions]\n if os.path.isfile(self.computed_answers_file):\n self.set_payload(cache_read(self.computed_answers_file))\n\n def set_payload(self, payloads):\n for q, _ in self.questions:\n for item, _ in q.items:\n if q.name not in payloads or item.name not in payloads[q.name]:\n continue\n item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n\ndef extract_numbers(txt):\n import re\n numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n rx = re.compile(numeric_const_pattern, re.VERBOSE)\n all = rx.findall(txt)\n all = [float(a) if \'.\' in a else int(a) for a in all]\n return all\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pickle\nimport pyfiglet\n\n# from unitgrade.unitgrade import Hidden\n# import unitgrade as ug\n# import unitgrade.unitgrade as ug\nimport inspect\nimport os\nimport argparse\n\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python report1.py\n\nTo run only question 2 or question 2.1\n\n> python report1.py -q 2\n> python report1.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nAs an example, suppose the report file is Documents/course_package/report1.py, and `course_package` is a python package. Change directory to \'Documents/` and execute:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""")\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (example: -q 2)\')\nparser.add_argument(\'--verbose\', nargs=\'?\', type=bool, default=False, help=\'Show expected output\')\n# parser.add_argument(\'integers\', metavar=\'N\', type=int, nargs=\'+\',\n# help=\'an integer for the accumulator\')\n# parser.add_argument(\'--sum\', dest=\'accumulate\', action=\'store_const\',\n# const=sum, default=max,\n# help=\'sum the integers (default: find the max)\')\n\n\n\ndef evaluate_report_student(report, question=None, qitem=None):\n args = parser.parse_args()\n if question is None and args.q is not None:\n question = args.q\n if "." in question:\n question, qitem = [int(v) for v in question.split(".")]\n else:\n question = int(question)\n results, table_data = evaluate_report(report, question=question, qitem=qitem, verbose=args.verbose)\n if question is None:\n print("Provisional evaluation")\n tabulate(table_data)\n table = table_data\n print(tabulate(table))\n print(" ")\n print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n\n fr = inspect.getouterframes(inspect.currentframe())[1].filename\n print(">>>", os.path.basename(fr)[:-3] + "_grade.py")\n print("In the same manner as you ran this file.")\n return results\n\n\ndef upack(q):\n # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n h = np.asarray(h)\n return h[:,0], h[:,1], h[:,2],\n #\n # ws, possible, obtained = (np.asarray(x).squeeze() for x in zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]))\n # return ws, possible, obtained\n\ndef evaluate_report(report, question=None, qitem=None, verbose=False):\n now = datetime.now()\n ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n print(b + " v" + __version__)\n dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n print("Started: " + dt_string)\n print("Evaluating " + report.title, "(use --help for options)")\n print("")\n table_data = []\n nL = 80\n\n score = {}\n for n, (q, w) in enumerate(report.questions):\n q_hidden = issubclass(q.__class__, Hidden)\n if question is not None and n+1 != question:\n continue\n\n print(f"Question {n+1}: {q.title}")\n print("="*nL)\n q.possible = 0\n q.obtained = 0\n\n q_ = {} # Gather score in this class.\n for j, (item, iw) in enumerate(q.items):\n if qitem is not None and question is not None and item is not None and j+1 != qitem:\n continue\n\n ss = f"*** q{n+1}.{j+1}) {item.title}"\n el = nL-4\n if len(ss) < el:\n ss += \'.\'*(el-len(ss))\n hidden = issubclass(item.__class__, Hidden)\n if not hidden:\n print(ss, end="")\n (current, possible) = item.get_points()\n q_[j] = {\'w\': iw, \'possible\': possible, \'obtained\': current, \'hidden\': hidden}\n # q.possible += possible * iw\n # q.obtained += current * iw\n if not hidden:\n if current == possible:\n print(f"PASS")\n else:\n print(f"*** FAILED")\n\n ws, possible, obtained = upack(q_)\n possible = int(ws @ possible)\n obtained = int(ws @ obtained)\n obtained = myround(int((w * obtained) / possible )) if possible > 0 else 0\n score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'Ãtems\': q_, \'hidden\': q_hidden}\n\n q.obtained = obtained\n q.possible = possible\n\n s1 = f"*** Question q{n+1}"\n s2 = f" {q.obtained}/{w}"\n print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n print(" ")\n table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n ws, possible, obtained = upack(score)\n possible = int( msum(possible) )\n obtained = int( msum(obtained) ) # Cast to python int\n report.possible = possible\n report.obtained = obtained\n now = datetime.now()\n dt_string = now.strftime("%H:%M:%S")\n print(f"Completed: "+ dt_string)\n table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n results = {\'total\': (obtained, possible), \'details\': score}\n return results, table_data\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n with getattr(bz2, \'open\')(token, "wt") as f:\n f.write(json_str)\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n n = 80\n results, table_data = evaluate_report(report)\n print(" ")\n print("="*n)\n print("Final evaluation")\n print(tabulate(table_data))\n # also load the source code of missing files...\n results[\'sources\'] = {}\n print("Gathering files...")\n for m in report.pack_imports:\n with open(m.__file__, \'r\') as f:\n results[\'sources\'][m.__name__] = f.read()\n print(f"*** {m.__name__}")\n\n results[\'sources\'] = {}\n json_str = json.dumps(results, indent=4)\n # now = datetime.now()\n # dname = os.path.dirname(inspect.getfile(report.__class__))\n # dname = os.getcwd()\n if output_dir is None:\n output_dir = os.getcwd()\n\n payload_out_base = report.__class__.__name__ + "_handin"\n\n obtain, possible = results[\'total\']\n token = "%s_%i_of_%i.token"%(payload_out_base, obtain, possible) # + str(obtained) +"_" + ".token"\n token = os.path.join(output_dir, token)\n bzwrite(json_str, token)\n\n print(" ")\n print("To get credit for your results, please upload the single file: ")\n print(">", token)\n print("To campusnet without any modifications.")\n\ndef source_instantiate(name, report1_source, payload):\n eval("exec")(report1_source, globals())\n report = eval(name)()\n pl = pickle.loads(payload)\n report.set_payload(pl)\n return report\n\n\n\nclass AdditionQuestion(QuestionGroup):\n title = "Addition Test"\n class AdditionItem1(QPrintItem):\n a, b = 2,2\n def compute_answer_print(self):\n from tutorial.addition import add\n v = add(self.a, self.b)\n print(add(self.a, self.b))\n\n class AdditionItem2(AdditionItem1):\n a,b = -4, 2\n\n class AdditionItem3(AdditionItem1):\n a,b = -10, 2\n\nclass FruitListQuestion(QuestionGroup):\n title = "buy lots of fruit.py"\n class FruitListItem1(QPrintItem):\n fruit_list = [ (\'apples\', 2.0), (\'pears\',3.0), (\'limes\',4.0) ]\n def compute_answer_print(self):\n from tutorial.buyLotsOfFruit import buyLotsOfFruit\n print(buyLotsOfFruit(self.fruit_list))\n\n class FruitListItem2(FruitListItem1):\n fruit_list = [(\'apples\', 4.0), (\'pears\', 3.0), (\'limes\', 2.0)]\n\n class FruitListItem3(FruitListItem1):\n fruit_list = [(\'apples\', 1.25), (\'pears\', 1.50), (\'limes\', 1.75)]\n\nclass FruitReport(Report):\n title = "Fruit and addition report"\n questions = [(AdditionQuestion, 1), (AdditionQuestion, 3) ]\n pack_imports = [] # Include this file in .token file' +mkUjQcFvadVsEefODhLM=b'\x80\x03}q\x00X\x10\x00\x00\x00AdditionQuestionq\x01}q\x02(X\r\x00\x00\x00AdditionItem1q\x03}q\x04X\x07\x00\x00\x00payloadq\x05X\x01\x00\x00\x004q\x06sX\r\x00\x00\x00AdditionItem2q\x07}q\x08h\x05X\x02\x00\x00\x00-2q\tsX\r\x00\x00\x00AdditionItem3q\n}q\x0bh\x05X\x02\x00\x00\x00-8q\x0csus.' +mkUjQcFvadVsEefODhng="FruitReport" +mkUjQcFvadVsEefODhLi=mkUjQcFvadVsEefODhbX(mkUjQcFvadVsEefODhng,mkUjQcFvadVsEefODhLq,mkUjQcFvadVsEefODhLM) +mkUjQcFvadVsEefODhLz=mkUjQcFvadVsEefODhbt.dirname(__file__) +mkUjQcFvadVsEefODhbY(mkUjQcFvadVsEefODhLi,mkUjQcFvadVsEefODhLz) \ No newline at end of file diff --git a/tutorial/grading.py b/tutorial/grading.py new file mode 100644 index 0000000000000000000000000000000000000000..edde8e39b6ff1b203bbc8a5c88783f3c96d5a3ad --- /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 <pre> 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("<div>") + + # first sum + total_possible = sum(self.maxes.values()) + total_score = sum(self.points.values()) + checkOrX = '<span class="incorrect"/>' + if (total_score >= total_possible): + checkOrX = '<span class="correct"/>' + header = """ + <h3> + Total score ({total_score} / {total_possible}) + </h3> + """.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 = '<span class="incorrect"/>' + if (self.points[q] >= self.maxes[q]): + checkOrX = '<span class="correct"/>' + # messages = '\n<br/>\n'.join(self.messages[q]) + messages = "<pre>%s</pre>" % '\n'.join(self.messages[q]) + output = """ + <div class="test"> + <section> + <div class="shortform"> + Question {q} ({points}/{max}) {checkOrX} + </div> + <div class="longform"> + {messages} + </div> + </section> + </div> + """.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("</div>") + 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 0000000000000000000000000000000000000000..fa1a1ec94bb1a2e37891fa1b2fcbdb93bb371ff7 --- /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 0000000000000000000000000000000000000000..b4d604d7d79181ecf9937a3347543ba4ae5b0e8b --- /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 "<FruitShop: %s>" % self.getName() + + def __repr__(self): + return str(self) diff --git a/tutorial/shopAroundTown.py b/tutorial/shopAroundTown.py new file mode 100644 index 0000000000000000000000000000000000000000..fd1a6c27e4d5e3b55d8c9c5f4bd4a65cb02ff5c3 --- /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 0000000000000000000000000000000000000000..9cef4cc86d7e0f6ac1eca1ae9beca80eb2dd924b --- /dev/null +++ b/tutorial/shopSmart.py @@ -0,0 +1,47 @@ +# 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 + """ + "*** YOUR CODE HERE ***" + cs = [s.getPriceOfOrder(orderList) for s in fruitShops] + return fruitShops[cs.index(min(cs))] + + +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/submission_autograder.py b/tutorial/submission_autograder.py new file mode 100644 index 0000000000000000000000000000000000000000..e0489a116d26fb92fd23b969100c7f569c99c022 --- /dev/null +++ b/tutorial/submission_autograder.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import print_function +from codecs import open +import os, ssl +if (not os.environ.get('PYTHONHTTPSVERIFY', '') and getattr(ssl, '_create_unverified_context', None)): + ssl._create_default_https_context = ssl._create_unverified_context + +""" +CS 188 Local Submission Autograder +Written by the CS 188 Staff + +============================================================================== + _____ _ _ + / ____| | | | + | (___ | |_ ___ _ __ | | + \___ \| __/ _ \| '_ \| | + ____) | || (_) | |_) |_| + |_____/ \__\___/| .__/(_) + | | + |_| + +Modifying or tampering with this file is a violation of course policy. +If you're having trouble running the autograder, please contact the staff. +============================================================================== +""" +import bz2, base64 +exec(bz2.decompress(base64.b64decode('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'))) + diff --git a/tutorial/testClasses.py b/tutorial/testClasses.py new file mode 100644 index 0000000000000000000000000000000000000000..7a4ce35ba9432a3c4f57269d41947830312888a0 --- /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 0000000000000000000000000000000000000000..15646d0f6df6029220345b09fc8a49bc180122e7 --- /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 0000000000000000000000000000000000000000..eec8c0bb1ac467c116425c86f23e0e58bb0bbaff --- /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 0000000000000000000000000000000000000000..279f0f0ca87e4dec7da1450bb477755f583aa64c --- /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 0000000000000000000000000000000000000000..62619756aa94ea9bd687f3cad8e3b21780fbaabe --- /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 0000000000000000000000000000000000000000..2d807c7d4cac8bdce7a70b6ee5ddf96f283082f6 --- /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 0000000000000000000000000000000000000000..49ebaa01fb689e02c5c7410bcefa4744d8e76866 --- /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 0000000000000000000000000000000000000000..76a0d91429d2324ff0a443b8b350a52131d7e1a0 --- /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 0000000000000000000000000000000000000000..c2584706e5dee1683bf49536c42556c6e14974e2 --- /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 0000000000000000000000000000000000000000..462ff131ae8e2c95f169961757eabf388553d282 --- /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 0000000000000000000000000000000000000000..279f0f0ca87e4dec7da1450bb477755f583aa64c --- /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 0000000000000000000000000000000000000000..b2a8e87f0e44739262306263ccbcc18456b29d56 --- /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 0000000000000000000000000000000000000000..93d2c3d746f0bcf7862c206839b1a03dddddce19 --- /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 0000000000000000000000000000000000000000..e3ec5e814a44eab4973d309d281c410efb071d65 --- /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 0000000000000000000000000000000000000000..b70e8d93ca239ecbe28c611d5ec29c7576002e1c --- /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 0000000000000000000000000000000000000000..23976d69ae8d0ee22dfe0d318ec728124789e42b --- /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 0000000000000000000000000000000000000000..9d9a395c9acdf5ee0afedc2654fcdc1db802c978 --- /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 0000000000000000000000000000000000000000..279f0f0ca87e4dec7da1450bb477755f583aa64c --- /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 0000000000000000000000000000000000000000..083c232ef5bf98a4250ebb1252bf5068726cca2c --- /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: "<FruitShop: shop1>" diff --git a/tutorial/test_cases/q3/select_shop1.test b/tutorial/test_cases/q3/select_shop1.test new file mode 100644 index 0000000000000000000000000000000000000000..05b358acdfebf2d0172eefa829db91b4404fac85 --- /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 0000000000000000000000000000000000000000..849788168e0898967cdf33acf14f7501ef8116a4 --- /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: "<FruitShop: shop2>" diff --git a/tutorial/test_cases/q3/select_shop2.test b/tutorial/test_cases/q3/select_shop2.test new file mode 100644 index 0000000000000000000000000000000000000000..04cb6f7d0ee286231060420789e46e937ca0ba90 --- /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 0000000000000000000000000000000000000000..a6b63521496249d894873659451eaee3497ca48b --- /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: "<FruitShop: shop3>" diff --git a/tutorial/test_cases/q3/select_shop3.test b/tutorial/test_cases/q3/select_shop3.test new file mode 100644 index 0000000000000000000000000000000000000000..24eeb06d4d45ce0247614fb1b0be0ce67b16d84c --- /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 0000000000000000000000000000000000000000..21f2b2ca4c903435fb42ee9e7040278538efc52f --- /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 0000000000000000000000000000000000000000..dfbce0198d3aa49e7dda8d5eb2732303b6811276 --- /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/tutorial.token b/tutorial/tutorial.token new file mode 100644 index 0000000000000000000000000000000000000000..d42145ce685cf8f2964af72db281400fdd0a27f6 --- /dev/null +++ b/tutorial/tutorial.token @@ -0,0 +1 @@ +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 diff --git a/tutorial/tutorialTestClasses.py b/tutorial/tutorialTestClasses.py new file mode 100644 index 0000000000000000000000000000000000000000..07ea4c348ad8374ea7bd74a1ec243d70b9c48386 --- /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 0000000000000000000000000000000000000000..50cd74a58362ec449c3d1f3dc40eb6ea5f79ccb5 --- /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)) == "<type 'module'>"] + 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("<Press enter/return to continue>") + 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 diff --git a/unitgrade_private/__pycache__/hidden_create_files.cpython-36.pyc b/unitgrade_private/__pycache__/hidden_create_files.cpython-36.pyc index 5bbc47a3f98795e19bc84bc75aea42370b40e855..f40a0a12093b3c455ff55030ca869e4f35c1bea3 100644 Binary files a/unitgrade_private/__pycache__/hidden_create_files.cpython-36.pyc and b/unitgrade_private/__pycache__/hidden_create_files.cpython-36.pyc differ diff --git a/unitgrade_private/__pycache__/hidden_gather_upload.cpython-36.pyc b/unitgrade_private/__pycache__/hidden_gather_upload.cpython-36.pyc index d1b8579b18a393e2fdb792be88d9ac27a13a0e6b..2698f77a94381be7470acd475c0945400637cdb6 100644 Binary files a/unitgrade_private/__pycache__/hidden_gather_upload.cpython-36.pyc and b/unitgrade_private/__pycache__/hidden_gather_upload.cpython-36.pyc differ diff --git a/unitgrade_private/hidden_create_files.py b/unitgrade_private/hidden_create_files.py index b1dcda2c7794bcf8646670226be2d31fbf6f9ade..a4f03f4cbb706cdc7284526d030e9f48de7526f0 100644 --- a/unitgrade_private/hidden_create_files.py +++ b/unitgrade_private/hidden_create_files.py @@ -7,6 +7,8 @@ import pickle import inspect # import json import os +from unitgrade_private import hidden_gather_upload +# import time data = """ {{head}} @@ -14,7 +16,7 @@ data = """ report1_source = {{source}} report1_payload = {{payload}} name="{{Report1}}" -from unitgrade.unitgrade_helpers import source_instantiate + report = source_instantiate(name, report1_source, report1_payload) output_dir = os.path.dirname(__file__) gather_upload_to_campusnet(report, output_dir) @@ -44,9 +46,9 @@ def strip_main(report1_source): def setup_grade_file_report(ReportClass, execute=True, obfuscate=False, minify=False, bzip=True, nonlatin=False): # from irlc.autograde.autograde import setup_answers - import time print("Seeting up answers...") setup_answers(ReportClass()) + import time time.sleep(0.1) print("Packing student files...") # pack report into a binary blob thingy the students can run on their own. @@ -59,13 +61,46 @@ def setup_grade_file_report(ReportClass, execute=True, obfuscate=False, minify=F payload = cache_read(report.computed_answers_file) picklestring = pickle.dumps(payload) - from unitgrade_private import hidden_gather_upload - # from unitgrade_private import unitgrade_helpers - pyhead = "" - for fname in [hidden_gather_upload.__file__]: #, unitgrade_helpers.__file__]: - with open(fname, 'r') as f: - pyhead += f.read() + "\n" + "\n" + from unitgrade import unitgrade_helpers + import unitgrade + + # Include all framework-specific stuff in the report. + + + # for fname in [unitgrade.__file__, unitgrade.unitgrade.__file__, unitgrade_helpers.__file__, hidden_gather_upload.__file__]: + # with open(fname, 'r') as f: + # pyhead += f.read() + "\n" + "\n" + excl = ["unitgrade.unitgrade_helpers", + "from . import", + "from unitgrade.", + "from unitgrade ", + "import unitgrade"] + + def rmimports(s, excl): + s = "\n".join([l for l in s.splitlines() if not any([l.strip().startswith(e) for e in excl])]) + return s + + unitgrade_framework = [unitgrade.__file__, unitgrade.unitgrade.__file__, unitgrade_helpers.__file__, hidden_gather_upload.__file__] + def lload(flist, excl): + s = "" + for fname in flist: + with open(fname, 'r', encoding="utf-8") as f: + s += f.read() + "\n" + "\n" + s = rmimports(s, excl) # remove import statements from helper class. + return s + report1_source = rmimports(report1_source, excl) + + pyhead = lload(unitgrade_framework, excl) + report1_source = pyhead + "\n" + report1_source + + print("\n".join([l for l in pyhead.splitlines() if l.find("unitgrade") > 0])) + # pyhead = "\n".join([l for l in pyhead.splitlines() if + # not any([l.startswith(e) for e in excl])]) # remove import statements from helper class. + + # for fname in [hidden_gather_upload.__file__]: + # with open(fname, 'r') as f: + # pyhead += f.read() + "\n" + "\n" s = jinja2.Environment().from_string(data).render({'Report1': ReportClass.__name__, 'source': repr(report1_source), @@ -74,7 +109,7 @@ def setup_grade_file_report(ReportClass, execute=True, obfuscate=False, minify=F 'head': pyhead}) output = fn[:-3] + "_grade.py" - with open(output, 'w') as f: + with open(output, 'w', encoding="utf-8") as f: f.write(s) if minify: # obfuscate: diff --git a/unitgrade_private/hidden_gather_upload.py b/unitgrade_private/hidden_gather_upload.py index 21a02d6532c3aa6afec734f5247de0af06bc3e75..ff1d809c93562e0ff94d757f0df95a5acd5b99af 100644 --- a/unitgrade_private/hidden_gather_upload.py +++ b/unitgrade_private/hidden_gather_upload.py @@ -5,6 +5,7 @@ import inspect import json import os import bz2 +import pickle def bzwrite(json_str, token): # to get around obfuscation issues with getattr(bz2, 'open')(token, "wt") as f: @@ -44,3 +45,10 @@ def gather_upload_to_campusnet(report, output_dir=None): print("To get credit for your results, please upload the single file: ") print(">", token) print("To campusnet without any modifications.") + +def source_instantiate(name, report1_source, payload): + eval("exec")(report1_source, globals()) + report = eval(name)() + pl = pickle.loads(payload) + report.set_payload(pl) + return report