Neue Generierung von moodle-formulas Fragen schon weit entwickelt. Dynamischer Aufgabenaufbau.
This commit is contained in:
		@@ -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"<![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
 | 
			
		||||
 | 
			
		||||
    # 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):
 | 
			
		||||
    """
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user