CDS401-Mathematics-I/Code/combinations.py
2025-04-17 13:17:21 +02:00

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()