CAT-SOOP is a flexible, programmable learning management system based on the Python programming language. https://catsoop.mit.edu
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

186 lines
5.3 KiB

  1. # This file is part of CAT-SOOP
  2. # Copyright (c) 2011-2020 by The CAT-SOOP Developers <catsoop-dev@mit.edu>
  3. #
  4. # This program is free software: you can redistribute it and/or modify it under
  5. # the terms of the GNU Affero General Public License as published by the Free
  6. # Software Foundation, either version 3 of the License, or (at your option) any
  7. # later version.
  8. #
  9. # This program is distributed in the hope that it will be useful, but WITHOUT
  10. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  11. # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  12. # details.
  13. #
  14. # You should have received a copy of the GNU Affero General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. """
  17. Library of common checker functions.
  18. """
  19. import ast
  20. def equal():
  21. """
  22. Generate a check function that checks whether two values are equivalent
  23. using Python's `==`.
  24. **Parameters:** none
  25. **Returns:** a function suitable for use as a `csq_check_function`.
  26. """
  27. return lambda sub, soln: sub == soln
  28. def check_result(f=None):
  29. """
  30. Given a check function that directly compares values, return a new check
  31. function that compares the `'result'` fields of a submission and solution
  32. using the given function. For use with the `pythoncode` question type.
  33. **Parameters:**
  34. * `f` (optional:) a function that compares two values. Defaults to
  35. comparison using `==`.
  36. **Returns:** a function suitable for use as a `csq_check_function`.
  37. """
  38. f = f if f is not None else equal()
  39. def _inner(sub, soln):
  40. try:
  41. return f(sub["result"], soln["result"])
  42. except:
  43. return False
  44. return _inner
  45. def number_close(absolute_threshold=None, ratio_threshold=None):
  46. """
  47. Generate a check function that checks whether two numbers are close (within
  48. the given thresholds).
  49. **Parameters:**
  50. * `absolute_threshold` (optional): the maximum absolute difference that
  51. will be accepted.
  52. * `ration_threshold` (optional): the maximum ratio `submission / solution`
  53. that will be accepted.
  54. **Returns:** a function suitable for use as a `csq_check_function`.
  55. """
  56. def _checker(sub, sol):
  57. try:
  58. r = ratio_threshold is not None
  59. a = absolute_threshold is not None
  60. if r and a:
  61. threshold = max(ratio_threshold * sol, absolute_threshold)
  62. elif r:
  63. threshold = ratio_threshold * sol
  64. elif a:
  65. threshold = absolute_threshold
  66. else:
  67. return sub == sol
  68. if abs(sub - sol) > abs(threshold):
  69. return False
  70. except:
  71. return False
  72. return True
  73. return _checker
  74. def evaled(f=None):
  75. """
  76. Given a check function that directly compares values, return a new check
  77. function that evaluates its arguments (using `ast.literal_eval`) before
  78. calling the given function on the evaluated results
  79. **Parameters:**
  80. * `f` (optional:) a function that compares two values. Defaults to
  81. comparison using `==`.
  82. **Returns:** a function suitable for use as a `csq_check_function`.
  83. """
  84. f = f if f is not None else equal()
  85. def _inner(sub, soln):
  86. try:
  87. return f(ast.literal_eval(sub), ast.literal_eval(soln))
  88. except:
  89. return False
  90. return _inner
  91. def list_all(f=None):
  92. """
  93. Given a function that compares two values, generate a new check function
  94. that checks that two lists are equal (same values in the same order).
  95. **Parameters:**
  96. * `f` (optional:) a function that compares two values. Defaults to
  97. comparison using `==`.
  98. **Returns:** a function suitable for use as a `csq_check_function`.
  99. """
  100. f = f or equal()
  101. return lambda sub, soln: (
  102. len(sub) == len(soln) and all(f(i, j) for i, j in zip(sub, soln))
  103. )
  104. def list_all_unordered(f=None):
  105. """
  106. Given a function that directly compares two values, generate a new check
  107. function that checks whether two lists contain all the same elements
  108. (including duplicates), regardless of order.
  109. **Parameters:**
  110. * `f` (optional:) a function that compares two values. Defaults to
  111. comparison using `==`.
  112. **Returns:** a function suitable for use as a `csq_check_function`.
  113. """
  114. f = f or equal()
  115. def _cmp(sub, soln):
  116. sub = list(sub)
  117. soln = list(soln)
  118. while sub:
  119. elt = sub.pop()
  120. for elt2 in soln:
  121. if f(elt, elt2):
  122. soln.remove(elt2)
  123. break
  124. else:
  125. return False
  126. return len(sub) == len(soln) == 0
  127. return _cmp
  128. def dict_all(f=None):
  129. """
  130. Given a function that directly compares two valies, generate a new check
  131. function that takes two dictionaries and checks that all keys are idntical,
  132. and that the associated values are equivalent according to the given
  133. function.
  134. **Parameters:**
  135. * `f` (optional:) a function that compares two values. Defaults to
  136. comparison using `==`.
  137. **Returns:** a function suitable for use as a `csq_check_function`.
  138. """
  139. f = f or equal()
  140. return lambda sub, soln: (
  141. set(sub) == set(soln) and all(f(sub[i], soln[i]) for i in sub)
  142. )