198 lines
7.0 KiB
Python
198 lines
7.0 KiB
Python
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()
|