Wir müssen eine geeignete Tuple-Repräsentation für die Stückliste finden, um die vorhandenen Operationen fread und fwrite verwenden zu können; für jedes beschriebene Teil in der Stückliste (also in deren Argumentmenge) erzeugen wir daher den folgenden, variabel langen Datensatz:
Wir könnten die Teilnummer auch als Optional modellieren und das Ende mit nil markieren. Wir verwenden hier nur deshalb direkt den für Nat1 ungültigen Wert, damit das Beispiel nicht den Rahmen sprengt -- Sie sollten aber in einer vergleichbaren Situation an die Optionals denken!
read_plist Die Funktion read_plist soll
ein Tuple aus einer Datei lesen und in das Plist-Format konvertieren.
Dazu wird das Tuple sequentiell verarbeitet: das erste Element eines
Datensatzes ist die Nummer des zu beschreibenden Teils pn, sie wird
entnommen (mit sel_pntup) und eine (zunächst leere) Bag der
Bestandteile erzeugt (mk_empty_pnbag). Nun wird das nächste
Element aus dem Tuple entnommen; wenn es ungleich ZERO ist, ist es ein
Bestandteil von pn und das nächste Element gibt seine
Häufigkeit an. Jedes gefundene Paar wird der Bag hinzugefügt (
union1_pnbag), bis schließlich der Datensatz mit ZERO endet; dann wird
pn mit seiner Bag in die Stückliste eingefügt (mit
union1_plist). Sind noch weitere Daten im Tuple, wird das nächste Teil
pn bearbeitet (diese Prüfung muß für die korrekte Bearbeitung des leeren
Tuple jeweils am Anfang erfolgen). Ist die Stückliste komplett, wird das Tuple
wieder gelöscht (del_pntup).
Plist read_plist (Str datafile)
*
{
*
Pntup flist;
*
Pnbag pbag;
*
Nat1 pn;
*
Nat0 sub_pn;
*
Nat1 length;
*
Nat1 i = 1;
*
Plist plist = mk_empty_plist();
flist = fread_pntup(datafile);
*
length = len_pntup(flist);
while (i <= length) {
*
pn = sel_pntup(flist, i++);
*
sub_pn = sel_pntup(flist, i++);
*
pbag = mk_empty_pnbag();
while (sub_pn != ZERO) {
*
pbag = union1_pnbag(pbag, sub_pn, sel_pntup(flist,
i++));
*
sub_pn = sel_pntup(flist, i++);
*
}
*
plist = union1_plist(plist, pn, pbag);
*
}
*
del_pntup(flist);
*
return plist;
*
}
*[1.5ex]
Wenn der Lesevorgang mit fread nicht erfolgreich war, wird eine leere Stückliste zurückgegeben. Diese Funktion ist ein Konstruktor -- eventuell müssen Sie also vor dem Aufruf die Stückliste, der Sie das Resultat dieser Funktion zuweisen wollen, explizit löschen!
write_plist Die Funktion write_plist
soll ein ebenes Tuple aus der strukturierten Stückliste erzeugen und diese in
eine Datei schreiben. Ablauf: ein Teil pn aus der Stückliste wird gewählt
(sel_dom_plist) und dem Tuple mit appr_pntup
angehängt. Dann wird die zugehörige Bag mit sel_plist ermittelt
und das Teil aus der Stückliste entfernt (sub1_plist); in einer
Schleife werden der Reihe nach die Bestandteile der Bag entnommen
(auf die gleiche Weise wie bei den Teilen pn) und ihre Nummern gemeinsam
mit ihren Häufigkeiten dem Tuple angehängt (mit conc_pntup). Ist die
Bag leer, wird dem Tuple die Ende-Kennung des Datensatzes (ZERO) angehängt und
der Ablauf wiederholt sich, wenn die Stückliste noch Teile enthält (damit eine
leere Stückliste korrekt bearbeitet wird, muß diese Prüfung zu Beginn
erfolgen). Schließlich wird das fertige Tuple mit fwrite_pntup in
die Datei geschrieben. Da die Stückliste bei diesem Vorgang zerstört wird,
wird vorher eine ,,Arbeitskopie`` angelegt, ebenso von jeder Bag, die der
Stückliste entnommen wird. Das Funktionsergebnis entspricht dem Regebnis von
fwrite.
Bool write_plist (Plist plist, Str datafile)
*
{
*
Plist plwork = copy1_plist(plist);
*
Pntup flist = mk_empty_pntup();
*
Pnbag pbag;
*
Nat1 pn, sub_pn, count;
*
Bool result;
while (! is_empty_plist(plwork)) {
*
pn = sel_dom_plist(plwork);
*
pbag = copy1_pnbag(sel_plist(plwork, pn));
*
plwork = sub1_plist(plwork, pn);
*
flist = appr_pntup(flist, pn);
while (! is_empty_pnbag(pbag)) {
*
sub_pn = sel_dom_pnbag(pbag);
*
count = sel_pnbag(pbag, sub_pn);
*
pbag = sub1_pnbag(pbag, sub_pn);
*
flist = conc_pntup(flist, mk2_pntup(sub_pn, count));
*
}
*
flist = appr_pntup(flist, ZERO);
*
}
result = fwrite_pntup(flist, datafile);
*
del_pntup(flist);
*
return result;
*
}
*[1.5ex]
Die Eingabe unterteilt sich in folgende Abschnitte:
Wie beendet man die Eingabe der Bestandteil-Liste ? Hier nutzen wir wieder die ungültige Teilnummer ZERO, die uns beim Laden und Speichern schon geholfen hat. Außerdem ist es wünschenswert, die Eingabe der Beschreibung abbrechen zu können, wenn ein Bestandteil noch nicht in der Stückliste enthalten ist -- vielleicht möchte man es zunächst eingeben.
new_part Die Funktion new_part gliedert sich entsprechend der oben angegebenen Abschnitte der Eingabe. Zunächst wird eine Teilnummer pn angefordert und mit is_in_plist geprüft, ob das Teil bereits beschrieben ist; wenn ja, muß die Neueingabe einer Beschreibung bestätigt werden. Dieser Ablauf wiederholt sich solange, bis entweder die Nummer ZERO für den Abbruch der Eingabe oder ein gültiges, zu beschreibendes Teil eingegeben wurde. Wurde ZERO eingegeben, wird die Funktion mit unveränderter Stückliste verlassen.
Nun wird eine leere Bag zur Aufnahme der Bestandteile erzeugt (
mk_empty_pnbag). In einer Schleife werden nun Bestandteilnummer
und Häufigkeit eingegeben, bis die Eingabe mit der Nummer ZERO beendet oder
nach Eingabe eines unbekannten Teils abgebrochen wird. Das Flag exist
gibt an, ob das Teil in der Stückliste beschrieben ist (
is_in_plist); wenn nicht, wird gefragt, ob die Eingabe abgebrochen werden
soll und bei positiver Anwort die Funktion mit unveränderter Stückliste
verlassen. Ist das Teil aber vorhanden, wird mit is_in_pnbag
geprüft, ob es bereits als Bestandteil von pn eingegeben wurde. Wenn
nicht, muß nur noch geprüft werden, ob es das zu beschreibende Teil pn
selbst als Bestandteil hat -- dazu wird mit der Hilfsfunktion parts_pn
ein Set sämtlicher Bestandteile von
ermittelt und mit
is_in_pnset geprüft, ob das Teil pn nicht in diesem Set enthalten ist.
War
ungültig, muß die Eingabe wiederholt werden. Wurde schließlich
eine gültige Nummer eingegeben (also auch nicht ZERO zum ordnungsgemäßen Ende
der Eingabe), wird deren Häufigkeit erfragt, die natürlich mindestens 1 sein
muß. Das vollständige Paar wird nun mit union1_pnbag der Bag von
Bestandteilen hinzugefügt.
Nach erfolgreicher Beendigung der Eingabe wird die Stückliste geändert: ist das Teil pn neu (wird es also nicht re-definiert), kann das Paar pn und Bag mit union1_plist in die Stückliste eingetragen werden; sonst muß der bestehende Eintrag überschrieben und dabei gleichzeitig gelöscht werden -- das macht ovwrt1d_plist. Die Stückliste wird nun zurückgeliefert.
Auf die Prüfung der Benutzereingaben wird hier übrigens verzichtet, damit die Funktion nicht den Rahmen sprengt.
Plist new_part (Plist plist)
*
{
*
Pnbag pbag;
*
Pnset ps;
*
Bool ovwrt, exist, twice, recursive;
*
int c;
*
Nat0 pn, sub_pn, count;
do {
*
puts("
Neue Teilnummer (Abbruch mit 0):"
);
*
scanf("
%ld"
, &pn);
*
ovwrt = is_in_plist(pn, plist);
*
if (ovwrt) {
*
puts("
Teil ist vorhanden - neu definieren ?"
);
*
c = toupper(getchar());
*
}
*
else
*
c = 'J';
*
} while (c != 'J');
if (pn == ZERO)
*
return plist;
pbag = mk_empty_pnbag();
*
do {
*
do {
*
printf("
Bestandteil von %ld (Ende mit 0):\n"
,
pn);
*
scanf("
%ld"
, &sub_pn);
*
exist = sub_pn == ZERO || is_in_plist(sub_pn,
plist);
*
brk = twice = recursive = FALSE;
*
if (sub_pn != ZERO)
*
continue;/ Vorzeitiges Ende der Schleife
/
if (! exist) {
*
printf("
Teil %ld nicht definiert.\n"
, sub_pn);
*
printf("
Eingabe fuer %ld abbrechen ?\n"
, pn);
*
c = toupper(getchar());
*
if (c == 'J') {
*
del_pnbag(pbag);
*
return plist;
*
}
*
}
*
else {
*
twice = is_in_pnbag(sub_pn, pbag);
*
if (twice)
*
puts("
Teil ist schon angegeben."
);
*
else {
*
ps = parts_pn(plist, sub_pn);
*
recursive = is_in_pnset(pn, ps);
*
del_pnset(ps);
*
if (recursive)
*
puts("
Rekursive Beschreibung."
);
*
}
*
}
*
} while (! exist || twice || recursive);
if (sub_pn != ZERO) {
*
do {
*
printf("
Haeufigkeit des Teils %ld:\n"
, sub_pn);
*
scanf("
%ld"
, &count);
*
if (count < ONE)
*
puts("
Haeufigkeit bitte groesser als 0 !"
);
*
} while (count < ONE);
*
pbag = union1_pnbag(pbag, sub_pn, count);
*
}
*
} while (sub_pn != ZERO);
if (ovwrt)
*
plist = ovwrt1d_plist(plist, pn, pbag);
*
else
*
plist = union1_plist(plist, pn, pbag);
*
return plist;
*
}
*[1.5ex]
parts_pn Die Hilfsfunktion parts_pn
liefert, wie Sie es schon von der qualitativen Stückliste her kennen, den Set
aller Bestandteile eines Teils -- dazu wird der Set aller direkten
Bestandteile ermittelt und damit die Funktion parts_pnset aufgerufen,
die zu einem Set den Set aller Bestandteile ermittelt. Die Funktion
parts_pnset finden Sie auf
parts_pn etwas anders aussieht als ihr Pendant: damit erkannt wird, ob
pn direkt durch sich selbst definiert wird, wird das Teil in einen
ein-elementigen Set umgewandelt; dann enthält der Set aller Bestandteile von
auch
selbst und ein separater Test auf Gleichheit von
pn und
kann entfallen.
Pnset parts_pn (Plist plist, Nat1 pn)
*
{ return parts_pnset(plist, mk_one_pnset(pn)); }
*[1.5ex]
Die von parts_pnset benötigte Hilfsfunktion subparts_pnset können wir mit Hilfe von fold für Sets auf die gleiche Weise realisieren wie für eine qualitative Stückliste -- sie finden diese Funktion auf Unterschied betrifft allerdings die Konvertierungsfunktion, die ja keine qualitative Stückliste als Argument erhält und demzufolge den Set der direkten Bestandteile eines Teils auf eine andere Weise ermitteln muß! Da der Set der direkten Bestandteile die Argumentmenge der zugeordneten Bag ist, brauchen wir eine Funktion dom_pnbag, die uns diese Argumentmenge liefert. Wie auf Maps realisierbar:
IMPLEMENTATIONS
*
Pnbag=¯...
*
+ fold.dom(RESULT=
mit der Konvertierungsfunktion "
Pnset"
,
*
CVFCT="
arg"
,
*
NULLFCT="
mk_empty"
,
*
FOLDFCT="
union"
)
*
Pnset arg_nat1_nat1 (Nat1 pn, Nat1 sub_pn)
*
{ return mk_one_pnset(pn); }
*[1.5ex]
Dann sieht die Konvertierungsfunktion für subparts_pnset so aus:
Pnset get_nat1 (Nat1 pn, Plist plist)
*
{ return dom_pnbag(copy1_pnbag(sel_plist(plist, pn))); }
*[1.5ex]
Die Struktur-Stückliste für ein Teil ist rekursiv und muß daher durch eine rekursive semantische Funktion aufgebaut werden. Für Testzwecke kann ihre Ausgabe auf dem Bildschirm mit Hilfe der Operation pr_slist erfolgen, so daß wir uns auf ihre Konstruktion beschränken wollen.
Beispiel: Das Teil #10 besteht aus zwei Teilen #14 und einem Teil #3, welches unzerlegbar ist; das Teil #14 wiederum enthält einmal Teil #3, sechsmal das atomare Teil #31 und dreimal das Teil #9, welches wiederum viermal das Teil #31 enthält. Die Struktur-Stückliste hat dann folgendes schematisches Aussehen:
Teil #10 | |||
*[1ex] | #14(2x) | #3 (1x) | |
*[1ex] #3 (2x) | #9(6x) | #31(12x) | |
*[1ex] | #31 (24x) |
Auf jeder Stufe soll also die Anzahl der benötigten Teile angegeben sein, die von der Anzahl übergeordneter Teile abhängt; diese Angabe muß der Funktion als Argument übergeben werden. Das zu strukturierende Teil wird einmal benötigt, also ist das Argument beim erstmaligen Aufruf gleich 1. Außerdem wird für das zu strukturierende Teil auf jeder Stufe die Bag der Bestandteile ermittelt und ebenfalls an die Funktion übergeben. Damit diese Bags beim rekursiven Aufruf auch zugreifbar sind, ist die Stückliste ein weiteres, allerdings unveränderliches Argument.
build_slist Diese Funktion heißt build_slist und führt, solange die als Argument erhaltene Bag der Bestandteile nicht leer ist, folgende Schritte aus:
Zu Beginn der Funktion wird eine lokale Struktur-Stückliste im leeren Zustand erzeugt (mk_empty_slist), die dann als Resultat zurückgeliefert wird.
Slist build_slist (Pnbag pbag, Nat1 times, Plist plist)
*
{
*
Slist slist = mk_empty_slist();
*
Pnrec prec;
*
Pnbag sub_bag;
*
Nat1 sub_pn, count;
while (! is_empty_pnbag(pbag)) {
*
sub_pn = sel_dom_pnbag(pbag);
*
count = times sel_pnbag(pbag, sub_pn);
*
pbag = sub1_pnbag(pbag, sub_pn);
*
sub_bag = copy1_pnbag(sel_plist(plist, sub_pn));
*
prec = mk_pnrec(count, build_slist(sub_bag, count,
plist));
*
slist = union1_slist(slist, sub_pn, prec);
*
}
*
return slist;
*
}
*[1.5ex]
Das Steuerprogramm
Ein größeres Beispiel
Quantitative Stückliste - das