Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen angezeigt.

Link zu dieser Vergleichsansicht

modul:m323:learningunits:lu01:loesungen:paradigmenvergleich [2026/08/18 10:12] – Neue Loesung LU01.L13 - Dasselbe Problem in drei Paradigmen adminmodul:m323:learningunits:lu01:loesungen:paradigmenvergleich [2026/08/18 11:36] (aktuell) – Funktionale Fassung auf map/filter/reduce umgestellt (Bearbeitung nach LU04) admin
Zeile 1: Zeile 1:
 ====== LU01.L13 - Dasselbe Problem in drei Paradigmen ====== ====== LU01.L13 - Dasselbe Problem in drei Paradigmen ======
 +
 +<WRAP center round info 80%>
 +Diese Aufgabe gehört inhaltlich zu LU01, wird aber erst **nach LU04** bearbeitet – die funktionale Fassung setzt ''map'', ''filter'' und ''reduce'' voraus.
 +</WRAP>
  
 ===== 1. prozedural.py ===== ===== 1. prozedural.py =====
  
 <code python> <code python>
-noten = [("Alina", 5.5), ("Ben", 3.5), ("Chiara", 4.0), ("Dario", 5.0), ("Elif", 3.9)]+noten = [('Alina', 5.5), ('Ben', 3.5), ('Chiara', 4.0), ('Dario', 5.0), ('Elif', 3.9)]
  
 if __name__ == '__main__': if __name__ == '__main__':
Zeile 20: Zeile 24:
     durchschnitt = summe / len(noten)     durchschnitt = summe / len(noten)
  
-    print(f"Durchschnitt: {durchschnitt:.2f}"+    print(f'Durchschnitt: {durchschnitt:.2f}'
-    print(f"Ungenuegend: {ungenuegend}"+    print(f'Ungenuegend: {ungenuegend}'
-    print(f"Beste Note: {beste}")+    print(f'Beste Note: {beste}')
 </code> </code>
  
Zeile 48: Zeile 52:
  
 if __name__ == '__main__': if __name__ == '__main__':
-    klasse = Klasse([("Alina", 5.5), ("Ben", 3.5), ("Chiara", 4.0), +    klasse = Klasse([('Alina', 5.5), ('Ben', 3.5), ('Chiara', 4.0), 
-                     ("Dario", 5.0), ("Elif", 3.9)])+                     ('Dario', 5.0), ('Elif', 3.9)])
  
-    print(f"Durchschnitt: {klasse.durchschnitt():.2f}"+    print(f'Durchschnitt: {klasse.durchschnitt():.2f}'
-    print(f"Ungenuegend: {klasse.ungenuegende()}"+    print(f'Ungenuegend: {klasse.ungenuegende()}'
-    print(f"Beste Note: {klasse.beste_note()}")+    print(f'Beste Note: {klasse.beste_note()}')
 </code> </code>
  
Zeile 59: Zeile 63:
  
 <code python> <code python>
 +from functools import reduce
 +
 +
 +def noten_werte(noten):
 +    """Loest die Noten aus den Paaren heraus."""
 +    return map(lambda paar: paar[1], noten)
 +
 +
 def durchschnitt(noten): def durchschnitt(noten):
-    return sum(note for _note in noten) / len(noten)+    return reduce(lambda ab: a + b, noten_werte(noten)) / len(noten)
  
  
 def ungenuegende(noten): def ungenuegende(noten):
-    return [name for namenote in noten if note < 4.0]+    return list(map(lambda paar: paar[0], 
 +                    filter(lambda paar: paar[1] < 4.0, noten)))
  
  
 def beste_note(noten): def beste_note(noten):
-    return max(note for _note in noten)+    return reduce(lambda ab: a if a > b else b, noten_werte(noten))
  
  
Zeile 76: Zeile 89:
  
 if __name__ == '__main__': if __name__ == '__main__':
-    noten = [("Alina", 5.5), ("Ben", 3.5), ("Chiara", 4.0), +    noten = [('Alina', 5.5), ('Ben', 3.5), ('Chiara', 4.0), 
-             ("Dario", 5.0), ("Elif", 3.9)]+             ('Dario', 5.0), ('Elif', 3.9)]
  
-    print(f"Durchschnitt: {durchschnitt(noten):.2f}"+    print(f'Durchschnitt: {durchschnitt(noten):.2f}'
-    print(f"Ungenuegend: {ungenuegende(noten)}"+    print(f'Ungenuegend: {ungenuegende(noten)}'
-    print(f"Beste Note: {beste_note(noten)}")+    print(f'Beste Note: {beste_note(noten)}')
 </code> </code>
 +
 +<WRAP center round tip 80%>
 +**Kürzer geht es mit den eingebauten Funktionen**
 +
 +''sum'' und ''max'' sind selbst schon Reduktionen – ''reduce'' von Hand zu schreiben, macht hier nur sichtbar, was in ihnen steckt:
 +
 +<code python>
 +def durchschnitt(noten):
 +    return sum(note for _, note in noten) / len(noten)
 +
 +def beste_note(noten):
 +    return max(note for _, note in noten)
 +</code>
 +
 +Beide Fassungen sind funktional. Verlangt ist in der Aufgabe die explizite mit ''reduce'', weil man daran sieht, dass //jede// Aggregation dasselbe Muster hat: Startwert, Verknüpfung, Ergebnis.
 +</WRAP>
  
 ===== Vergleichstabelle ===== ===== Vergleichstabelle =====
Zeile 92: Zeile 121:
 | Was muss man wissen, um eine Zeile zu verstehen? | Den bisherigen Verlauf der Schleife: Welche Werte haben ''summe'', ''beste'' gerade? | Den aktuellen Zustand des Objekts – der von aussen nicht sichtbar ist. | Nur die Signatur der Funktion und ihre Argumente. | | Was muss man wissen, um eine Zeile zu verstehen? | Den bisherigen Verlauf der Schleife: Welche Werte haben ''summe'', ''beste'' gerade? | Den aktuellen Zustand des Objekts – der von aussen nicht sichtbar ist. | Nur die Signatur der Funktion und ihre Argumente. |
 | Gleichzeitiger Zugriff? | Problematisch: geteilte Variablen können sich gegenseitig überschreiben. | Problematisch: zwei Programmteile mit derselben Objektreferenz sehen Änderungen des jeweils anderen. | Unkritisch: Es gibt keinen geteilten veränderlichen Zustand. | | Gleichzeitiger Zugriff? | Problematisch: geteilte Variablen können sich gegenseitig überschreiben. | Problematisch: zwei Programmteile mit derselben Objektreferenz sehen Änderungen des jeweils anderen. | Unkritisch: Es gibt keinen geteilten veränderlichen Zustand. |
-| Anzahl Codezeilen | am wenigsten (~15) | am meisten (~20, plus Klassengerüst) | dazwischen (~18), dafür in unabhängigen Einheiten |+| Anzahl Codezeilen | mittel (~18) | am meisten (~20, plus Klassengerüst) | am wenigsten im Berechnungsteil, dafür in unabhängigen Einheiten |
  
 <WRAP center round info 80%> <WRAP center round info 80%>
 **Zur Zeilenzahl** **Zur Zeilenzahl**
  
-Die Zeilenzahl ist das schwächste der sechs Kriterien. Die prozedurale Fassung ist am kürzesten und trotzdem am schlechtesten wartbar – Kürze und Qualität sind nicht dasselbe. Bewerten Sie in Ihrer Abgabe vor allem die ersten fünf Zeilen der Tabelle.+Die Zeilenzahl ist das schwächste der sechs Kriterien. Bewerten Sie in Ihrer Abgabe vor allem die ersten fünf Zeilen der Tabelle – Kürze und Qualität sind nicht dasselbe.
 </WRAP> </WRAP>
  
Zeile 115: Zeile 144:
 klasse = Klasse(noten) klasse = Klasse(noten)
 print(round(klasse.durchschnitt(), 3))   # 4.38 print(round(klasse.durchschnitt(), 3))   # 4.38
-klasse.hinzufuegen("Fabio", 2.0)+klasse.hinzufuegen('Fabio', 2.0)
 print(round(klasse.durchschnitt(), 3))   # 3.983 print(round(klasse.durchschnitt(), 3))   # 3.983
 </code> </code>
Zeile 125: Zeile 154:
 <code python> <code python>
 print(durchschnitt(noten))                          # 4.38 print(durchschnitt(noten))                          # 4.38
-erweitert = note_hinzufuegen(noten, "Fabio", 2.0)+erweitert = note_hinzufuegen(noten, 'Fabio', 2.0)
 print(durchschnitt(noten))                          # 4.38 - unveraendert print(durchschnitt(noten))                          # 4.38 - unveraendert
 print(durchschnitt(erweitert))                      # 3.983 - andere Daten, anderer Name print(durchschnitt(erweitert))                      # 3.983 - andere Daten, anderer Name
Zeile 142: Zeile 171:
 Objektorientierung ist also **kein Gegensatz** zur imperativen Programmierung, sondern eine Art, imperativen Code zu organisieren: Sie bündelt Zustand und die darauf arbeitenden Prozeduren zu Einheiten. Der Gegensatz verläuft zwischen //Zustand verändern// und //Werte erzeugen//, nicht zwischen //OO// und //funktional//. Objektorientierung ist also **kein Gegensatz** zur imperativen Programmierung, sondern eine Art, imperativen Code zu organisieren: Sie bündelt Zustand und die darauf arbeitenden Prozeduren zu Einheiten. Der Gegensatz verläuft zwischen //Zustand verändern// und //Werte erzeugen//, nicht zwischen //OO// und //funktional//.
  
-==== 4. Wann welches Paradigma? ====+==== 4. Leere Notenliste ==== 
 + 
 +<code> 
 +TypeError: reduce() of empty iterable with no initial value 
 +</code> 
 + 
 +''reduce'' ohne Startwert nimmt das erste Element als Anfang – bei einer leeren Folge gibt es keines. Zwei saubere Behandlungen: 
 + 
 +<code python> 
 +# Variante A: Startwert angeben 
 +def summe(noten): 
 +    return reduce(lambda a, b: a + b, noten_werte(noten), 0) 
 + 
 +# Variante B: den Randfall zur Bedingung der Funktion machen 
 +def durchschnitt(noten): 
 +    if not noten: 
 +        raise ValueError('Durchschnitt einer leeren Notenliste ist undefiniert'
 +    return reduce(lambda a, b: a + b, noten_werte(noten)) / len(noten) 
 +</code> 
 + 
 +Bei der Summe ist ''0'' ein sinnvoller Startwert. Beim Durchschnitt nicht: Er wäre eine Division durch null. Und bei ''beste_note'' gibt es überhaupt keinen neutralen Startwert – hier ist Variante B die einzig ehrliche Antwort. Das ist ein guter Moment, um zu sehen, dass nicht jede Aggregation einen sinnvollen Anfangswert hat. 
 + 
 +==== 5. Wann welches Paradigma? ====
  
 Mögliche Antworten – die Beispiele der Lernenden dürfen abweichen, müssen aber begründet sein: Mögliche Antworten – die Beispiele der Lernenden dürfen abweichen, müssen aber begründet sein:
  • modul/m323/learningunits/lu01/loesungen/paradigmenvergleich.txt
  • Zuletzt geändert: 2026/08/18 11:36
  • von admin