Uppgift 4A - Definition av enkla funktioner (defun start-klockslag (tidsperiod) "tidsperiod -> klockslag" (typkontroll tidsperiod #'tidsperiod?) (first (packa-upp tidsperiod))) (defun slut-klockslag (tidsperiod) "tidsperiod -> klockslag" (typkontroll tidsperiod #'tidsperiod?) (second (packa-upp tidsperiod))) (defun skapa-tidsrymd (timme minut) "timme x minut -> tidsrymd" (typkontroll timme #'timme?) (typkontroll minut #'minut?) (packa-ihop 'tidsrymd (list (skapa-timme (+ (heltal timme) (floor (/ (heltal minut) 60)))) (skapa-minut (mod (heltal minut) 60))))) (defun längd-av-tidsperiod (tidsperiod) "tidsperiod -> tidsrymd" (typkontroll tidsperiod #'tidsperiod?) (let ( (timme1 (heltal (timdel (start-klockslag tidsperiod)))) (timme2 (heltal (timdel (slut-klockslag tidsperiod)))) (minut1 (heltal (minutdel (start-klockslag tidsperiod)))) (minut2 (heltal (minutdel (slut-klockslag tidsperiod))))) (skapa-tidsrymd (skapa-timme (+ (- timme2 timme1) (floor (/ (- minut2 minut1) 60)))) (skapa-minut (mod (- minut2 minut1) 60))))) (defun överlapp (period1 period2) "tidsperiod x tidsperiod -> tidsperiod" (typkontroll period1 #'tidsperiod?) (typkontroll period2 #'tidsperiod?) (if (överlappar? period1 period2) (senaste-klockslag (start-klockslag period1) (start-klockslag period2)) (tidigaste-klockslag (slut-klockslag period1) (slut-klockslag period2))) nil)) 2006-11-08 1
Uppgift 4B - Ny datatyp (defun skapa-tidsperioder () "-> tidsperioder" (packa-ihop 'tidsperioder '())) (defun tidsperioder? (objekt) "Lisp-objekt -> sanningsvärde" (eq 'tidsperioder (typ objekt))) (defun tom-tidsperioder? (perioder) "tidsperioder -> sanningsvärde" (typkontroll perioder #'tidsperioder?) (null (packa-upp perioder))) Tidsperiod-före kollar om en tidsperiod börjar före en annan: (defun tidsperiod-före? (period1 period2) "tidsperiod x tidsperiod -> sanningsvärde" (typkontroll period1 #'tidsperiod?) (typkontroll period2 #'tidsperiod?) (före-klockslag? (start-klockslag period1) (start-klockslag period2))) Lägg-in-tidsperiod lägger in en ny tidsperiod i tidsperioderna. Den sorterar in den nya perioden genom att sätta ihop <perioder före> + nya perioden + <perioder efter>. (defun lägg-in-tidsperiod (perioder nyperiod) "tidsperioder x tidsperiod -> tidsperioder" (typkontroll perioder #'tidsperioder?) (typkontroll nyperiod #'tidsperiod?) (packa-ihop 'tidsperioder (append (remove-if-not #'(lambda (per) (tidsperiod-före? per nyperiod)) (packa-upp perioder)) (list nyperiod) (remove-if #'(lambda (per) (tidsperiod-före? per nyperiod)) (packa-upp perioder))))) 2006-11-08 2
(defun första-tidsperiod (perioder) "tidsperioder -> tidsperiod" (typkontroll perioder #'tidsperioder?) (first (packa-upp perioder))) (defun resten-tidsperiod (perioder) "tidsperioder -> tidsperioder" (typkontroll perioder #'tidsperioder?) (packa-ihop 'tidsperioder (rest (packa-upp perioder)))) (defun skriv-tidsperioder (perioder) "tidsperioder ->" (typkontroll perioder #'tidsperioder?) (mapc #'(lambda (per) (skriv-tidsperiod per)(format t "~%")) (packa-upp perioder))) Uppgift 4C - Avbokning av möten Tar bort möten som börjar vid ett klockslag från en dagalmanacka (defun ta-bort-möte (klockslag dagalma) "klockslag x dagalmanacka -> dagalmanacka" (typkontroll klockslag #'klockslag?) (typkontroll dagalma #'dagalmanacka?) (packa-ihop 'dagalmanacka (remove-if #'(lambda (mt) (samma-klockslag? (start-klockslag (tidsperioddel mt)) klockslag)) (packa-upp dagalma)))) (defun avboka-möte (årsalma dag mån start) "årsalmanacka x dag x månad x klockslag -> årsalmanacka" (typkontroll årsalma #'årsalmanacka?) (typkontroll dag #'dag?) (typkontroll mån #'månad?) (typkontroll start #'klockslag?) (let ((dagalma (dagalmanacka dag (månadsalmanacka mån årsalma)))) (lägg-in-månadsalmanacka mån (lägg-in-dagalmanacka dag (ta-bort-möte start dagalma) (månadsalmanacka mån årsalma)) årsalma))) 2006-11-08 3
Gränssnittsfunktion för att avboka: (defun avboka (namn d m tid) "symbol x heltal x symbol x sträng ->" (typkontroll namn #'symbolp) (typkontroll d #'numberp) (typkontroll m #'symbolp) (let* ( (dag (skapa-dag d)) (mån (skapa-månad m)) (start (omvandla-klockslag tid)) (dagalma (dagalmanacka dag (månadsalmanacka mån (hämta-almanacka namn))))) (if (bokat-från? dagalma start) (progn (lägg-in-almanacka namn (avboka-möte (hämta-almanacka namn) dag mån start)) (format t "Mötet är avbokat.~%")) (format t "Mötet existerar inte!~%")) (values))) Körexempel CL-USER(9): (boka 'tomasf 14 'sep "08:00" "12:00" "Koda Lisp") Mötet är bokat. CL-USER(10): (boka 'tomasf 14 'sep "13:00" "15:00" "Koda mer Lisp") Mötet är bokat. CL-USER(11): (visa 'tomasf 14 'sep) 14 SEPTEMBER ============ 08:00-12:00 Koda Lisp 13:00-15:00 Koda mer Lisp CL-USER(12): (avboka 'tomasf 14 'sep "08:00") Mötet är avbokat. CL-USER(13): (visa 'tomasf 14 'sep) 14 SEPTEMBER ============ 13:00-15:00 Koda mer Lisp avboka avboka-möte ta-bort-möte almanacka.cl bokningar.cl primitiver.cl 2006-11-08 4
Uppgift 4D - Lediga tider Tar reda på första mötestiden efter ett visst klockslag, eller nil om inget hittas: (defun första-mötestid-efter (dagalma tid) "dagalmanacka x klockslag -> mötestid" (typkontroll dagalma #'dagalmanacka?) (typkontroll tid #'klockslag?) (if (tom-dagalmanacka? dagalma) nil (if (före-klockslag? tid (start-klockslag (tidsperioddel (första-mötestid dagalma)))) (första-mötestid dagalma) (första-mötestid-efter (resten-dagalmanacka dagalma) tid)))) Kollar om klockslaget ligger inom perioden: (defun tid-inom-period? (period tid) "tidsperiod x klockslag -> sanningsvärde" (typkontroll period #'tidsperiod?) (typkontroll tid #'klockslag?) (and (före-klockslag? tid (slut-klockslag period)) (not (före-klockslag? tid (start-klockslag period))))) Returnerar mötestiden som ligger på det avgivna klockslaget: (defun mötestid-klockan (dagalma tid) "dagalmanacka x klockslag -> mötestid" (typkontroll dagalma #'dagalmanacka?) (typkontroll tid #'klockslag?) (if (tom-dagalmanacka? dagalma) nil (if (tid-inom-period? (tidsperioddel (första-mötestid dagalma)) tid) (första-mötestid dagalma) (mötestid-klockan (resten-dagalmanacka dagalma) tid)))) 2006-11-08 5
Returnerar det närmast följande lediga klockslaget efter ett visst klockslag: (defun nästa-lediga-klockslag (dagalma klockslag) "dagalmanacka x klockslag -> klockslag" (typkontroll dagalma #'dagalmanacka?) (typkontroll klockslag #'klockslag?) (let ((mötestid (mötestid-klockan dagalma klockslag))) (if (null mötestid) klockslag (nästa-lediga-klockslag dagalma (slut-klockslag (tidsperioddel mötestid)))))) Lägger ihop innehållet av två tidsperioder-objekt: (defun sätt-ihop-tidsperioder (perioder1 perioder2) "tidsperioder x tidsperioder -> tidsperioder" (typkontroll perioder1 #'tidsperioder?) (typkontroll perioder2 #'tidsperioder?) (if (tom-tidsperioder? perioder2) perioder1 (sätt-ihop-tidsperioder (lägg-in-tidsperiod perioder1 (första-tidsperiod perioder2)) (resten-tidsperioder perioder2)))) 2006-11-08 6
Följande funktion tar reda på alla lediga tidsperioder på en dagalmanacka mellan två klockslag. Specifikationen i bokningar.cl kräver att funktionen tar dessa tre argument. Annars kunde det kanske passa med en tidsperiod istället för två klockslag. (defun lediga-tidsperioder (dagalma startkl slutkl) "dagalmanacka x klockslag x klockslag -> tidsperioder" (typkontroll dagalma #'dagalmanacka?) (typkontroll startkl #'klockslag?) (typkontroll slutkl #'klockslag?) (if (före-eller-samma-klockslag? slutkl startkl) er) (let* ( (ledigtks (nästa-lediga-klockslag dagalma startkl)) (mötestid (första-mötestid-efter dagalma ledigtks))) (if (null mötestid) (if (samma-klockslag? ledigtks slutkl) er) (lägg-in-tidsperiod er) ledigtks slutkl))) (sätt-ihop-tidsperioder (lägg-in-tidsperiod er) ledigtks (start-klockslag (tidsperioddel mötestid)))) (lediga-tidsperioder dagalma (slut-klockslag (tidsperioddel mötestid)) slutkl)))))) Gränssnittsfunktion för att kolla ledighet: (defun ledigt (namn d m t1 t2) "symbol x heltal x symbol x sträng x sträng ->" (let* ( (dag (skapa-dag d)) (mån (skapa-månad m)) (start (omvandla-klockslag t1)) (slut (omvandla-klockslag t2)) (dagalma (dagalmanacka dag (månadsalmanacka mån (hämta-almanacka namn)))) (ledigaperioder (lediga-tidsperioder dagalma start slut))) (if (tom-tidsperioder? ledigaperioder) (format t "Inga lediga tider hittade!~%") (skriv-tidsperioder (lediga-tidsperioder dagalma start slut))) (values))) 2006-11-08 7
Körexempel CL-USER(54): (boka 'tomasf 14 'sep "08:00" "12:00" "Koda Lisp") Mötet är bokat. CL-USER(55): (boka 'tomasf 14 'sep "13:00" "15:00" "Koda mer Lisp") Mötet är bokat. CL-USER(56): (ledigt 'tomasf 14 'sep "08:00" "15:00") 12:00-13:00 CL-USER(57): (ledigt 'tomasf 14 'sep "07:00" "16:00") 07:00-08:00 12:00-13:00 15:00-16:00 CL-USER(58): (boka 'tomasf 14 'sep "12:00" "13:00" "Lunch. Med Lisp.") Mötet är bokat. CL-USER(59): (ledigt 'tomasf 14 'sep "08:00" "15:00") Inga lediga tider hittade! CL-USER(60): (ledigt 'tomasf 14 'sep "07:30" "15:00") 07:30-08:00 Testfunktioner Det finns ett antal specialfall man vill testa: En dag helt utan lediga perioder (fallet inga-tider) Två möten direkt efter varandra (det ska inte bli en hårfin ledig tid mellan dem) (fallet tight) Det valda intervallet börjar mitt i ett möte (fallet mitt-i) Det valda intervallet slutar mitt i ett möte (fallet mitt-i) Jag har gjort en funktion testa-ledighet som först bygger upp en mängd data som sedan kan användas i testfall. Denna kallar sedan funktionen test som utvärderar fallen. Test tar 4 argument; ett namn på fallet, ett förväntat värde, det utvärderade värdet, och ett predikat som jämför värdena. Namnet används i eventuell utskrift så det blir lättare att hitta testfallet som misslyckas. Test returnerar antingen en tom lista eller en lista med en sträng som beskriver hur testet misslyckades. Detta gör att det blir enkelt att lägga in resultatet i en lista i testa-ledighet. 2006-11-08 8
(defun testa (namn testvärde rättvärde jämförelse) "Sträng x Lisp-objekt x Lisp-objekt x (Lisp-objekt x Lisp-objekt -> sanningsvärde) -> lista" (if (funcall jämförelse testvärde rättvärde) '() (list (format nil "~a: Resultatet: ~a~%...förväntades vara: ~a.~%" namn testvärde rättvärde)))) Det blir mycket kod för att bygga upp värdena, eftersom allt byggs från grunden... (defun testa-ledighet () (setq dag1 (skapa-dagalmanacka)) (setq dag1 (lägg-in-möte (skapa-mötestid (skapa-klockslag (skapa-timme 8) (skapa-minut 00)) (skapa-klockslag (skapa-timme 12) (skapa-minut 00))) (skapa-möte "Foo")) dag1)) (setq dag1 (lägg-in-möte (skapa-mötestid (skapa-klockslag (skapa-timme 13) (skapa-minut 00)) (skapa-klockslag (skapa-timme 15) (skapa-minut 00))) (skapa-möte "Foo")) dag1)) (setq dag2 (lägg-in-möte (skapa-mötestid (skapa-klockslag (skapa-timme 12) (skapa-minut 00)) (skapa-klockslag (skapa-timme 13) (skapa-minut 00))) (skapa-möte "Foo")) dag1)) (setq dag3 (skapa-dagalmanacka)) (setq dag3 (lägg-in-möte (skapa-mötestid (skapa-klockslag (skapa-timme 8) (skapa-minut 00)) (skapa-klockslag (skapa-timme 12) (skapa-minut 00))) (skapa-möte "Foo")) 2006-11-08 9
dag3)) (setq dag3 (lägg-in-möte (skapa-mötestid (skapa-klockslag (skapa-timme 12) (skapa-minut 00)) (skapa-klockslag (skapa-timme 13) (skapa-minut 00))) (skapa-möte "Foo")) dag3)) (setq resultat1 er)) (setq resultat1 (lägg-in-tidsperiod resultat1 (skapa-klockslag (skapa-timme 12) (skapa-minut 00)) (skapa-klockslag (skapa-timme 13) (skapa-minut 00))))) (setq resultat2 er)) (setq resultat2 (lägg-in-tidsperiod resultat2 (skapa-klockslag (skapa-timme 07) (skapa-minut 00)) (skapa-klockslag (skapa-timme 08) (skapa-minut 00))))) (setq resultat2 (lägg-in-tidsperiod resultat2 (skapa-klockslag (skapa-timme 12) (skapa-minut 00)) (skapa-klockslag (skapa-timme 13) (skapa-minut 00))))) (setq resultat2 (lägg-in-tidsperiod resultat2 (skapa-klockslag (skapa-timme 15) (skapa-minut 00)) (skapa-klockslag (skapa-timme 16) (skapa-minut 00))))) (setq resultat3 er)) (setq resultat3 (lägg-in-tidsperiod resultat3 (skapa-klockslag (skapa-timme 13) (skapa-minut 00)) (skapa-klockslag (skapa-timme 15) (skapa-minut 00))))) 2006-11-08 10
Här börjar vi äntligen testa våra värden! (append (testa "vanlig-dag" (lediga-tidsperioder dag1 (skapa-klockslag (skapa-timme 8) (skapa-minut 00)) (skapa-klockslag (skapa-timme 15) (skapa-minut 00))) resultat1 #'equal) (testa "inga-tider" (lediga-tidsperioder dag2 (skapa-klockslag (skapa-timme 8) (skapa-minut 00)) (skapa-klockslag (skapa-timme 15) (skapa-minut 00))) er) #'equal) (testa "tight" (lediga-tidsperioder dag3 (skapa-klockslag (skapa-timme 8) (skapa-minut 00)) (skapa-klockslag (skapa-timme 15) (skapa-minut 00))) resultat3 #'equal) (testa "mitt-i" (lediga-tidsperioder dag1 (skapa-klockslag (skapa-timme 9) (skapa-minut 00)) (skapa-klockslag (skapa-timme 14) (skapa-minut 00))) resultat1 #'equal) (testa "extralång" (lediga-tidsperioder dag1 (skapa-klockslag (skapa-timme 7) (skapa-minut 00)) (skapa-klockslag (skapa-timme 16) (skapa-minut 00))) resultat2 #'equal) ) ) 2006-11-08 11
Körexempel Med de korrekta värdena ovan: CL-USER(76): (testa-ledighet) NIL Om man ändrar starttiden "vanlig-dag" till klockan 7, kan vi få ett fall att misslyckas: CL-USER(82): (testa-ledighet) ("vanlig-dag: Resultatet: (TIDSPERIODER (TIDSPERIOD (KLOCKSLAG (TIMME. 7) (MINUT. 0)) (KLOCKSLAG (TIMME. 8) (MINUT. 0))) (TIDSPERIOD (KLOCKSLAG (TIMME. 12) (MINUT. 0)) (KLOCKSLAG (TIMME. 13) (MINUT. 0))))...förväntades vara: (TIDSPERIODER (TIDSPERIOD (KLOCKSLAG (TIMME. 12) (MINUT. 0)) (KLOCKSLAG (TIMME. 13) (MINUT. 0)))). ") 2006-11-08 12