import streamlit as st import math import networkx as nx import matplotlib.pyplot as plt def calculate_formula(with_replacement, ordered): if with_replacement: if ordered: formula = f"n^k" else: formula = f"Binomialkoeffizient (n + k - 1 über k)" else: if ordered: formula = f"P(n, k) = n! / (n - k)!" else: formula = f"C(n, k) = n! / (k! (n - k)!)" return formula def calculate_number_of_possibilities(with_replacement, ordered, n, k): if with_replacement: if ordered: return n ** k else: return math.comb(n + k - 1, k) else: if ordered: return math.perm(n, k) else: return math.comb(n, k) def build_tree(n, k, with_replacement, ordered): G = nx.DiGraph() G.add_node("Start") # Queue enthält Tupel aus (current_node, used_elements, last_selected) queue = [("Start", [], 0)] # (current_node, used_elements, last_selected) for depth in range(1, min(k + 1, 4)): # Limit auf 3 Schichten next_queue = [] for node, used, last_selected in queue: for i in range(1, n + 1): if not with_replacement: if i in used: # Ohne Zurücklegen und Element bereits verwendet: Eliminiert child_label = f"{node}->{i} (elim)" G.add_node(child_label) G.add_edge(node, child_label) continue if not ordered: if depth == 1: # Im ersten Schritt gibt es keine Einschränkungen pass else: # Bei Kombinationen: nur Elemente >= dem letzten ausgewählten Element if i < last_selected: # Diese Auswahl würde eine Permutation darstellen, die wir bei Kombinationen vermeiden child_label = f"{node}->{i} (elim)" G.add_node(child_label) G.add_edge(node, child_label) continue # Gültige Auswahl if with_replacement: new_used = used.copy() else: new_used = used.copy() new_used.append(i) child_label = f"{node}->{i}" G.add_node(child_label) G.add_edge(node, child_label) next_queue.append((child_label, new_used, i)) queue = next_queue return G def hierarchy_pos(G, root=None, width=1.0, vert_gap=0.2, vert_loc=0, xcenter=0.5, pos=None, parent=None, parsed=None): """ Hierarchical layout für einen Baum, mit der Wurzel oben. Optimiert die horizontale Verteilung basierend auf der Anzahl der Unterknoten. """ if pos is None: pos = {} if parsed is None: parsed = set() if root is None: root = "Start" children = list(G.successors(root)) if not children: pos[root] = (xcenter, vert_loc) else: # Anzahl der Kinder num_children = len(children) # Gesamte Breite für die Kinder dx = width / num_children next_x = xcenter - width / 2 for child in children: next_x += dx pos = hierarchy_pos(G, child, width=dx, vert_gap=vert_gap, vert_loc=vert_loc - vert_gap, xcenter=next_x, pos=pos, parent=root, parsed=parsed) pos[root] = (xcenter, vert_loc) return pos def plot_tree(G, show_eliminated, with_replacement, ordered, n, k): pos = hierarchy_pos(G) plt.figure(figsize=(12, 8)) ax = plt.gca() ax.set_axis_off() # Bestimme welche Knoten gezeichnet werden sollen if show_eliminated: nodes_to_draw = list(G.nodes()) else: nodes_to_draw = [node for node in G.nodes() if "(elim)" not in node] # Bestimme welche Kanten gezeichnet werden sollen edges_to_draw = [] for edge in G.edges(): source, target = edge if show_eliminated: edges_to_draw.append(edge) else: if "(elim)" not in target: edges_to_draw.append(edge) # Bestimme Farben node_colors = [] for node in nodes_to_draw: if node == "Start": color = 'black' elif '->' in node: if "(elim)" in node: color = 'red' else: color = 'blue' else: color = 'black' node_colors.append(color) # Zeichne Knoten nx.draw_networkx_nodes(G, pos, nodelist=nodes_to_draw, node_color=node_colors, node_size=500, alpha=0.9) # Zeichne Kanten nx.draw_networkx_edges(G, pos, edgelist=edges_to_draw, arrows=True, arrowstyle='->', arrowsize=20) # Zeichne Labels labels = {} for node in nodes_to_draw: if node == "Start": labels[node] = "Start" else: # Entferne "(elim)" für die Anzeige, wenn eliminiert ist label = node.replace("Start->", "").replace("->", "\n→ ") if "(elim)" in label: label = label.replace(" (elim)", "") labels[node] = label nx.draw_networkx_labels(G, pos, labels=labels, font_size=10, font_weight='bold') st.pyplot(plt) plt.close() def main(): st.title("Interaktives Lerntool für Kombinatorische Formeln") st.sidebar.header("Einstellungen") with_replacement = st.sidebar.checkbox("Mit Zurücklegen", value=False) ordered = st.sidebar.checkbox("Berücksichtigung der Reihenfolge", value=False) st.sidebar.header("Parameter") n = st.sidebar.number_input("Anzahl der Elemente (n)", min_value=1, value=5, step=1) k = st.sidebar.number_input("Anzahl der Auswahlen (k)", min_value=1, value=3, step=1) formula = calculate_formula(with_replacement, ordered) st.subheader("Entsprechende Formel") st.write(formula) num_possibilities = calculate_number_of_possibilities(with_replacement, ordered, n, k) st.subheader("Anzahl der Möglichkeiten") st.write(f"Anzahl der Möglichkeiten: {num_possibilities}") st.subheader("Baumdarstellung (erste 3 Schichten)") G = build_tree(n, k, with_replacement, ordered) show_eliminated = st.checkbox("Eliminierte Möglichkeiten anzeigen", value=True) plot_tree(G, show_eliminated, with_replacement, ordered, n, k) st.markdown(""" --- **Hinweise:** - *Mit Zurücklegen*: Elemente können mehrfach ausgewählt werden. - *Berücksichtigung der Reihenfolge*: Reihenfolge der Auswahl ist wichtig. - *Baumdarstellung*: Zeigt die ersten 3 Schichten der Auswahlmöglichkeiten. - *Eliminierte Möglichkeiten*: Elemente, die aufgrund der Einstellungen nicht zulässig sind, werden rot markiert. """) if __name__ == "__main__": main()