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):
"""