def hard_coding(val_list): message = ("Please show code that makes the computer extract " "the value from the dictionary.") code = "hard_code" tldr = "Printing raw value" # Pattern 1 possibility matches = find_matches("print(__exp__)") for match in matches: __exp__ = match["__exp__"] value = __exp__.value if value in val_list: return explain_r(message, code, label=tldr) # Pattern 2 possibility matches = find_matches("__exp__\n" "print(_var_)") for match in matches: __exp__ = match["__exp__"] _var_ = match["_var_"] submatches = __exp__.find_matches("_var_ = __exp2__") for submatch in submatches: __exp2__ = submatch["__exp2__"] value = __exp2__.value if value in val_list: return explain_r(message, code, label=tldr) return False
def prevent_advanced_iteration(): message = "You should not use a <code>while</code> loop to solve this problem." code = "while_usage" label = "Usage of <code>while</code>" ast = parse_program() if ast.find_all('While'): explain_r(message, code, label=label) prevent_builtin_usage([ 'sum', 'map', 'filter', 'reduce', 'len', 'max', 'min', 'max', 'sorted', 'all', 'any', 'getattr', 'setattr', 'eval', 'exec', 'iter' ])
def prevent_builtin_usage(function_names): message = "You cannot use the builtin function <code>{}</code>." code = "builtin_use" label = "Builtin Usage" # Prevent direction calls ast = parse_program() all_calls = ast.find_all('Call') for a_call in all_calls: if a_call.func.ast_name == 'Name': if a_call.func.id in function_names: explain_r(message.format(a_call.func.id), code, label=label) return a_call.func.id return None
def wrong_debug_10_6(): """ Should be on change feedback as opposed to on-run Returns: """ message = "This is not one of the two changes needed. Undo the change and try again." code = "debug_10.6" tldr = "At least one unnecessary change" matches = find_matches('quakes = earthquakes.get("location.depth","(None)","")\n' 'quakes_in_miles = []\n' 'for quake in _list1_:\n' ' _list2_.append(quake * 0.62)\n' 'plt.hist(quakes_in_miles)\n' 'plt.xlabel("Depth in Miles")\n' 'plt.ylabel("Number of Earthquakes")\n' 'plt.title("Distribution of Depth in Miles of Earthquakes")\n' 'plt.show()') for match in matches: name1 = match["_list1_"][0].ast_node.id name2 = match["_list2_"][0].ast_node.id master_list = ["quake", "quakes", "quakes_in_miles"] if (name1 in master_list and name2 in master_list and name1 != "quakes_in_miles" and name2 != "quakes" and (name1 != "quake" or name2 != "quake")): return False return explain_r(message, code, label=tldr)
def wrong_filter_problem_atl1_10_5(): """ find pattern where expression is equal to _item_*0.62 and where the condition is not equivalent to _expr_ > 10 Returns: """ message = "You are not correctly filtering out values from the list." code = "filt_alt1_10.5" tldr = "Incorrect Filter Statement" matches = find_matches("for _item_ in ___:\n" " if __cond__:\n" " _list_.append(__expr__)") if matches: for match in matches: _item_ = match["_item_"][0].astNode __cond__ = match["__cond__"] __expr__ = match["__expr__"] # matches02 = __expr__.find_matches("{0!s}*0.62".format(_item_.id)) matches02 = __expr__.find_matches("_item_*0.62") if matches02: for match02 in matches02: if (__cond__.has(_item_) and not __cond__.numeric_logic_check(0.1, "item > 16.1290322580645")): return explain_r(message, code, label=tldr) return False
def wrong_should_be_summing(): """ std_ast = parse_program() for_loops = std_ast.find_all('For') for loop in for_loops: assignments = loop.find_all('Assign') for assignment in assignments: binops = assignment.find_all('BinOp') for binop in binops: if binop.has(1) and binop.op == 'Add': explain('This problem asks for the total of all the values in the list not the number of items in ' 'the list.<br><br><i>(not_sum)<i></br>') """ message = "This problem asks for the total of all the values in the list not the number of items in the list." code = "not_sum" tldr = "Counting instead of summing" matches = find_matches("for _item_ in ___:\n" " __expr__") if matches: for match in matches: __expr__ = match["__expr__"] submatches = __expr__.find_matches("___ = 1 + ___", ) if submatches: return explain_r(message, code, label=tldr) return False
def missing_key(keys): """ Checks if student is missing a key TODO: Should be good if run AFTER the var_instead_of_key check, although it doesn't appear to catch a key that's been assigned as the value of an unused variable. :param keys: list of keys :type keys: list of Str :return: Feedback String :rtype: Str """ message = "You seem to be missing the following dictionary key(s):<ul>{}</ul>" code = "miss_key" tldr = "Missing necessary keys" key_list = "" first = False for key in keys: matches = find_matches("\"{}\"".format(key)) if not matches: if not first: key_list += ", " key_list += '<li><code>"' + key + '"</code></li>' if key_list != "": return explain_r(message.format(key_list), code, label=tldr) return False
def histogram_argument_not_list(): """ Name: histogram_argument_not_list Pattern: plt.hist(<argument>) Where type(<argument>) is not "list" Feedback: Making a histogram requires a list; <argument> is not a list. Returns: """ message = "Making a histogram requires a list; <code>{0!s}</code> is not a list." code = "hist_arg_not_list" tldr = "Making Histogram from Non-list" matches = find_matches("plt.hist(_argument_)") if matches: for match in matches: _argument_ = match["_argument_"].astNode if not _argument_.get_data_state( ) or not _argument_.get_data_state().was_type('list'): return explain_r(message.format(_argument_.id), code, label=tldr) return False
def histogram_wrong_list(): """ Name: histogram_wrong_list Pattern: for ___ in ___: <target>.append(___) plt.hist(<list>) where name(<target>) != name(<list>) Feedback: The list created in the iteration is not the list being used to create the histogram. Returns: """ message = "The list created in the iteration is not the list being used to create the histogram." code = "histo_wrong_list" tldr = "Plotting Wrong List" matches = find_matches("for ___ in ___:\n" " __expr__\n" "plt.hist(_list_)") if matches: for match in matches: _list_ = match["_list_"].astNode __expr__ = match["__expr__"] submatches = __expr__.find_matches("_list_.append(___)") if submatches: return False return explain_r(message, code, label=tldr) return False
def key_order_unchained(keys): message = "It looks like you aren't using the correct keys, or the correct key order. Double check your data map." code = "key_order_u" tldr = "Wrong key order" construct = None find_chain = "" for a_slice in range(len(keys)): find_chain += "_var{a2}_ = _var{a1}_[__str{a1}__]\n".format( a2=a_slice + 1, a1=a_slice) if find_match(find_chain): construct = "" count = 0 for key in keys: construct += "_var{a2}_ = _var{a1}_['{key}']\n".format(a2=count + 1, a1=count, key=key) count += 1 if construct: matches = find_matches(construct) if not matches: return explain_r(message, code, label=tldr) return False
def missing_addition_slot_empty(): """ std_ast = parse_program() assignments = std_ast.find_all('Assign') for assignment in assignments: # left = assignment.target right = assignment.value binOp = right.find_all('BinOp') if len(binOp) == 1: binOp = binOp[0] if binOp.op == 'Add': if binOp.left.ast_name == 'Name' and binOp.right.ast_name == 'Name': if binOp.left.id == '___' or binOp.right.id == '___': explain('You must fill in the empty slot in the addition.<br><br><i>(add_empty)<i></br>') return True return False Returns: """ message = "You must fill in the empty slot in the addition." code = "add_empty" tldr = "Addition Blank" matches = find_matches("___ + _item_") if matches: for match in matches: _item_ = match["_item_"][0] if _item_.id == "___": return explain_r(message, code, tldr) return False
def key_order(keys): # TODO: Is it possible to run this test after confirming (through other tests) that there are no unused keys and # that all keys used are the correct keys, such that the feedback message can explicitly address JUST the case of # wrong order? message = "It looks like you aren't using the correct keys, or the correct key order. Double check your data map." code = "key_order_c" tldr = "Wrong key order" construct = None # Assemble chain of dictionary slicing find_chain = "_var_" for a_slice in range(len(keys)): find_chain += "[__str{}__]".format(a_slice) # If we find a chain of dictionary accesses if find_match(find_chain): # Assemble a new match pattern using the provided key order construct = "_var_" for key in keys: construct += "['{}']".format(key) if construct: # check if we have a set of keys of the proper order matches = find_matches(construct) if not matches: return explain_r(message, code, label=tldr) return False
def wrong_cannot_sum_list(): """ std_ast = parse_program() for_loops = std_ast.find_all('For') for loop in for_loops: list_prop = loop.iter assignments = loop.find_all('Assign') for assignment in assignments: binops = assignment.find_all('BinOp') for binop in binops: if binop.has(list_prop) and binop.op == 'Add': explain('Addition can only be done with a single value at a time, not with an entire list at one' ' time.<br><br><i>(sum_list)<i></br>') Returns: """ message = 'Addition can only be done with a single value at a time, not with an entire list at one' code = "sum_list" tldr = "Cannot Sum a List" matches = find_matches("for ___ in _list_ :\n" " __expr__") if matches: for match in matches: _list_ = match["_list_"][0] __expr__ = match["__expr__"] # submatches = __expr__.find_matches("___ = ___ + {}".format(_list_.id), ) submatches = __expr__.find_matches("___ = ___ + _list_") if submatches: return explain_r(message, code, label=tldr) return False
def missing_counting_list(): """ std_ast = parse_program() has_count = False for_loops = std_ast.find_all('For') if len(for_loops) > 0: for loop in for_loops: assignments = loop.find_all('Assign') if len(assignments) < 1: continue for assignment in assignments: binops = assignment.find_all('BinOp') if len(binops) < 1: continue lhs = assignment.target for binop in binops: if binop.has(lhs) and binop.has(1) and binop.op == 'Add': has_count = True if not has_count: explain('Count the total number of items in the list using iteration.<br><br><i>(miss_count_list)<i></br>') Returns: """ message = 'Count the total number of items in the list using iteration.' code = "miss_count_list" tldr = "Missing Count in Iteration" matches = find_matches("for _item_ in ___:\n" " __expr__") if matches: for match in matches: __expr__ = match["__expr__"] submatches = __expr__.find_matches("_sum_ = _sum_ + 1", ) if submatches: return False return explain_r(message, code, label=tldr)
def wrong_duplicate_var_in_add(): message = "You are adding the same variable twice; you need two different variables in your addition." code = "dup_var" tldr = "Duplicate Division" match = find_match("_item_ + _item_") if match: return explain_r(message, code, label=tldr) return False
def plot_group_error(output=None, plots=None): if output is None: output = get_output() if plots is None: plots = get_plots() if len(plots) > 1: explain_r('You should only be plotting one thing!', "print_one", "Multiple Calls to plot") return True elif len(plots) == 0: explain_r('The algorithm is plotting an empty list. Check your logic.', 'blank_plot', "Blank Plot") return True elif output: explain('You should be plotting, not printing!', 'printing', "Printing instead of Plotting") return True elif len(plots[0]['data']) != 1: explain('You should only be plotting one thing!', 'one_plot', "Too Many Plots") return True
def missing_iterator_initialization(): message1 = "The slot to hold a list in the iteration is empty." code1 = "no_iter_init-blank" tldr1 = "Iteration Variable is Blank" message2 = "The variable <code>{0!s}</code> is in the list slot of the iteration but is not a list." code2 = "no_iter_init" tldr2 = "Iteration Variable is Not a List" match = find_match("for ___ in _list_:\n pass") if match: _list_ = match["_list_"].astNode if _list_.id == "___": return explain_r(message1, code1, label=tldr1) elif not data_state(_list_).was_type('list'): return explain_r(message2.format(_list_.id), code2, label=tldr2) return False
def wrong_accumulator_initialization_9_2(): message = ("The variable to hold the total value of the rainfall amounts " "(<code>rainfall_count</code>) is not initialized properly.") code = "accu_init_9.2" tldr = "Incorrect Initialization" if not find_matches("rainfall_count = 0"): return explain_r(message, code, label=tldr) return False
def missing_no_print(): message = "Program does not output anything." code = "no_print" tldr = "Missing Output" prints = find_match('print(___)', cut=True) if not prints: return explain_r(message, code, label=tldr) return False
def wrong_list_initialization_9_2(): message = "The list of rainfall amounts (<code>rainfall_list</code>) is not initialized properly." code = "list_init_9.2" tldr = "Incorrect List Initialization" matches = find_matches('rainfall_list = weather.get("Data.Precipitation","Station.Location","Blacksburg, VA")') if not matches: return explain_r(message, code, label=tldr) return False
def app_assign(): message = ("Appending modifies the list, so unlike addition," " an assignment statement is not needed when using append.") code = "app_asgn" matches = find_matches("_sum_ = _sum_.append(__exp__)") if matches: return explain_r(message, code) return False
def wrong_iteration_body_9_1(): message = "The addition of each rainfall amount to the total rainfall is not in the correct place." code = "iter_body_9.1" tldr = "Accumulation Statement Misplaced or Missing" matches = find_matches("for _item_ in _list_:\n" " rainfall_sum = ___") if not matches: return explain_r(message, code, label=tldr) return False
def wrong_accumulator_initialization_9_1(): message = ("The variable to hold the total value of the rainfall amounts (<code>rainfall_sum</code>) " "is not initialized properly.") code = "accu_init_9.1" tldr = "Incorrect Accumulation Variable initialization" match = find_match("rainfall_sum = 0") if not match: return explain_r(message, code, label=tldr) return False
def col_dict(): message = "When using multiple keys, each key should have it's own set of brackets." code = "col_dict" tldr = "Improper Dictionary Access" matches = find_matches("_var_[__str1__: __str2__]") if matches: return explain_r(message, code, label=tldr) return False
def wrong_iteration_body_8_3(): message = "The addition of each episode length to the total length is not in the correct place." code = "iter_body_8.3" tldr = "Accumulation Misplaced" match = find_match("for _item_ in _list_:\n" " sum_length = ___ + ___\n") if not match: return explain_r(message, code, label=tldr) return False
def wrong_for_inside_if(): message = "The iteration should not be inside the decision block." code = "for_in_if" tldr = "For inside if" match = find_match("if ___:\n" " for ___ in ___:\n" " pass") if match: return explain_r(message, code, label=tldr) return False
def wrong_list_is_constant_8_2(): message = 'You must set <code>shoppping_cart</code> to a list of values not to a single number.' code = "list_is_const_8.2" tldr = "Shopping Cart not set to list" matches = find_matches("shopping_cart = __expr__") for match in matches: __expr__ = match["__expr__"] if __expr__.ast_name == "Num": return explain_r(message, code, label=tldr) return False
def func_filter(keys): message = "Please do not modify the function call to retrieve the data." code = "func_filt" tldr = "Attempting to filter using fetch" matches = find_matches("_var_.get_weather(__str__)") for match in matches: __str__ = match["__str__"] if __str__.value in keys: # TODO: Relies on .value returning id for Name nodes return explain_r(message, code, label=tldr) return False
def miss_dict_acc(): message = ( "You are missing something that looks like a dictionary access. " "In this unit, you should be using dictionary access") code = "miss_acc" tldr = "Missing Dictionary Access" if not find_matches("_var_[__str1__]"): return explain_r(message, code, label=tldr) return False
def missing_target_slot_empty(): message = "You must fill in the empty slot in the iteration." code = "target_empty" tldr = "Missing Iteration Variable" match = find_match("for _item_ in ___:\n pass") if match: _item_ = match["_item_"].astNode if _item_.id == "___": return explain_r(message, code, label=tldr) return False