From 8f0c9ab93c67ef4b6394380ff645c89b0703963d Mon Sep 17 00:00:00 2001 From: Martin Putzlocher Date: Mon, 2 Dec 2024 00:21:09 +0100 Subject: [PATCH] Neue Generierung von moodle-formulas Fragen schon weit entwickelt. Dynamischer Aufgabenaufbau. --- aufgabengenerator_formulas_fraction_new.py | 238 +++++++++++++++++++-- 1 file changed, 217 insertions(+), 21 deletions(-) diff --git a/aufgabengenerator_formulas_fraction_new.py b/aufgabengenerator_formulas_fraction_new.py index bddc6bc..953a6eb 100644 --- a/aufgabengenerator_formulas_fraction_new.py +++ b/aufgabengenerator_formulas_fraction_new.py @@ -1,19 +1,23 @@ import random +import operator +import re from math import gcd, lcm import xml.etree.ElementTree as ET from collections import Counter +# Define operators and their corresponding functions +ops = { + '+': operator.add, + '-': operator.sub, + '*': operator.mul, + '/': operator.truediv +} + # Konstanten # Anzahl der zu generierenden Fragen. NUM_QUESTIONS = 1 # Algebraische Struktur der Aufgaben -STRUCTURE = "-(+)" - -# TODO -# Platzhalter hinzufügen - -# Anzahl der benötigten Brüche -NUM_FRACTIONS = 1 + STRUCTURE.count("+") + STRUCTURE.count("+") + STRUCTURE.count("*") + STRUCTURE.count("/") +STRUCTURE = "-(+)-" # Größter Zähler MAX_NUMERATOR = 20 # Größter Nenner @@ -23,11 +27,99 @@ MAX_COMMON_DENOMINATOR = 100 # Dateiname für Ausgabe FILENAME = 'formulas_new_quiz.xml' +def parse_expression(expression): + """Parse an algebraic expression and determine the required number of numbers""" + op_count = 0 + + # Count operators in parentheses + paren_count = 1 + max_depth = 0 + + for i, char in enumerate(expression): + if char == '(': + paren_count += 1 + elif char == ')': + paren_count -= 1 + + # Update max depth when a closed parenthesis is encountered + max_depth = max(max_depth, paren_count) + + if char in ops.keys(): + op_count += 1 + + return max_depth + op_count + +# Anzahl der benötigten Brüche +NUM_FRACTIONS = parse_expression(STRUCTURE) +# Platzhalter hinzufügen +def add_placeholders(expression): + placeholder_counter = 0 # Zähler für die Platzhalter + + def placeholder_generator(): + nonlocal placeholder_counter + placeholder = f'n{placeholder_counter}' + placeholder_counter += 1 + return placeholder + + output = [] # Ausgabe als Liste zum späteren Zusammensetzen + need_placeholder = True # Flag, um zu tracken, ob ein Platzhalter hinzugefügt werden soll + + for char in expression: + if char in '+-*/': # Operator gefunden + if need_placeholder: # Wenn wir einen Platzhalter brauchen + output.append(placeholder_generator()) + output.append(char) # Fügen Sie den Operator hinzu + need_placeholder = True # Nach einem Operator müssen wir wieder einen Platzhalter hinzufügen + elif char == '(': # Eine neue Sub-Expression beginnt + output.append(char) # Fügen Sie die öffnende Klammer hinzu + need_placeholder = True + elif char == ')': # Eine Sub-Expression endet + if need_placeholder: # Wenn wir einen Platzhalter brauchen + output.append(placeholder_generator()) + output.append(char) # Fügen Sie die schließende Klammer hinzu + need_placeholder = False # Nach einer schließenden Klammer erwarten wir keine Platzhalter mehr + else: # Bei anderen Zeichen + if need_placeholder: # Wenn wir einen Platzhalter brauchen + output.append(placeholder_generator()) + output.append(char) # Fügen Sie das Zeichen hinzu + need_placeholder = False # Nach einem Platzhalter sollten wir keinen weiteren setzen + + # Platzhalter am Beginn hinzufügen + if output and output[0] != f'n{0}': + result = [placeholder_generator()] + output + else: + result = output + # Platzhalter am Ende hinzufügen, wenn nötig + if output[-1] in '+-*/': + result = result + [placeholder_generator()] + else: + pass + + # Zusammensetzen des finalen Ausdrucks + final_expression = ''.join(result).strip() + return final_expression -def create_formula_question(n): +# Algebraische Struktur mit Platzhaltern +STRUCTURE_PH = add_placeholders(STRUCTURE) + +def calculate(expression, numbers): + """Calculate the result of the expression using n[0], n[1], ...""" + # numbers = [random.randint(1, 10) for _ in range(num)] + print(numbers) + # Replace placeholders with actual number values + for i, num in enumerate(numbers): + placeholder = f'n{i}' + expression = re.sub(rf'\{placeholder}\s*([+-/*])', rf'{num} \1', expression) + expression = re.sub(rf'({placeholder})\s*', rf'{num}', expression) + + print(expression) + # Evaluate the result + return eval(expression) + +def create_formula_question(ex_number: int): numerators = [] denominators = [] for i in range(NUM_FRACTIONS): @@ -46,8 +138,11 @@ def create_formula_question(n): print(numerators) print(denominators) # [DEBUG] + + # Hauptnenner bestimmen *denoms_integers, = denominators common_denominator = lcm(*denoms_integers) + cd = common_denominator fractions = [] for i in range(NUM_FRACTIONS): @@ -57,7 +152,6 @@ def create_formula_question(n): print(fractions) # [DEBUG] - cd = common_denominator expanded_numerators = [frac["n"] * cd // frac["d"] for frac in fractions] expanded_fractions = [] @@ -68,25 +162,127 @@ def create_formula_question(n): print(expanded_fractions) # [DEBUG] - eval_string = f"{expanded_numerators[0]}" - cnt_next_fraction = 1 - for c in STRUCTURE: - if c == "(" or c ==")": - eval_string += c - elif c == "+" or c == "-" or c == "*" or c == "/": - eval_string += c - eval_string += f"{expanded_numerators[cnt_next_fraction]}" - cnt_next_fraction += 1 - + result_numerator = calculate(STRUCTURE_PH, expanded_numerators) # [DEBUG] - print(eval_string) + print(result_numerator) # [DEBUG] - result_numerator = eval(eval_string) + gcd_result = gcd(result_numerator, cd) + if gcd_result > 1: + # print("kürzbar") + shortable = True + shortened_numerator = result_numerator // gcd_result + shortened_denominator = common_denominator // gcd_result + else: + shortable = False + + # Start der Aufgabe question = ET.Element('question', attrib={'type': 'formulas'}) + # Name der Aufgabe + has_plus = '+' in STRUCTURE + has_minus = '-' in STRUCTURE + has_mult = '*' in STRUCTURE + has_div = '/' in STRUCTURE + + if has_plus and has_minus: + expression_type = "Addition/Subtraktion" + exercise_text = "Berechne den Term" + elif has_plus: + expression_type = "Addition" + exercise_text = "Addiere die Brüche" + elif has_minus: + expression_type = "Subtraktion" + exercise_text = "Subtrahiere die Brüche" + else: + expression_type = "Rechnung" + exercise_text = "Berechne den Term" + name_elem = ET.SubElement(question, 'name') + text_elem = ET.SubElement(name_elem, 'text') + text_elem.text = f'{expression_type} von {NUM_FRACTIONS} ungleichnamigen Brüchen {ex_number}' + + # Allgemeiner Aufgabentext + questiontext_elem = ET.SubElement(question, 'questiontext', attrib={'format': 'html'}) + text_elem = ET.SubElement(questiontext_elem, 'text') + if shortable: + text_elem.text = f"{exercise_text} und kürze das Ergebnis so weit wie möglich.

" + "

{#A}

]]>" + else: + text_elem.text = f"{exercise_text}.

"+"

{#A}

]]>" + + # Allgemeines Feedback + generalfeedback_elem = ET.SubElement(question, 'generalfeedback', attrib={'format': 'html'}) + text_elem = ET.SubElement(generalfeedback_elem, 'text') + text_elem.text = ' ' + + # Grundlegende Bewertungsinformationen + ET.SubElement(question, 'defaultgrade').text = '1.0000000' + ET.SubElement(question, 'penalty').text = '0.3333333' + ET.SubElement(question, 'hidden').text = '0' + ET.SubElement(question, 'idnumber').text = ' ' + + # Feedback für korrekte und inkorrekte Antworten + correctfeedback_elem = ET.SubElement(question, 'correctfeedback', attrib={'format': 'html'}) + text_elem = ET.SubElement(correctfeedback_elem, 'text') + text_elem.text = '''Die Antwort ist richtig.

]]> ''' + + partiallycorrectfeedback_elem = ET.SubElement(question, 'partiallycorrectfeedback', attrib={'format': 'html'}) + text_elem = ET.SubElement(partiallycorrectfeedback_elem, 'text') + text_elem.text = '''Die Antwort ist teilweise richtig.

]]> ''' + + incorrectfeedback_elem = ET.SubElement(question, 'incorrectfeedback', attrib={'format': 'html'}) + text_elem = ET.SubElement(incorrectfeedback_elem, 'text') + text_elem.text = '''Die Antwort ist falsch.

]]>''' + + ET.SubElement(question, 'shownumcorrect') + + varsrandom_elem = ET.SubElement(question, 'varsrandom') + text_elem = ET.SubElement(varsrandom_elem, 'text') + text_elem.text = f' ' + + # Globale Variablen definieren + varsglobal_elem = ET.SubElement(question, 'varsglobal') + text_elem = ET.SubElement(varsglobal_elem, 'text') + numerators_string = ",".join([str(n) for n in numerators]) + denominators_string = ",".join([str(d) for d in denominators]) + text_elem.text = f'n = [{numerators_string}];\n d = [{denominators_string}];' + + # Durchnummerierung der Antworten + answernumbering_elem = ET.SubElement(question, 'answernumbering') + text_elem = ET.SubElement(answernumbering_elem, 'text') + text_elem.text = 'ABCD' + + # Antworten definieren + answers_elem = ET.SubElement(question, 'answers') + + # Indizes für die Antworten + partindex_elem = ET.SubElement(answers_elem, 'partindex') + ET.SubElement(partindex_elem, 'text').text = '0' + placeholder_elem = ET.SubElement(answers_elem, 'placeholder') + ET.SubElement(placeholder_elem, 'text').text = '#A' + + # Punktzahl auf Teilaufgabe + answermark_elem = ET.SubElement(answers_elem, 'answermark') + if shortable: + ET.SubElement(answermark_elem, 'text').text = '2' + else: + ET.SubElement(answermark_elem, 'text').text = '1' + + # Antworttyp + answertype_elem = ET.SubElement(answers_elem, 'answertype') + ET.SubElement(answertype_elem, 'text').text = '0' + numbox_elem = ET.SubElement(answers_elem, 'numbox') + ET.SubElement(numbox_elem, 'text').text = '2' return question + # Berechnungsvariablen + vars1_elem = ET.SubElement(answers_elem, 'vars1') + if shortable: + vars1_elem_text = "" + else: + vars1_elem_text = "" + ET.SubElement(vars1_elem, 'text').text = vars1_elem_text + + def generate_questions(num_questions): """