mdlAufgabengeneratoren/aufgabengenerator_formulas_fraction_new.py

561 lines
19 KiB
Python

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 = 5
# Algebraische Struktur der Aufgaben
STRUCTURE = "(-)-(+(-))"
# Gleiche Nenner erlauben
# TODO
# Größter Zähler
MAX_NUMERATOR = 10
# Größter Nenner
MAX_DENOMINATOR = 8
# Größter erlaubter gemeinsamer Nenner
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 + 1
# 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 not (output[0] == f'n{0}' or output[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()
print(final_expression)
return final_expression
# 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 gen_calculate_string(expression :str, liststring :str):
# Replace placeholders with actual number values
for i in range(NUM_FRACTIONS):
placeholder = f'n{i}'
expression = re.sub(rf'\{placeholder}\s*([+-/*])', rf'{liststring}[{i}] \1', expression)
expression = re.sub(rf'({placeholder})\s*', rf'{liststring}[{i}]', expression)
print(expression)
# Return the changed expression
return expression
def gen_latex_calculate_string(expression :str, liststring: str):
# Replace placeholders with actual number values
for i in range(NUM_FRACTIONS):
placeholder = f'n{i}'
replacement = liststring[i]
print(replacement)
expression = re.sub(rf'\{placeholder}\s*([+-/*])', replacement, expression)
expression = re.sub(rf'({placeholder})\s*', replacement, expression)
# Klammern anpassen
expression = re.sub(rf'\(', u'\\\\left(', expression)
expression = re.sub(rf'\)', u'\\\\right)', expression)
# print(expression)
# Return the changed expression
return expression
def create_html_table_for_fracture_input(rows: list):
table_rows = []
# Tabellenzeilen festlegen
for i, row in enumerate(rows):
if (i % 2) == 0:
table_row = "<tr style='border-bottom: 1px solid black;'>"
else:
table_row = "<tr>"
for cell in row:
table_row += f"<td style='padding: 5px;'>{cell}</td>"
table_row += "</tr>"
table_rows.append(table_row)
# Tabelle zusammenbauen
table_html = f"<table style='border-collapse: collapse; display: inline-table;'>\n" + "\n".join(table_rows) + "</table>"
return table_html
def create_formula_question(ex_number: int):
numerators = []
denominators = []
for i in range(NUM_FRACTIONS):
n = random.randint(1, MAX_NUMERATOR)
d = random.randint(2, MAX_DENOMINATOR)
numerators.append(n)
denominators.append(d)
# Sicherstellen, dass die Nenner unterschiedlich sind
same_denominators = [k for k,v in Counter(denominators).items() if v>1]
while len(same_denominators) > 1:
new_denom = random.randint(2, MAX_DENOMINATOR)
try:
index = denominators.index(same_denominators[0])
denominators[index] = new_denom
except ValueError:
pass
# denominators.replace(same_denominators[0], new_denom)
same_denominators = [k for k,v in Counter(denominators).items() if v>1]
# [DEBUG]
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):
fraction = {"n": numerators[i], "d": denominators[i]}
fractions.append(fraction)
# [DEBUG]
print(fractions)
# [DEBUG]
expanded_numerators = [frac["n"] * cd // frac["d"] for frac in fractions]
expanded_fractions = []
for i in range(NUM_FRACTIONS):
fraction = {"n": expanded_numerators[i], "d": cd}
expanded_fractions.append(fraction)
# [DEBUG]
print(expanded_fractions)
# [DEBUG]
result_numerator = calculate(STRUCTURE_PH, expanded_numerators)
# [DEBUG]
print(result_numerator)
print(cd)
# [DEBUG]
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" + f" ({STRUCTURE})"
exercise_text = "Berechne den Term"
elif has_plus:
expression_type = "Addition" + f" ({STRUCTURE})"
exercise_text = "Addiere die Brüche"
elif has_minus:
expression_type = "Subtraktion" + f" ({STRUCTURE})"
exercise_text = "Subtrahiere die Brüche"
else:
expression_type = "Rechnung" + f" ({STRUCTURE})"
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'
# Berechnungsvariablen
# common divisor
cdtext = ""
for i in range(NUM_FRACTIONS):
if i == 0:
pass
elif i == 1:
cdtext += f"cd{i} = lcm(d[{i}],d[{i-1}]);"
else:
cdtext += f"cd{i} = lcm(cd{i-1}, d[{i}]);"
cdtext += f"cd = cd{NUM_FRACTIONS-1};"
# expansions
extext = "e = ["
for i in range(NUM_FRACTIONS):
extext += f"cd / d[{i}],"
extext = extext[:-1]
extext += "];"
# expanded_numerators
entext = "ens = ["
for i in range(NUM_FRACTIONS):
entext += f"n[{i}]*e[{i}],"
entext = entext[:-1]
entext += "];"
# result numerator
resnum = "resnum = " + gen_calculate_string(STRUCTURE_PH, "ens") + ";"
# gcd of result numerator and result (common) denominator
resgcd = "resgcd = gcd(resnum, cd);"
# shortened result numerator
sresnum = "sresnum = resnum / resgcd;"
# shortened result denominator
sresden = "sresden = cd / resgcd;"
vars1_elem = ET.SubElement(answers_elem, 'vars1')
if shortable:
vars1_elem_text = f"<![CDATA[{cdtext}\n {extext}\n {entext}\n {resnum}\n {resgcd}\n {sresnum}\n {sresden}]]>"
else:
vars1_elem_text = f"<![CDATA[{cdtext}\n {extext}\n {entext}\n {resnum}]]>"
ET.SubElement(vars1_elem, 'text').text = vars1_elem_text
# Antwort
answer_elem = ET.SubElement(answers_elem, 'answer')
if shortable:
answer_text = '[resnum, cd, sresnum, sresden]'
else:
answer_text = '[resnum, cd]'
ET.SubElement(answer_elem, 'text').text = answer_text
answernotunique_elem = ET.SubElement(answers_elem, 'answernotunique')
ET.SubElement(answernotunique_elem, 'text').text = '0'
vars2_elem = ET.SubElement(answers_elem, 'vars2')
if shortable:
vars2_elem_text = "<![CDATA[ ga = _0 == resnum && _1 == cd;\n gb = (_0 / _1) == (resnum / cd);\n gsa = _2 == sresnum && _3 == sresden;\n gsb = (_2 / _3) == (sresnum / sresden); ]]>"
else:
vars2_elem_text = "<![CDATA[ ga = _0 == resnum && _1 == cd; gb = (_0 / _1) == (resnum / cd); ]]>"
ET.SubElement(vars2_elem, 'text').text = vars2_elem_text
correctness_elem = ET.SubElement(answers_elem, 'correctness')
if shortable:
ET.SubElement(correctness_elem, 'text').text = '0.25*ga + 0.25*gb + 0.25*gsa + 0.25*gsb'
else:
ET.SubElement(correctness_elem, 'text').text = '0.5*ga + 0.5*gb'
unitpenalty_elem = ET.SubElement(answers_elem, 'unitpenalty')
ET.SubElement(unitpenalty_elem, 'text').text = '1'
postunit_elem = ET.SubElement(answers_elem, 'postunit')
ET.SubElement(postunit_elem, 'text').text = ' '
ruleid_elem = ET.SubElement(answers_elem, 'ruleid')
ET.SubElement(ruleid_elem, 'text').text = '1'
otherrule_elem = ET.SubElement(answers_elem, 'otherrule')
ET.SubElement(otherrule_elem, 'text').text = ' '
# Unterfragen-Text
subqtext_elem = ET.SubElement(answers_elem, 'subqtext', attrib={'format': 'html'})
fractions_latex_strings = []
for i in range(NUM_FRACTIONS):
l_str = "\\\\frac{{"+ f"n[{i}]" +"}}{{"+ f"d[{i}]" +"}}"
fractions_latex_strings.append(l_str)
latex_calculation = gen_latex_calculate_string(STRUCTURE_PH, fractions_latex_strings)
sub_q_t_begin = "<![CDATA[ <p>Berechne:</p>"
sub_q_t_math = "<p>\\(\\Large \\displaystyle " + f"{latex_calculation}" + "=\\)</p>"
rows_list = [
["Zähler:", "{_0}"],
["Nenner:", "{_1}"]
]
sub_q_t_input = '<div style="display: inline-table"> ='
if shortable:
sub_q_t_input += create_html_table_for_fracture_input(rows_list)
rows_list_s = [["Gekürzter Zähler:", "{_2}"],
["Gekürzter Nenner:", "{_3}"]]
sub_q_t_input += " = "
sub_q_t_input += create_html_table_for_fracture_input(rows_list_s)
else:
sub_q_t_input += create_html_table_for_fracture_input(rows_list)
sub_q_t_input += '</div>'
sub_q_t_end = "]]>"
sub_question_text = sub_q_t_begin + sub_q_t_math + sub_q_t_input + sub_q_t_end
ET.SubElement(subqtext_elem, 'text').text = sub_question_text
expanded_fractions_latex_strings = []
for i in range(NUM_FRACTIONS):
l_str = "\\\\frac{{"+ f"ens[{i}]" +"}}{{"+ f"cd" +"}}"
expanded_fractions_latex_strings.append(l_str)
expanded_latex_calculation = gen_latex_calculate_string(STRUCTURE_PH, expanded_fractions_latex_strings)
# Lösungshinweis
feedback_elem = ET.SubElement(answers_elem, 'feedback', attrib={'format': 'html'})
if shortable:
feedback_text = '''<![CDATA[ <h4>Lösungshinweis</h4>
<p>Die korrekte Lösung ist:</p>
<p>\\(\\displaystyle''' + f"{latex_calculation}" +" = " + f"{expanded_latex_calculation}" + ''' = \\frac{{resnum}}{{cd}} = \\frac{{sresnum}}{{sresden}} \\) </p> ]]>'''
else:
feedback_text = '''<![CDATA[ <h4>Lösungshinweis</h4>
<p>Die korrekte Lösung ist:</p>
<p>\\( \\displaystyle ''' + f"{latex_calculation}" +" = " + f"{expanded_latex_calculation}" + ''' = \\frac{{resnum}}{{cd}} \\) </p> ]]>'''
ET.SubElement(feedback_elem, 'text').text = feedback_text
# correctfeedback
correctfeedback_elem = ET.SubElement(answers_elem, 'correctfeedback', attrib={'format': 'html'})
correctfeedback_text = '''<![CDATA[ <p>Richtig!</p>]]>'''
ET.SubElement(correctfeedback_elem, 'text').text = correctfeedback_text
# partially correct feedback
pcorrectfeedback_elem = ET.SubElement(answers_elem, 'partiallycorrectfeedback', attrib={'format': 'html'})
pcorrectfeedback_text = '''<![CDATA[ <p>Teilweise richtig.</p>]]>'''
ET.SubElement(pcorrectfeedback_elem, 'text').text = pcorrectfeedback_text
# incorrect feedback
incorrectfeedback_elem = ET.SubElement(answers_elem, 'incorrectfeedback', attrib={'format': 'html'})
incorrectfeedback_text = '''<![CDATA[ <p>Leider nicht richtig.</p>]]>'''
ET.SubElement(incorrectfeedback_elem, 'text').text = incorrectfeedback_text
return question
def generate_questions(num_questions):
"""
Generiert eine Liste von Aufgaben.
Args:
num_questions (int): Die Anzahl der zu generierenden Fragen.
Returns:
list: Eine Liste von Frage-Elementen.
"""
questions = []
for i in range(num_questions):
# Hier können Sie die Funktionsdefinition für die Erzeugung einer Aufgabe hinzufügen
questions.append(create_formula_question(i))
return questions
def create_quiz(questions):
"""
Erstellt das gesamte Quiz-Element.
Args:
questions (list): Eine Liste von Frage-Elementen.
Returns:
Element: Das gesamte Quiz -Element.
"""
quiz = ET.Element('quiz')
for q in questions:
quiz.append(q)
return quiz
def save_to_file(xml_element, filename):
"""
Speichert das XML-Dokument in eine Datei.
Args:
xml_element (Element): Das XML-Element, zu speichern.
filename (str): Der Name der Zieldatei.
Returns:
None
"""
tree = ET.ElementTree(xml_element)
ET.indent(tree, space="\t", level=0)
with open(filename, 'wb') as f:
tree.write(f, encoding='utf_8', xml_declaration=True)
return
def replace_vars2(xml_file):
# XML-Datei lesen
with open(xml_file, 'r') as f:
text = f.read()
text = text.replace("&amp;","&")
text = text.replace("&lt;","<")
text = text.replace("&gt;",">")
with open(xml_file, 'w') as f:
f.write(text)
# Hauptprogramm
if __name__ == "__main__":
questions = generate_questions(NUM_QUESTIONS)
quiz = create_quiz(questions)
save_to_file(quiz, FILENAME)
replace_vars2(FILENAME)
print("Moodle-XMLDatei erfolgreich erstellt.")