<!DOCTYPE article PUBLIC "-//NLM//DTD JATS (Z39.96) Journal Archiving and Interchange DTD v1.0 20120330//EN" "JATS-archivearticle1.dtd">
<article xmlns:xlink="http://www.w3.org/1999/xlink">
  <front>
    <journal-meta>
      <journal-title-group>
        <journal-title>Potsdam</journal-title>
      </journal-title-group>
    </journal-meta>
    <article-meta>
      <title-group>
        <article-title>Automatische Erzeugung und Bewertung von Aufgaben zu Algorithmen und Datenstrukturen</article-title>
      </title-group>
      <contrib-group>
        <contrib contrib-type="author">
          <string-name>Johannes Waldmann</string-name>
        </contrib>
      </contrib-group>
      <pub-date>
        <year>2017</year>
      </pub-date>
      <volume>1</volume>
      <abstract>
        <p>Ich zeige, wie das Verständnis von Algorithmen gefördert und überprüft werden kann, ohne dass Studenten durch Eigenheiten konkreter Programmiersprachen abgelenkt werden. Ich verwende stattdessen stark eingeschränkte Modellierungssprachen mit unformer und trivialer Syntax sowie aufgabenspezifischer Semantik. Aufgaben-Erzeugung sowie -Bewertung werden im E-Learning/EAssessment-System autotool technisch realisiert, das seit ca. 2000 an der Universität Leipzig, der HTWK Leipzig, sowie gelegentlich an weiteren Hochschulen eingesetzt wird. Ein klassischer Bestandteil der Lehre von Algorithmen und Datenstrukturen [CLR09] ist das Thema „balancierte Suchbäume“, mit dem Beispiel AVL-Bäume. Der Inhalt sind Sätze über Algorithmen: Balance erzwingt logarithmische Höhe, Rotation erhält die SuchbaumEigenschaft und repariert die Balance lokal; sowie die zugrundeliegenden Beweismethoden (Induktion über die Höhe) sowie Programmierkonzepte (Rekursion, Iteration). Das Verständnis dieser Ideen kann dadurch unterstützt werden, dass Studenten die Algorithmen ausführen (auf dem Papier, an der Tafel) und implementieren (als Programmtext). Ich beschreibe hier eine weitere Möglichkeit von unterstützenden Aufgaben sowie deren Automatisierung innerhalb des Systems autotool[RW02, Wa17]. Studenten sollen eine Folge s von Einfüge- und Lösch-Operationen finden, die aus einem vorgegebenen AVLBaum t1 einen vorgegebenen Baum t2 erzeugt. Für die Folge s sind die Länge sowie einige Elemente vorgegeben und die restlichen zu finden. Die Vorgabe ist ein Folge mit Lücken s?. Die Aufgabenstellung besteht dann aus ¹t1; s?; t2º. Studenten tragen einen Lösungsversuch (eine Folge s) in einem Web-Formular ein. Nach Absenden führt das autotool-System die Operationsfolge s aus und gibt die Folge der dabei erzeugten Bäume aus. Schließlich wird das Endresultat mit der Vorgabe verglichen. Eine Lösung ist jede Folge s, die zu s? passt und t1 in t2 überführt. Korrektheit ist also 1 HTWK Leipzig, Fakultät IMN, Postfach 301166, 04251 Leipzig, https://www.imn.htwk-leipzig.de/~waldmann/</p>
      </abstract>
    </article-meta>
  </front>
  <body>
    <sec id="sec-1">
      <title>Motivation</title>
      <p>2</p>
    </sec>
    <sec id="sec-2">
      <title>Aufgaben zu Suchbäumen</title>
      <p>Suchbäume S¹Kº mit Schlüsseln einer Menge K realisieren den abstrakten Datentyp Menge
mit folgender Schnittstelle. Die Semantik JtK eines Baumes ist die Menge seiner Schlüssel.
empty : S¹Kº mit JemptyK = ;,
null : S¹Kº ! B mit Jnull¹tºK = ¹; = JtKº
contains : K S¹Kº ! B mit Jcontains¹x; tºK = ¹x 2 JtKº
insert : K S¹Kº ! S¹Kº mit Jinsert¹x; tºK = fxg [ JtK
delete : K S¹Kº ! S¹Kº mit Jdelete¹x; tºK = JtK n fxg</p>
      <sec id="sec-2-1">
        <title>Für die Aufgaben verwenden wir Zahlen als Schlüssel. Jeder implementierende Typ S¹Kº</title>
        <p>besitzt eine Funktion zur grafischen Darstellung. Wir verwenden dot [Ga17].</p>
        <p>Automatische Erzeugung und Bewertung von Aufgaben zu Algorithmen und Datenstrukturen 3
Derzeit existieren diese Implementierungen: nicht balancierte binäre Bäume, AVL-Bäume,
Rot-Schwarz-Bäume, B-Bäume.</p>
        <p>Ein Beispiel für eine Aufgabenstellung für AVL-Bäume ist:</p>
        <sec id="sec-2-1-1">
          <title>Auf den Baum</title>
          <p>sollen diese Operationen angewendet werden
(wobei Sie Any geeignet ersetzen sollen):
[ Any , Any , Any , Delete 86 , Any, Any , Insert 13 , Insert 21 ]
so dass dieser Baum entsteht:
Eine korrekte Lösung ist
[ Insert 53, Insert 80, Delete 97, Delete 86
, Delete 96, Insert 0, Insert 13, Insert 21 ]
Für die Erzeugung zufälliger Aufgabeninstanzen werden, wie in der Einleitung beschrieben,
t1 und s0 ausgewürfelt und daraus t2 bestimmt. Damit interessante, d.h., nicht trivial lösbare,
Instanzen entstehen, werden jeweils mehrere Kandidaten-Instanzen bestimmt und dann
diejenige verwendet, die den Hamming- oder Editier-Abstand von t1 zu t2 maximiert. Die
Wurzeln von t1 und t2 sollen sich unterscheiden und die Wurzeln der nächsten Teilbäume
möglichst auch, usw., damit die Aufgabe nicht durch Betrachtung unabhängiger Teilbäume
einfach lösbar ist. Unterschiedliche Wurzeln erreicht man bei nicht balancierten Bäumen
durch delete-Operationen, bei anderen auch als Folge von Balance-Reparaturen.
3</p>
        </sec>
      </sec>
    </sec>
    <sec id="sec-3">
      <title>Aufgaben zu heap-geordneten Bäumen</title>
      <sec id="sec-3-1">
        <title>Heaps H¹Kº mit Schlüsseln (Prioritäten) einer total geordneten Menge K realisieren den</title>
        <p>abstrakten Datentyp Prioritätswarteschlange unter anderem den folgenden Operationen. Die
Semantik JhK eines Heaps h ist die (Multi)menge der Schlüssel. Die insert-Operation liefert
einen Verweis p 2 Ref¹Kº, der bei decrease verwendet wird. In der grafischen Darstellung
werden für jeden Knoten der Schlüssel sowie der Name des Verweises auf diesen Knoten
gezeichnet.</p>
        <p>Derzeit wird die Schnittstelle implementiert durch fast vollständig balancierte binäre Heaps.
Dabei sind alle Blätter in Tiefe h oder, rechts davon, in Tiefe h 1. Solche Heaps können
implizit durch Arrays repräsentiert werden, was in dieser Aufgabe jedoch keine Rolle spielt.
Grundsätzlich trefen Bemerkungen zu Suchbäumen aus Abschnitt 2 zu. Es zeigte sich,
dass Aufgaben zu Heaps schwieriger waren, erkennbar an einer höheren Anzahl von
Fehlversuchen. Das kann daran liegen, dass die decrease-Operation eine direkte Zuordnung
zwischen den Schlüsseln in der Eingabe t1 und denen in der Ausgabe t2 erschwert.
4</p>
      </sec>
    </sec>
    <sec id="sec-4">
      <title>Aufgaben zu Hashtabellen</title>
      <sec id="sec-4-1">
        <title>Eine Hashtabelle T ¹Kº repräsentiert den abstrakten Datentyp Menge, die Implementierung</title>
        <p>verwendet direkten Zugrif mittels Hashfunktionen. Gegenüber den vorher beschriebenen
Aufgaben zu Bäumen enthalten Aufgaben zu Hashing nicht nur eine Beschreibung der
vorgegebenen Start- und Endzustände, hier: Belegung der Hashtabelle, sondern zusätzlich
eine Spezifikation des verwendeten Hash-Verfahrens sowie seiner Parameter, insbesondere
seiner Hashfunktion(en). Form und Koefizienten von Hashfunktionen können teilweise
versteckt werden. Damit wird die Angabe der Hashfunktion zu einem Teil der Aufgabe.
Derzeit gibt es Aufgaben zu diesen Hashverfahren:</p>
        <p>Hashing mit Verkettung: jede Zelle der Hashtabelle enthält eine Liste von Elementen.
ofenes Hashing mit linearem Sondieren: jede Zelle enthält kein oder ein Element,
die Sondierungsreihenfolge für x mit hash¹xº = p ist p; p + 1; p + 2; : : : (modulo
Tabellengröße).</p>
        <p>Automatische Erzeugung und Bewertung von Aufgaben zu Algorithmen und Datenstrukturen 5
doppeltes Hashing: jede Zelle enthält kein oder ein Element, die
Sondierungsreihenfolge für x mit hash1¹xº = p und hash2¹xº = q ist p; p + q; p + 2q; : : : .</p>
        <p>Die Hashfunktion kann vorgegeben werden als lineare Funktion mit unbekannten
Koefifzienten h¹xº = ? + ? x. Bei Hashing mit Verkettung sind die Koefizienten leicht zu
rekonstruieren, da man Paare ¹x; h¹xºº direkt aus der Tabelle ablesen kann. Bei ofenem
Hashing ist das schwieriger, da nicht klar ist, nach wieviel Sondierungs-Schritten ein
Element eingefügt wurde. Das ist gut so, denn dann wird genau das beim Bearbeiten der
Aufgabe geübt. Eine Beispiel-Aufgabenstellung für doppeltes Hashing ist</p>
        <sec id="sec-4-1-1">
          <title>Replace each hole (_) in the configuration</title>
          <p>
            Config { size = 10 , hash1 = \ x -&gt; _ + _ * x , hash2 = \ x -&gt; _ }
and in the sequence of operations
[ Insert _ , Insert _ , Insert _
, Insert 40 , Insert _ , Insert _ , Insert 22 ]
with a numerical expression
such that the sequence of operations transforms
Table (listToFM [ (
            <xref ref-type="bibr" rid="ref10">2,12</xref>
            ),(9,41)])
to Table (listToFM
[ (0,11),(1,25),(
            <xref ref-type="bibr" rid="ref10">2,12</xref>
            ),(3,89),(4,22),(6,40),(7,97),(8,64),(9,41) ])
Eine Lösung ist
          </p>
          <p>Solution { config = Config { size = 10 , hash1 = \ x -&gt; 6 + 3 * x
, hash2 = \ x -&gt; 1 }
, ops = [ Insert 25 , Insert 11 , Insert 89 , Insert 40
, Insert 97 , Insert 64 , Insert 22 ] }
Ein Ausschnitt aus der Antwort des System ist
Die Aufgaben zum doppelten Hashing erwiesen sich beim Einsatz im Sommersemester
2017 tatsächlich als schwierig, mit einer hohen Anzahl von Fehlversuchen.</p>
        </sec>
      </sec>
    </sec>
    <sec id="sec-5">
      <title>5 Algorithmen auf Graphen</title>
      <p>Die Breitensuche wird durch folgenden Aufgabentyp getestet: eine Aufgaben-Instanz
besteht aus einem Muster GM für die Adjazenz-Listen-Darstellung eines Graphen, einem
Startknoten s und einem Muster TM für einen Baum. Eine Lösung ist eine
AdjazenzListen-Darstellung G eines Graphen. Die Lösung ist korrekt, wenn G zu GM paßt und der</p>
      <sec id="sec-5-1">
        <title>Breitensuchbaum BFS¹Gº, der von autotool berechnet wird, zu TM paßt.</title>
        <p>Eine Beispiel-Aufgabenstellung ist</p>
        <sec id="sec-5-1-1">
          <title>Replace the holes (_) in the adjacency list of the graph G</title>
          <p>Adjacency_List [ ( _ , [ _, 4, 7] ), ( _, [ 5, _]), ( 4, [ _, _])
, ( _, [ _, _, 4, 5]), ( _, [ 4, _, 1, 3]), ( 3, _), ( 6, [ _, 1, _]) ]
so that the breadth first search tree of G, starting at 3,
matches the pattern</p>
        </sec>
        <sec id="sec-5-1-2">
          <title>Branch 3 [ Branch _ [ Branch 6 _ , Branch 5 [ Branch 2 [ ] , Branch _ [ ] ] ] ]</title>
          <p>Die Konfiguration des Aufgabengenerators ist</p>
        </sec>
        <sec id="sec-5-1-3">
          <title>Config { graph_generator = Graph_Generator { directed = True, vertices = 7, out_degree_range = ( 1, 4) } , handle_graph = Show 0.7, handle_bfs_tree = Show 0.7, candidates = 100 }</title>
          <p>Es werden 100 Graphen G erzeugt und für jeden T = BFS¹Gº bestimmt. In G und T werden
zufällig ausgewählte Teilstrukturen versteckt, d.h., durch _ ersetzt, so dass der Anteil der
sichtbaren Funktionssymbole möglichst nahe bei 70 Prozent liegt.</p>
          <p>Eine ähnliche Aufgabe behandelt Tiefensuch-Wälder.</p>
        </sec>
      </sec>
    </sec>
    <sec id="sec-6">
      <title>6 Technische Details</title>
      <p>Das System autotool besteht aus einem zustandslosen Semantik-Service zur Generierung
von Aufgabeninstanzen und zur Bewertung von Lösungsversuchen, implementiert als
XML-RPC-Service mit haxr, einer Persistenz-Schicht zur Speicherung von Aufgaben,
Einschreibungen, Punkten u.ä., implementiert als mysql (mariabdb) mit persistent [Sn17] einer
Web-Oberfläche [Si16], implementiert mit yesod [Sn12]. Damit können Studenten Aufgaben
bearbeiten und Bewertungen betrachten sowie Dozenten die Aufgaben konfigurieren und
testen sowie Aufgaben und Punkte verwalten.</p>
      <p>Automatische Erzeugung und Bewertung von Aufgaben zu Algorithmen und Datenstrukturen 7
Die hier beschriebenen Aufgaben sind als Module im Semantik-Service realisiert. Die
gemeinsame Modulschnittstelle ist durch diese mehrparametrige Relation (Typklasse) für p
(Aufgabentyp), i (Typ der Instanz), b (Typ der Einsendung) beschrieben:
describe: p i ! Text beschreibt Aufgabenstellung,
initial: p i ! b berechnet einen syntaktisch korrekten, aber semantisch falschen
Lösungsvorschlag,</p>
      <p>Text bewertet die Einsendung, liefert Resultat richtig/falsch und
eval: p i b ! B
Begründung
generate: p
c</p>
      <sec id="sec-6-1">
        <title>Seed ! i</title>
        <p>Das Würfeln von Aufgabeninstanzen nach einer Konfiguration c wird beschrieben durch
wobei Seed aus der Matrikelnummer (oder einem anderen Kennzeichen des Studenten)
bestimmt wird und der Initialisierung eine deterministischen Pseudozufallsgenerators dient.
Der Student gibt eine textuelle Repräsentation der Lösung (vom Typ b) ein. Das
Texteingabefeld wird mit initial¹p; iº initialisiert. Mit Absicht gibt es keine grafische Eingabemöglichkeit,
um Studenten an die wissenschaftliche Notation (durch Terme in einer geeigneten Signatur)
zu gewöhnen. Bei den allermeisten Aufgaben ist die konkrete Syntax für b die
DatenSyntax von Haskell [Sim10], mit den Ausdrucksmitteln für primitive Daten: 123, False,
für Tupel: (124,False), für (typreine) Listen: [(123,False),(456,True)], Konstruktoren
algebraischer Datentypen, mit Argumenten: Just (123,False), Records mit benannten
Komponenten: Config{size=10}. Pretty-Printer und Parser werden automatisch
generiert. Gelegentlich wird eine aufgabenspezifische konkrete Syntax verwendet, z.B. für
arithmetische Ausdrücke in Hashfunktionen.</p>
        <p>Die Eingabe-Parser [LM16] erzeugen nützliche Fehlermeldungen. Die fehlerhafte Stelle
wird markiert und mögliche Fortsetzungen werden ausgegeben. Jedes Eingabefeld (für den
Studenten: das für den Lösungsversuch, für den Dozenten: das für die Konfiguration der
Instanz oder des Instanzengenerators) ist typisiert und wird automatisch mit einem Verweis
auf die API-Dokumentation des jeweiligen Typs versehen.</p>
        <p>Die hier gezeigten Aufgabe verwenden das Prinzip, dass Teile einer Datenstruktur (Bsp:
Koefizienten der Hashfunktion, Elemente der Liste von Operationen) in der
Aufgabenstellung versteckt werden und zur Lösung zu ergänzen sind. Ein Typ p dient in diesem Sinne
als Vorlagentyp, wenn für ihn diese Eigenschaften deklariert werden:
der zugehörige Basistyp: type (Base p)
der Mustervergleich match : p</p>
      </sec>
      <sec id="sec-6-2">
        <title>Base¹pº ! B</title>
        <p>Text
die Injektion inject: Base¹pº ! p
das pseudozufällige Verstecken von Bestandteilen obfuscate : Base¹pº</p>
      </sec>
      <sec id="sec-6-3">
        <title>Seed ! p.</title>
        <p>7</p>
      </sec>
    </sec>
    <sec id="sec-7">
      <title>Diskussion</title>
      <p>Der autotool-Semantik-Service wird ebenfalls vom autolat-Plugin [FS10] für
OpenOLAT [Fr17] unterstützt und an der Universität Leipzig angewendet. Ich möchte autotool
gern in weitere Systeme integrieren. Eine Verwendung der QTI-Schnittstelle [IM15] ist
naheliegend, scheint aber derzeit nicht möglich, da im Standard weder das externe
Generieren von Aufgaben noch das externe Bewerten von Einsendungen vorgesehen sind, sondern
höchsten Zeichenkettenvergleiche, die von QTI-Player selbst ausgeführt werden. Das ist für
eine semantische Bewertung mit sinnvoller Rückmeldung völlig unzureichend.
Die hier beschriebenen Aufgaben habe ich für die Vorlesung Algorithmen und
Datenstrukturen für Informatik-Bachelor-Studenten im 2. Semester, die ich vertretungsweise
übernommen habe, überarbeitet bzw. neu entwickelt. Dabei haben ca. 80 Studenten über das
Sommersemester 2017 hinweg jede Woche 1 bis 2 autotool-Aufgaben bearbeitet, mit ca. 10
Einsendungen pro Aufgabe. Für einzelne Aufgaben habe ich deutlich mehr Einsendungen
registriert. Das kann an der Schwierigkeit der Aufgabe liegen, aber auch daran, dass ich
explizit zum Durchprobieren verschiedener, auch “falscher” Einsendungen ermuntert hatte.
Ich habe damit die eigentlich vorgesehenen Java-Programmier-Übungen ersetzt. Die
autotool-Aufgaben schienen mir dem didaktischen Ziel des Verständnisses der
Algorithmen nützlicher. Der Aufwand zum Lösen und Korrigieren von Java-Programmen ist
für Student und Dozent deutlich höher, so dass Programmieraufgaben üblicherweise nur
dreimal während des Semesters gestellt wurden. Tatsächlich habe ich wohl die Arbeit
verschoben: vom (z.T. händischen) Korrigieren der Java-Aufgaben zum Programmieren der
autotool-Aufgaben — allerdings in Haskell, das sehr konzise Quelltexte ermöglicht und
sehr hohe statische Sicherheit garantiert und somit den Entwicklungsaufwand verringert.
Die Studenten haben in dieser Vorlesung keine Programme selbst geschrieben und (außer
Pseudocode im Skript und autotool-Quelltexten) kaum welche gesehen — das finde
ich nicht so tragisch, zumal es parallel eine Vorlesung zur anwendungsorientierten
JavaProgrammierung gibt und vorher eine zu Grundlagen der Programmierung.
Durch die autotool-Aufgaben haben sich die Studenten auch ohne Programmieren mit
den Algorithmen beschäftigt, indem sie ihre Ausführung durch das autotool beobachtet
und durch geeignete Eingaben zielgerichtet gesteuert haben. Für die Übungen habe ich
damit Zeit gewonnen für die Diskussion von Aufgaben zu Eigenschaften von Algorithmen.
(Die Prüfungszulassung ergibt sich aus beiden Anteilen.) Eine wissenschaftliche fundierte
Einschätzung des tatsächlichen Nutzens (und Aufwandes) von autotool-Aufgaben scheint
mir hier nicht möglich, da zu viele weitere Parameter variieren: nicht nur Änderung der
Übungs-Aufgaben, sondern auch im Vortragsstil, im Skript, in Klausuraufgaben.
Berichten von Kollegen sowie eigene Erfahrung mit autotool in anderen Vorlesungen [Wa15,
Wa14] bestätigen, dass Studenten das System schnell verstehen und gern benutzen und
besonders die sofortigen, ausführlichen und objektiven Antworten begrüßen.</p>
      <p>Automatische Erzeugung und Bewertung von Aufgaben zu Algorithmen und Datenstrukturen 9</p>
    </sec>
    <sec id="sec-8">
      <title>Literaturverzeichnis</title>
    </sec>
  </body>
  <back>
    <ref-list>
      <ref id="ref1">
        <mixed-citation>
          [CLR09] Cormen, Thomas H.; Leiserson, Charles E.; Rivest, Ronald: Introduction to Algorithms. MIT Press,
          <year>2009</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref2">
        <mixed-citation>
          [Fr17]
          <article-title>Frentix GmbH: , LMS OpenOLAT</article-title>
          . https://www.openolat.com/,
          <year>2017</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref3">
        <mixed-citation>
          [FS10] Felgenhauer, Bertram; Schölhorn, Klemens: ,
          <article-title>Autolat: autotool integration for OpenOLAT</article-title>
          . https://github.com/klemens/openolat/wiki,
          <year>2010</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref4">
        <mixed-citation>
          [Ga17] Gansner, Emden et al.: , Graphviz - Graph Visualization Software. http://www.graphviz. org/,
          <year>2017</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref5">
        <mixed-citation>
          [IM15]
          <article-title>IMS Global Learning Consortium:</article-title>
          ,
          <source>IMS Question and Test Interoperability v2</source>
          .
          <article-title>2 Final Specification</article-title>
          . https://www.imsglobal.org/question/index.html,
          <year>2015</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref6">
        <mixed-citation>
          [LM16] Leijen, Daan; Martini, Paolo: , parsec: Monadic Parser Combinators. https://hackage. haskell.org/package/parsec,
          <year>2016</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref7">
        <mixed-citation>
          [RW02] Rahn, Mirko; Waldmann,
          <string-name>
            <surname>Johannes:</surname>
          </string-name>
          <article-title>The Leipzig autotool System for Grading Student Homework</article-title>
          . In: Intl. Workshop on Functional and
          <article-title>Declarative Programming in Education (FDPE</article-title>
          <year>2002</year>
          ).
          <source>Technical Report 0210</source>
          , University of Kiel,
          <year>2002</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref8">
        <mixed-citation>
          [Si16]
          <article-title>Siegburg, Marcellus: REST-orientierte Refaktorisierung des E-Learning-Systems Autotool</article-title>
          . Masterarbeit, HTWK Leipzig,
          <year>2016</year>
          . http://www-ps.informatik.uni-kiel.de/~msi/ thesis/thesis.pdf.
        </mixed-citation>
      </ref>
      <ref id="ref9">
        <mixed-citation>
          [Sim10]
          <article-title>Haskell 2010 Language Report</article-title>
          . https://www.haskell.org/onlinereport/haskell2010/,
          <year>2010</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref10">
        <mixed-citation>
          [Sn12]
          <string-name>
            <surname>Snoyman</surname>
          </string-name>
          , Michael: ,
          <source>Developing Web Applications with Haskell and Yesod</source>
          ,
          <year>2012</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref11">
        <mixed-citation>
          [Sn17]
          <article-title>Snoyman, Michael: , persistent: Type-safe, multi-backend data serialization</article-title>
          . https:// hackage.haskell.org/package/persistent,
          <year>2017</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref12">
        <mixed-citation>
          [Wa14]
          <article-title>Waldmann, Johannes: Automated Exercises for Constraint Programming</article-title>
          .
          <source>In: 28th Workshop on (Constraint) Logic Programming (WLP</source>
          <year>2014</year>
          ), Wittenberg.
          <year>2014</year>
          . http://www.imn.htwkleipzig.de/~waldmann/talk/14/wlp/auto/.
        </mixed-citation>
      </ref>
      <ref id="ref13">
        <mixed-citation>
          [Wa15]
          <article-title>Waldmann, Johannes: Automatisierte Bewertung und Erzeugung von Übungsaufgaben zu Prinzipien von Programmiersprachen</article-title>
          .
          <source>In: 18. Kolloquium Programmiersprachen und Grundlagen der Programmierung KPS</source>
          <year>2015</year>
          , Pörtschach.
          <year>2015</year>
          . http://www.imn.htwkleipzig.de/~waldmann/talk/15/kps/paper/.
        </mixed-citation>
      </ref>
      <ref id="ref14">
        <mixed-citation>
          [Wa17] Waldmann, Johannes: , Leipzig autotool. https://gitlab.imn.htwk-leipzig.de/autotool/ all0,
          <year>2017</year>
          .
        </mixed-citation>
      </ref>
    </ref-list>
  </back>
</article>