Neue Generierung von moodle-formulas Fragen schon weit entwickelt. Dynamischer Aufgabenaufbau.

This commit is contained in:
Martin Putzlocher 2024-12-02 00:21:09 +01:00
parent dd5e8c7286
commit 8f0c9ab93c

View File

@ -1,19 +1,23 @@
import random import random
import operator
import re
from math import gcd, lcm from math import gcd, lcm
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from collections import Counter from collections import Counter
# Define operators and their corresponding functions
ops = {
'+': operator.add,
'-': operator.sub,
'*': operator.mul,
'/': operator.truediv
}
# Konstanten # Konstanten
# Anzahl der zu generierenden Fragen. # Anzahl der zu generierenden Fragen.
NUM_QUESTIONS = 1 NUM_QUESTIONS = 1
# Algebraische Struktur der Aufgaben # Algebraische Struktur der Aufgaben
STRUCTURE = "-(+)" STRUCTURE = "-(+)-"
# TODO
# Platzhalter hinzufügen
# Anzahl der benötigten Brüche
NUM_FRACTIONS = 1 + STRUCTURE.count("+") + STRUCTURE.count("+") + STRUCTURE.count("*") + STRUCTURE.count("/")
# Größter Zähler # Größter Zähler
MAX_NUMERATOR = 20 MAX_NUMERATOR = 20
# Größter Nenner # Größter Nenner
@ -23,11 +27,99 @@ MAX_COMMON_DENOMINATOR = 100
# Dateiname für Ausgabe # Dateiname für Ausgabe
FILENAME = 'formulas_new_quiz.xml' 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 = [] numerators = []
denominators = [] denominators = []
for i in range(NUM_FRACTIONS): for i in range(NUM_FRACTIONS):
@ -46,8 +138,11 @@ def create_formula_question(n):
print(numerators) print(numerators)
print(denominators) print(denominators)
# [DEBUG] # [DEBUG]
# Hauptnenner bestimmen
*denoms_integers, = denominators *denoms_integers, = denominators
common_denominator = lcm(*denoms_integers) common_denominator = lcm(*denoms_integers)
cd = common_denominator
fractions = [] fractions = []
for i in range(NUM_FRACTIONS): for i in range(NUM_FRACTIONS):
@ -57,7 +152,6 @@ def create_formula_question(n):
print(fractions) print(fractions)
# [DEBUG] # [DEBUG]
cd = common_denominator
expanded_numerators = [frac["n"] * cd // frac["d"] for frac in fractions] expanded_numerators = [frac["n"] * cd // frac["d"] for frac in fractions]
expanded_fractions = [] expanded_fractions = []
@ -68,25 +162,127 @@ def create_formula_question(n):
print(expanded_fractions) print(expanded_fractions)
# [DEBUG] # [DEBUG]
eval_string = f"{expanded_numerators[0]}" result_numerator = calculate(STRUCTURE_PH, expanded_numerators)
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
# [DEBUG] # [DEBUG]
print(eval_string) print(result_numerator)
# [DEBUG] # [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'}) 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"<![CDATA[ <p>{exercise_text} und kürze das Ergebnis so weit wie möglich.</p>" + "<p>{#A} </p> ]]>"
else:
text_elem.text = f"<![CDATA[ <p>{exercise_text}.</p>"+"<p>{#A} </p> ]]>"
# 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 = '''<![CDATA[ <p>Die Antwort ist richtig.</p> ]]> '''
partiallycorrectfeedback_elem = ET.SubElement(question, 'partiallycorrectfeedback', attrib={'format': 'html'})
text_elem = ET.SubElement(partiallycorrectfeedback_elem, 'text')
text_elem.text = '''<![CDATA[ <p>Die Antwort ist teilweise richtig.</p> ]]> '''
incorrectfeedback_elem = ET.SubElement(question, 'incorrectfeedback', attrib={'format': 'html'})
text_elem = ET.SubElement(incorrectfeedback_elem, 'text')
text_elem.text = '''<![CDATA[ <p>Die Antwort ist falsch.</p> ]]>'''
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 return question
# Berechnungsvariablen
vars1_elem = ET.SubElement(answers_elem, 'vars1')
if shortable:
vars1_elem_text = "<![CDATA[ g = gcd(d[0],d[1]);\n cd = abs(d[0] * d[1]) / g;\n e = [cd / d[0], cd / d[1]];\n nums = [n[0]*e[0], n[1]*e[1]];\n thesumnum = nums[0] - nums[1];\n gcdres = gcd(thesumnum,cd);\n snum = thesumnum / gcdres;\n sdenom = cd / gcdres;]]>"
else:
vars1_elem_text = "<![CDATA[ g = gcd(d[0],d[1]);\ncd = abs(d[0] * d[1]) / g;\ne = [cd / d[0], cd / d[1]];\nnums = [n[0]*e[0], n[1]*e[1]];\nthesumnum = nums[0] - nums[1];]]>"
ET.SubElement(vars1_elem, 'text').text = vars1_elem_text
def generate_questions(num_questions): def generate_questions(num_questions):
""" """