PageRenderTime 42ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/CS 6.00/ProblemSet8/P08.py

https://github.com/apollolj/MIT-OpenCourseWare
Python | 393 lines | 245 code | 20 blank | 128 comment | 6 complexity | 5b81539e65e8dd0c413f7e234a92f303 MD5 | raw file
  1. # MIT OpenCourseWare - Online Education
  2. # CS 6.00 - Intro to Computer Science
  3. # Problem set 8 - Intelligent Course Advisor
  4. # Student: May Pongpitpitak
  5. # July 30, 2011
  6. import time
  7. SUBJECT_FILENAME = "subjects.txt"
  8. VALUE, WORK = 0, 1
  9. #
  10. # Problem 1: Building A Subject Dictionary
  11. #
  12. def loadSubjects(filename):
  13. """
  14. Returns a dictionary mapping subject name to (value, work), where the name
  15. is a string and the value and work are integers. The subject information is
  16. read from the file named by the string filename. Each line of the file
  17. contains a string of the form "name,value,work".
  18. returns: dictionary mapping subject name to (value, work)
  19. """
  20. # The following sample code reads lines from the specified file and prints
  21. # each one.
  22. # TODO: Instead of printing each line, modify the above to parse the name,
  23. # value, and work of each subject and create a dictionary mapping the name
  24. # to the (value, work).
  25. inputFile = open(filename,'r', 1)
  26. subjects = {}
  27. for line in inputFile:
  28. nextSubject = line.strip().split(',') # First remove the extra chars then split them
  29. # subjectName = nextSubject[0]
  30. # subjectValue = int(nextSubject[1])
  31. # subjectWork = int(nextSubject[2])
  32. subjects[nextSubject[0]] = {VALUE : int(nextSubject[1]), WORK : int(nextSubject[2])}
  33. return(subjects)
  34. def printSubjects(subjects):
  35. """
  36. Prints a string containing name, value, and work of each subject in
  37. the dictionary of subjects and total value and work of all subjects
  38. """
  39. totalVal, totalWork = 0,0
  40. if len(subjects) == 0:
  41. return 'Empty SubjectList'
  42. res = 'Course\tValue\tWork\n======\t====\t=====\n'
  43. subNames = list(subjects.keys()) # Added list() to the code to force conversion from keys to list. object.keys() doesn't return a list-type and can't be sorted.
  44. subNames.sort()
  45. for s in subNames:
  46. val = subjects[s][VALUE]
  47. work = subjects[s][WORK]
  48. res = res + s + '\t' + str(val) + '\t' + str(work) + '\n'
  49. totalVal += val
  50. totalWork += work
  51. res = res + '\nTotal Value:\t' + str(totalVal) +'\n'
  52. res = res + 'Total Work:\t' + str(totalWork) + '\n'
  53. print(res)
  54. def cmpValue(subInfo1, subInfo2):
  55. """
  56. Returns True if value in (value, work) tuple subInfo1 is GREATER than
  57. value in (value, work) tuple in subInfo2
  58. """
  59. val1 = subInfo1[VALUE]
  60. val2 = subInfo2[VALUE]
  61. return val1 > val2
  62. def cmpWork(subInfo1, subInfo2):
  63. """
  64. Returns True if work in (value, work) tuple subInfo1 is LESS than than work
  65. in (value, work) tuple in subInfo2
  66. """
  67. work1 = subInfo1[WORK]
  68. work2 = subInfo2[WORK]
  69. return work1 < work2
  70. def cmpRatio(subInfo1, subInfo2):
  71. """
  72. Returns True if value/work in (value, work) tuple subInfo1 is
  73. GREATER than value/work in (value, work) tuple in subInfo2
  74. """
  75. val1 = subInfo1[VALUE]
  76. val2 = subInfo2[VALUE]
  77. work1 = subInfo1[WORK]
  78. work2 = subInfo2[WORK]
  79. return float(val1) / work1 > float(val2) / work2
  80. #
  81. # Problem 2: Subject Selection By Greedy Optimization
  82. #
  83. def greedyAdvisor(subjects, maxWork, comparator):
  84. """
  85. Returns a dictionary mapping subject name to (value, work) which includes
  86. subjects selected by the algorithm, such that the total work of subjects in
  87. the dictionary is not greater than maxWork. The subjects are chosen using
  88. a greedy algorithm. The subjects dictionary should not be mutated.
  89. subjects: dictionary mapping subject name to (value, work)
  90. maxWork: int >= 0
  91. comparator: function taking two tuples and returning a boolean
  92. returns: dictionary mapping subject name to (value, work)
  93. """
  94. # TODO...
  95. assert comparator is 'cmpRatio' or 'cmpValue' or 'cmpWork' , "Invalid comparator"
  96. assert maxWork > 0 , "Maximum work limit is not enough"
  97. bestCourse = list(subjects.keys())[0]
  98. greedyCourseList = {}
  99. totalWork = 0
  100. # Select course by best value/work ratio
  101. if comparator is 'cmpRatio':
  102. while totalWork < maxWork:
  103. # Selecting the best course available in the list
  104. for course in subjects.keys():
  105. if cmpRatio(subjects[course], subjects[bestCourse]) is True \
  106. and course not in greedyCourseList.keys():
  107. bestCourse = course
  108. # If not exceeding maxWork, add the selected course to the list
  109. totalWork += subjects[bestCourse][WORK]
  110. if totalWork <= maxWork:
  111. greedyCourseList[bestCourse] = subjects[bestCourse]
  112. # Select course by best value
  113. elif comparator is 'cmpValue':
  114. while totalWork < maxWork:
  115. # Selecting the best course available in the list
  116. for course in subjects.keys():
  117. if cmpValue(subjects[course], subjects[bestCourse]) is True \
  118. and course not in greedyCourseList.keys():
  119. bestCourse = course
  120. # If not exceeding maxWork, add the selected course to the list
  121. totalWork += subjects[bestCourse][WORK]
  122. if totalWork <= maxWork:
  123. greedyCourseList[bestCourse] = subjects[bestCourse]
  124. # Select course by best work load
  125. elif comparator is 'cmpWork' :
  126. while totalWork < maxWork:
  127. # Selecting the best course available in the list
  128. for course in subjects.keys():
  129. if cmpWork(subjects[course], subjects[bestCourse]) is True \
  130. and course not in greedyCourseList.keys():
  131. bestCourse = course
  132. # If not exceeding maxWork, add the selected course to the list
  133. totalWork += subjects[bestCourse][WORK]
  134. if totalWork <= maxWork:
  135. greedyCourseList[bestCourse] = subjects[bestCourse]
  136. return(greedyCourseList)
  137. def bruteForceAdvisor(subjects, maxWork):
  138. """
  139. Returns a dictionary mapping subject name to (value, work), which
  140. represents the globally optimal selection of subjects using a brute force
  141. algorithm.
  142. subjects: dictionary mapping subject name to (value, work)
  143. maxWork: int >= 0
  144. returns: dictionary mapping subject name to (value, work)
  145. """
  146. nameList = list(subjects.keys())
  147. tupleList = list(subjects.values())
  148. bestSubset, bestSubsetValue = \
  149. bruteForceAdvisorHelper(tupleList, maxWork, 0, None, None, [], 0, 0)
  150. outputSubjects = {}
  151. for i in bestSubset:
  152. outputSubjects[nameList[i]] = tupleList[i]
  153. return outputSubjects
  154. def bruteForceAdvisorHelper(subjects, maxWork, i, bestSubset, bestSubsetValue, \
  155. subset, subsetValue, subsetWork):
  156. # Hit the end of the list.
  157. if i >= len(subjects):
  158. if bestSubset == None or subsetValue > bestSubsetValue:
  159. # Found a new best.
  160. return subset[:], subsetValue
  161. else:
  162. # Keep the current best.
  163. return bestSubset, bestSubsetValue
  164. else:
  165. s = subjects[i]
  166. # Try including subjects[i] in the current working subset.
  167. if subsetWork + s[WORK] <= maxWork:
  168. subset.append(i)
  169. bestSubset, bestSubsetValue = bruteForceAdvisorHelper(subjects, \
  170. maxWork, i+1, bestSubset, bestSubsetValue, subset, \
  171. subsetValue + s[VALUE], subsetWork + s[WORK])
  172. subset.pop()
  173. bestSubset, bestSubsetValue = bruteForceAdvisorHelper(subjects, \
  174. maxWork, i+1, bestSubset, bestSubsetValue, subset, \
  175. subsetValue, subsetWork)
  176. return bestSubset, bestSubsetValue
  177. #
  178. # Problem 3: Subject Selection By Brute Force
  179. #
  180. def bruteForceTime(subjects, maxWork):
  181. """
  182. Runs tests on bruteForceAdvisor and measures the time required to compute
  183. an answer.
  184. """
  185. # TODO...
  186. print("Measuring Brute Force method's performance.")
  187. startTimer = time.time()
  188. bruteForceCourseList = bruteForceAdvisor(subjects, maxWork)
  189. endTimer = time.time()
  190. performanceTime = endTimer-startTimer
  191. print("Total time to completion:", performanceTime)
  192. return(performanceTime, bruteForceCourseList)
  193. # Problem 3 Observations
  194. # ======================
  195. #
  196. # TODO: write here your observations regarding bruteForceTime's performance
  197. #
  198. # Problem 4: Subject Selection By Dynamic Programming
  199. #
  200. def dpAdvisor(subjects, maxWork):
  201. """
  202. Returns a dictionary mapping subject name to (value, work) that contains a
  203. set of subjects that provides the maximum value without exceeding maxWork.
  204. subjects: dictionary mapping subject name to (value, work)
  205. maxWork: int >= 0
  206. returns: dictionary mapping subject name to (value, work)
  207. """
  208. # TODO...
  209. dpCourseList = {}
  210. subjectsKeysList = list(subjects.keys())
  211. subjectsValuesList = list(subjects.values())
  212. # Get the best course list using recursive method
  213. courseListTree, courseListTreeValue = dpAdvisorHelperTree(subjectsValuesList, maxWork, \
  214. len(subjectsValuesList)-1, 0, {})
  215. for courses in courseListTree:
  216. dpCourseList[subjectsKeysList[courses]] = subjectsValuesList[courses]
  217. # Get the best course list using for-loop method
  218. # courseListTable = dpAdvisorHelperTable(subjects, maxWork)
  219. return(dpCourseList)
  220. def dpAdvisorHelperTree(subjectsValuesList, maxWork, i, trackedWork, courseMemoTree):
  221. """
  222. DESC: the best course list is determined by going down a decision tree between
  223. taking or not taking each course (binary)
  224. NOTE: This function might be better if the dictionary of all intermediate steps
  225. were made Global and doesn't need to be pass around
  226. subjectsValuesList: a list of all courses offer created from keys
  227. maxWork: limit amount of work load impose
  228. i: index of courses for tracking location on the subject list
  229. trackedWork: the total amount of work accumulated so far. This variable is also used
  230. as a second key in the courseMemoTree dictionary as it gives insight to
  231. the depth of tree
  232. courseMemoTree: a dictionary mapping index number and list of courses(keys)
  233. to the total value and work of the list for references
  234. (i, trackedWork) : (totalValue, totalWork)
  235. return: the better list of courses and its corresponding values
  236. as two separate items
  237. """
  238. # Check if the element has already been computed and stored in the reference
  239. try: return(courseMemoTree[i, trackedWork])
  240. # If not, proceed
  241. except KeyError:
  242. # Check if at the first/bottom possible element in the list.
  243. if i == 0:
  244. courseList = []
  245. courseListValue = 0
  246. courseMemoTree[i, trackedWork] = (courseList, courseListValue)
  247. return(courseList, courseListValue)
  248. else:
  249. # SCENARIO 1 - If not taking this course
  250. courseNotTaken, courseNotTakenValue = dpAdvisorHelperTree(subjectsValuesList, maxWork, i-1,
  251. trackedWork, courseMemoTree)
  252. # SCENARIO 2 - Take this course
  253. # If there is no space
  254. if trackedWork >= maxWork:
  255. courseMemoTree[i, trackedWork] = (courseNotTaken, courseNotTakenValue)
  256. return(courseNotTaken, courseNotTakenValue)
  257. # If there is space
  258. elif (trackedWork + subjectsValuesList[i][WORK]) <= maxWork:
  259. # Get the beginning of the list. Remember we are working backward in this method.
  260. courseTaken, courseTakenValue = dpAdvisorHelperTree(subjectsValuesList, maxWork, i-1,
  261. trackedWork + subjectsValuesList[i][WORK], courseMemoTree)
  262. courseTaken.append(i)
  263. courseTakenValue += subjectsValuesList[i][VALUE]
  264. # Compare the results from the two scenarios
  265. courseListValue = max(courseTakenValue, courseNotTakenValue)
  266. if courseListValue == courseTakenValue: courseList = courseTaken
  267. else: courseList = courseNotTaken
  268. # Store for reference
  269. courseMemoTree[i, trackedWork] = (courseList, courseListValue)
  270. return(courseList, courseListValue)
  271. ##def dpAdvisorHelperTable(subjects, maxWork):
  272. ## """
  273. ## DESC: solving the problem using for-loop instead of decision tree
  274. ##
  275. ## subjects: a dictionary mapping of all courses offer to their work load and value
  276. ## maxWork: limit amount of work load impose
  277. ##
  278. ## return: a dictionary mapping the best courses and their wprk load and value
  279. ## """
  280. ##
  281. ## courseMemoTable = {0:([], 0)} # a dictionary for storing the references
  282. ## courseList = {} # the final result list of best courses
  283. ##
  284. ## # Check through all the possible total work load value even at less than max
  285. ## for trackedWork in range(maxWork):
  286. ## bestCourseCombo = ([], 0) # set/re-set the best choice (list, total value)
  287. ##
  288. ## #
  289. ## for course in subjects:
  290. ##
  291. ## # Check if we're not going over work load limit and doesn't already exist in the reference
  292. ## if subjects[course][WORK] < trackedWork and course not in courseMemoTable[trackedWork][0]:
  293. ## currentCourseComboValue = courseMemoTable[trackedWork][1] + subjects[course][VALUE]
  294. ##
  295. ## #
  296. ## if currentCourseComboValue > bestCourseCombo[1]:
  297. ## bestCourseCombo = (courseMemoTable[trackedWork][0].append(course), currentCourseComboValue)
  298. ##
  299. ## courseMemoTable[trackedWork] = bestCourseCombo
  300. ##
  301. ## #
  302. ## for course in courseMemoTable[max(courseMemoTable)][0]:
  303. ## courseList[course] = subjects[course]
  304. ##
  305. ## return(courseList)
  306. #
  307. # Problem 5: Performance Comparison
  308. #
  309. def dpTime(subjects, maxWork):
  310. """
  311. DESC: Runs tests on dpAdvisor and measures the time required to compute an
  312. answer.
  313. return: the time value and the dictionary of selected courses from dpAdvisor
  314. """
  315. # TODO...
  316. print("Measuring Dynamic Programming method's performance")
  317. startTimer = time.time()
  318. dpCourseList = dpAdvisor(subjects, maxWork)
  319. endTimer = time.time()
  320. performanceTime = endTimer-startTimer
  321. print("Total time to completion:", performanceTime)
  322. # return(performanceTime, bruteForceCourseList)
  323. # Problem 5 Observations
  324. # ======================
  325. #
  326. # TODO: write here your observations regarding dpAdvisor's performance and
  327. # how its performance compares to that of bruteForceAdvisor.