prompt$ polecenie [ENTER]mc Twoim przyjacielemls - list - czyli wyświetl pliki w katalogu,
ls /usr/
ls -l
ls -a
ls -al /usr/
ls -R
cd - change directory - zmień katalog
cd /usr/
cd -
pwd - print working directory - pokaż bieżący katalog (gdybyśmy się zgubili :-)cp co? gdzie? - copy - kopiuj
cp ala ula
cp ala ola ula archiwum
cp -a
mv co? gdzie? - move - zmiana nazw bądź przeniesienie plików i katalogów do innego katalogu.
mv ala ula mv ala ola ula archiwum
rm - remove - usuń plik/katalog
rm -r
rm -f
rm -i
rm -rf [OSTROŻNIE!!!!]
mkdir - make directory - utwórz katalog
mkdir ~/podkatalog
mkdir -p ~/glowny/podglowny/malutki
rmdir - remove directory - usuń katalog (tylko jeśli niepusty -- bezpieczniejsze od rm -r)
rmdir ~/podkatalog
passwd -- zmień hasło [hasło powinno być łatwe do zapamiętania i trudne do zgadnięcia]
mcedit - prosty, wbudowany w mc,joe - w stylu edytorów Borlanda, pico, nano - proste, tekstowe, gedit, kate - okienkowe
vi - jest wszędzie, tekstowy, początkowo nieintuicyjny, skomplikowany, po dokładnym poznaniu efektywny, emacs - rozbudowany, początkowo skomplikowany, po bliższym poznaniu efektywny,C:\, jest jedno drzewo plików.mount) w systemie plików ils ala.c ula.c ela.pas progs.pas cp *.c *.pas = cp ala.c ula.c ela.pas progs.pas
ln dokąd? skąd? - link - dowiązanie i częściej używane dowiązanie symboliczne
ln plik1.ocaml plik1.ml
ln -s docs Dokumenty
mount - pokazuje co gdzie jest zamontowane. df - pokazuje miejsce na dyskach.
du - zajmowane miejsce (przez plik/katalog)
du ala.avi du -s du -s *
quota - moje limity na przestrzeń dyskową (twarde i miękkie limity).
pine / mutt -- używanie poczty w konsoli.forwardPoziomy te będziemy dalej oznaczali literkami u, g, o oraz a (All).
ls -l
chmod - change mode - zmiana praw dostępu.chmod u+rwx * chmod o-r *.c chmod ug+rx Pliki
chown - change owner - zmień właścicielachgrp - change group - zmień grupęls -l > plik - wyjście do pliku,ls -l / >> plik - wyjście doklejone na końcu pliku,( echo "To jest katalog"; ls -l ) > plik - wyjscie z kilku komend do pliku - plik na początku wyzerowany,P1 | P2 | ... | Pn - przetwarzanie potokowe,P1 `P2` - wyjście P2 staje się częścią polecenia P1 (zwykle chodzi o listy plików).cat - wyświetla zawartość plików (wbrew pozorom nie katalogów!)
cat .bashrc
cat > ~/plik
cat plik1 plik2 > plik
Przydatny przy przetwarzaniu potokowym.
less plik - lepszy do oglądania plików,sort - sortuje zadany plik lub standardowe wejścieuniq - usuwa powtórzenia (serie),
sort plik > plik.posortowany cat plik | sort | uniq > plik.bez.powtórzeń
grep - wypisuje linie zawierajace zadany napis
grep main *.c cat *.c | grep main ls -l | grep drwx cat *.c | grep main | sort | uniq > mains.txt grep drwx `cat lista.plikow`
ps
ps u
ps aux
top - najwięksi pożeracze zasobów,kill - zabicie (lub wysłanie sygnału do) procesu,
kill 1234 kill -9 1234 kill -HUP 1234
killall - zabicie (lub wysłanie sygnału do) wszystkich procesów wykonujących dany program,
kill screen kill -9 firefox-bin
pstree - drzewo procesówCtrl-Z - zawiesi (suspend) proces,bg - background - uruchomi w tle (odczepi do terminala) zawieszony proces,fg - foreground - uruchomi na pierwszym planie (można też jednak zawiesić terminal jak się chce),jobs - wypisze procesy przypięte do terminala,kill %1 - poprzez % (procent) można łatwiej wybrać procesy przypięte do terminala,x="ala ma kota" - przypisanieecho $x w paski - odwołanie się do wartościx=$[y * 2 + z] - wyrażenia arytmetyczneecho $HOME $USER $MAIL $PATH - różne ciekawe zmienne.#!/bin/bash
Określa on jaki program ma interpretować polecenia.
Przydatne, jeśli chcemy napisać skrypt w języku dla jakiegoś innego interpretera:
#!/usr/bin/ocaml #!/usr/bin/perl
ls --help
man ls
info ls
opcja --help działa dla wszystkich poleceń
man polecenie - też!
info polecenie - też (wyjście: ^X^C)
.
ocaml 2+3;; ^D
(nie działają strzałki itp.)
ledit ocaml
(działają strzałki!!!)
let f x = x*x;; let ma co = "Ala ma "^co;; ma "kota";; f "kota";; (error) let g x = (f x)^(f x);; (error) let h x = f (f x);; h 7;; h (f 7);; max_int;; max_int+1;; let rec silnia n = if n=0 then 1 else n*(silnia (n-1));; let rec powtorz n s = if n=0 then "" else s^(powtorz (n-1) s);; let powt3 = powtorz 3;; let powt_ala n = powtorz n "ala";;
#use "plik.ml";;
W terminalu piszemy:
ab123456@komputer:~$ ocaml
2+3;;
^D
(nie działają strzałki itp :-( )
ab123456@komputer:~$ ledit ocaml
(dzialaja strzalki! :-)
let f x = x * x;;
let ma co = "Ala ma " ^ co;;
ma "kota";;
f "kota";;
(error)
let g x = (f x)^(f x);;
(error)
let h x = f (f x);;
h 7;;
h (f 7);;
max_int;;
max_int+1;;
let rec silnia n =
if n = 0 then 1 else n * (silnia (n-1));;
let rec powtorz n s =
if n = 0 then "" else s ^ (powtorz (n-1) s);;
let powt3 = powtorz 3;;
let powt_ala n = powtorz n "ala";;
W terminalu wpisujemy ocaml (lub lepiej ledit ocaml).
W drugim okienku uruchamiamy edytor i co chwilę kopiujemy kod myszką.
W terminalu wpisujemy ocaml (lub lepiej ledit ocaml).
W drugim okienku uruchamiamy edytor z plikiem "plik.ml", co chwilę zapisujemy plik w edytorze i wpisujemy #use "plik.ml";;
(albo lepiej wychodzimy z Ocamla, wchodzimy ponownie i wtedy robimy #use "plik.ml";;).
Uwaga: Pisząc #use należy samemu wpisać # (hash - kratkę).
Używamy edytora emacs ze wsparciem dla ocamla.
Są jeszcze inne sposoby, ale o nich powiemy jak zaczniemy pracować z
z programami podzielonymi na wiele plików.
Na końcu pliku ~/.emacs należy dopisać:
;; Konfiguracja ocaml-mode
(setq auto-mode-alist (cons '("\.ml[iylp]?$" . caml-mode) auto-mode-alist))
(require 'caml)
(require 'camldebug)
(require 'caml-font)
A jeżeli w ogóle nie ma pliku ~/.emacs, to kopiujemy go stąd.
Dodatkowe informacje o instalacji ocaml emacs, np. w domu:
http://www.cs.jhu. edu/~scott/pl/caml/emacs.html
emacsemacs poleceniem emacs plik.ml,;;W przypadku gdy chcemy rozpocząć pracę w ocamlu od nowa:
| Załącznik | Wielkość |
|---|---|
| emacs. | 846 bajtów |
Ocaml bardzo silnie wspiera programowanie oparte o kontrakty (ang. Design by contract).
Rzeczywiście, każdy większy program w Ocamlu podzielony jest na moduły, z których każdy składa się ze specyfikacji
(czyli kontraktu, umieszczonego w pliku *.mli) i implementacji (w pliku *.ml).
Przykładowo, plik.mli może wyglądać tak:
type typ
val wartosc : typ
val operacja : typ -> typ
val string_of_typ : typ -> string
Odpowiadający mu plik.ml
może wyglądać tak:
let wartosc = 7
let operacyjka x = x + 1
let operacja x = operacyjka (operacyjka x)
type typ = int
let string_of_typ = string_of_int
Aby sprawdzić, że implementacja spełnia swoją specyfikację, należy najpierw skompilować plik.mli:
ocamlc -c plik.mli
O ile nie ma błędu, wyprodukowany zostanie plik.cmi.
Teraz można skompilować plik.ml:
ocamlc -c plik.ml
Ta komenda zakończy się powodzeniem, czyli wyprodukowaniem pliku plik.cmo, tylko jeśli wcześniej został
skompilowany plik.mli i implementacja rzeczywiście odpowiada specyfikacji.
Teraz w drugi_plik.ml
można skorzystać z poprzedniego modułu w następujący sposób:
let x = Plik.wartosc
let y = Plik.operacja x
open Plik
let z = operacja y
let _ = print_endline (string_of_typ z)
Zauważmy, że drugi_plik.ml nie ma pojęcia, że typ i int to to samo, ani że
istnieje coś takiego, jak operacyjka.
Istotnie, nie ma tego w specyfikacji modułu "Plik".
Aby skompilować drugi_plik.ml należy wykonać:
ocamlc -c drugi_plik.ml
Otrzymamy plik drugi_plik.cmo.
Aby uzyskać gotowy program wykonywalny (to tzw. linkowanie) należy wykonać:
ocamlc -o test plik.cmo drugi_plik.cmo
Teraz można sprawdzić, że działa:
./test
Co zostanie wypisane?
Wszystkie powyższe pliki można pobrać w postaci archiwum zip.
Warto w tym miejscu zapamiętać, że nazwy modułów piszemy w Ocamlu zawsze wielką literą, a nazwy
odpowiadających im plików zwyczajowo pisane są małymi literami.
Skompilowanych modułów można także używać wewnątrz interaktywnego środowiska Ocamla (tego
uruchamianego poleceniem ocaml, lub pod emacsem).
Przy pierwszym użyciu nazwy modułu Plik, ładowany jest automatycznie odpowiadający mu plik.cmi,
zawierający informacje o typach komponentów modułu.
Nie jest jednak ładowany kod samego modułu (plik.cmo), dlatego każda próba użycia komponentów
kończy się komunikatem o błędzie:
# Plik.wartosc;;
Reference to undefined global `Plik'
Plik.cmo ładowany jest tylko na wyraźne życzenie użytkownika.
Można do tego celu użyć komendy #load:
# #load "plik.cmo";;
Uwaga: pierwszy # (krzyżyk/hash) jest znakiem zachęty środowiska, drugi musimy wprowadzić samemu, podobnie
jak w przypadku komendy #use czy #quit.
Powyższa komenda dołącza skompilowany moduł Plik do środowiska.
Po tej komendzie komponentów modułu Plik można używać za pomocą prefiksu, np.:
Plik.string_of_typ Plik.wartosc;;
albo bez prefiksu, po uprzednim wykonaniu komendy open Plik:
open Plik;;
string_of_typ wartosc;;
Można również wydać polecenie załadowania skompilowanego modułu przy starcie środowiska:
ocaml plik.cmo
Można też stworzyć własną wersję środowiska z załadowanym plikiem:
ocamlmktop plik.cmo -o moj_toplevel
I potem używać jej zamiast standardowego środowiska:
./moj_toplevel
lub
ledit ./moj_toplevel
Oczywiście w przypadku jednego modułu, z tworzenia własnej wersji środowiska nie mamy wielkiego zysku.
| Załącznik | Wielkość |
|---|---|
| plik.mli | 90 bajtów |
| plik.ml | 135 bajtów |
| drugi_plik.ml | 119 bajtów |
| moduly.zip | 537 bajtów |
Celem tego laboratorium jest przećwiczenie operacji na listach.
Środkiem do tego celu będą operacje na wielomianach symbolicznych.
Wielomiany reprezentujemy jako listę współczynników (typu float) od końca,
np. lista [3.; 5.; 7.] oznacza wielomian \(7x^2 + 5x + 3\).
Zanim przystąpimy do dalszego ciągu należy zdecydować, czy lista może być pusta, czy może kończyć się zerem itp.
Po podjęciu decyzji należy ją zapisać w komentarzu do definicji typu oraz się jej trzymać, tzn.:
Należy zaimplementować następujące rzeczy:
type wielomian = ...
val oblicz : wielomian -> float -> float
(** [oblicz w x] oblicza wartość wielomianu [w] w punkcie [x] *)
val suma : wielomian -> wielomian -> wielomian
(** Suma wielomianów *)
val iloczyn : wielomian -> wielomian -> wielomian
(** Iloczyn wielomianów *)
val pochodna : wielomian -> wielomian
(** Pochodna wielomianu *)
val stopien : wielomian -> int
(** Zwraca stopień wielomianu (czyli najwyższą potęgę argumentu, przy której wykładnik jest różny od 0.) *)
val calka : wielomian -> wielomian
(** Calka wielomianu. [calka w] ma wyraz wolny równy 0. *)
Dobrze by było napisać też kilka generatorów większych wielomianów, np. jed n zwraca
\(x^n + x^{n-1} + \dots + 1\), jed_zer n zwraca \(x^{2n} + x^{2n-2} + \dots + x^2 + 1\) itp.
Należy zwrócić uwagę na złożoność operacji oraz czy implementacja jest ogonowa, czy nie
(o ile ma to wpływ na złożoność pamięciową) i zapisać te spostrzeżenia w komentarzu.
Wielomiany rzadkie, jak sama nazwa wskazuje, mają tylko niektóre współczynniki różne od zera, np. \(x^{1000} + 5x^{50} + 2x^{10} + 1\).
Ponieważ szkoda byłoby miejsca na przechowywanie takich wielomianów w sposób przedstawiony powyżej, będziemy reprezentować
je jako listę par (wykładnik,współczynnik) posortowaną wg rosnących wykładników.
Nasz przykładowy wielomian reprezentowany będzie przez [(0,1.); (10,2.); (50,5.); (1000;1.)].
Dla tej reprezentacji należy zaimplementować osobny zestaw narzędzi, składający się z definicji typu:
type rzadki = ...
i tych samych funkcji co powyżej, przy czym do wszystkich nazw należy dołączyć sufiks _rz (np. oblicz_rz),
a we wszystkich typach należy zastąpić wielomian przez rzadki.
W tym zadaniu również należy najpierw zastanowić się, czy (niektóre) współczynniki mogą być równe 0., jak
reprezentowany jest wielomian stale równy zero itp.
Należy zaimplementować funkcje konwertujące wielomiany zwykłe na rzadkie i z powrotem:
val rozrzedz : wielomian -> rzadki
(** Konwertuje wielomian do postaci rzadkiej usuwając wszystkie współczynniki równe 0. *)
val zagesc : rzadki -> wielomian
(** Kowertuje wielomian do postaci zwyklej dodajac (o ile trzeba) wyrazy o współczynnikach równych 0. *)
Do powyższych zestawów funkcji operujących na wielomianach dopisać funkcję przybliżającą miejsca zerowe wielomianu
(funkcja ta powinna zwracać listę wartości typu float).
Wskazówka: jak mają się miejsca zerowe wielomianu do miejsc zerowych jego pochodnej?
Narzędzie make służy do automatycznej kompilacji projektów programistycznych.
Historycznie make został oczywiście stworzony dla programów w języku C, ale dzięki swojej elastyczności
może być z powodzeniem stosowany do kompilacji projektów w innych językach oraz w dowolnych innych sytuacjach,
gdy mamy do czynienia z wielokrotnym automatycznym przerabianiem na różne sposoby pewnej liczby różnorodnych plików.
Do sterowania działaniem programu make służy plik o nazwie Makefile.
W przypadku przykładowego projektu z jednego z poprzednich laboratoriów składającego się z modułów
plik i
drugi_plik, w najbardziej
bezpośredniej i podstawowej wersji plik Makefile
mógłby wyglądać tak:
test: plik.cmo drugi_plik.cmo
ocamlc -o test plik.cmo drugi_plik.cmo
plik.cmi: plik.mli
ocamlc -c plik.mli
plik.cmo: plik.ml
plik.cmi ocamlc -c plik.ml
drugi_plik.cmo: drugi_plik.ml plik.cmo
ocamlc -c drugi_plik.ml
clean: rm *.cmi *.cmo
Aby użyć tego przepisu, należy po prostu wydać polecenie make, oczywiście wcześniej upewniwszy się, że jesteśmy w katalogu, w
którym znajdują się pliki plik.ml,
plik.mli,
drugi_plik.ml i
Makefile
(można je pobrać w postaci pliku zip).
Program make sam zorientuje się w jakiej kolejności należy wykonywać poszczególne polecenia i w wyniku
(o ile nie będzie błędów) otrzymamy gotowy plik test.
Jak widać plik Makefile
składa się z pewnej liczby przepisów, postaci:
cel: zależności
instrukcje
Cel zwykle jest nazwą pliku, który tworzy się za pomocą instrukcji, zwykle wykorzystując do tego
zależności (czyli inne cele, zwykle także pliki).
Lista zależności, jak i instrukcje mogą być puste, a dla jednego celu może być tylko jeden przepis z niepustą instrukcją.
Uwaga natury składniowej: instrukcje muszą być poprzedzone prawdziwym znakiem tabulacji (a nie zwykłymi odstępami!)
Make wywołany przez make nasz_cel działa w ten sposób, że najpierw buduje sobie drzewko zależności między
celami i następnie stara się zrealizować nasz_cel jak najtańszym kosztem.
Decyduje, czy należy daną instrukcję wykonać porównując datę pliku cel, z datą plików zależności.
Oczywiście po wykonaniu jednej instrukcji może zaistnieć konieczność wykonania innych i make to właśnie zrobi we właściwej kolejności.
Wywołanie make bez parametrów spowoduje próbę wykonania celu z pierwszego przepisu.
Zazwyczaj jest to cel o nazwie all.
Jak widać na przykładzie celem nie musi być plik, ale również dowolne hasło, np. clean.
Po wydaniu polecenia make clean, ponieważ plik clean nie występuje na dysku, więc make
zorientuje się, że wykonanie instrukcji jest konieczne i zgodnie z naszymi intencjami wyczyści wszystkie pośrednie efekty kompilacji.
Plik clean oczywiście nie zostanie stworzony, więc przy następnym wywołaniu make clean, instrukcja znów się wykona.
Oczywiście pisanie takiej ilości tekstu dla tak prostego projektu byłoby bardzo niewygodne.
Na szczęście większą część pliku Makefile można napisać w taki sposób, żeby pasowała
nie tylko do naszego konkretnego projektu, ale także do wszystkich podobnych.
Oto bardziej zaawansowana i uniwersalna wersja pliku
Makefile:
test: plik.cmo drugi_plik.cmo
ocamlc -o $@ $^
%.cmo: %.ml
ocamlc -c $<
%.cmi: %.mli
ocamlc -c $<
clean: rm *.cmi *.cmo
depend:
ocamldep *.ml *.mli > .depend
include .depend
W powyższym pliku, po pierwszym przepisie na plik test, zamiast przepisów do kompilacji poszczególnych
plików występuje po jednym przepisie na kompilacje plików jednego rodzaju.
Po przepisie na test, kolejny przepis mówi jak dostać plik o rozszerzeniu .cmo z pliku
(którego nazwa ma ten sam rdzeń) o rozszerzeniu .ml.
W instrukcjach występują tajemnicze znaczki $<, $^ i $@.
Oznaczają one odpowiednio: pierwszą z zależności ($<), wszystkie zależności ($^) i cel ($@) reguły.
Podobny przepis opisuje kompilację plików .mli do .cmi.
Zauważmy, że w tym Makefile'u nie podaliśmy zależności pomiędzy plikami.
Istotnie, zależności obliczone są automatycznie przez program ocamldep i zapisane
w pliku .depend, który jest przez make włączany do Makefile'a.
Z tego powodu przed pierwszym uruchomieniem make, gdy nie ma jeszcze pliku .depend,
należy go utworzyć z dowolną zawartością, choćby pustą, np. poleceniem touch .depend, a następnie
wykonać make depend.
Dopiero teraz jesteśmy gotowi do prawdziwej kompilacji (make test lub make).
Zauważmy, że po każdej większej zmianie projektu, tzn. takiej w której zmienia się graf zależności
między plikami należy na nowo ręcznie wykonać make depend.
Makefile podany poniżej,
oprócz wszystkich rzeczy opisanych wcześniej, sam tworzy sobie plik .depend (o ile ten plik nie istnieje),
umożliwia dodanie flag do komendy ocamlc dla wszystkich jego wywołań, umożliwia wywołanie kompilatora do kodu
natywnego (czyli użycie komendy ocamlopt zamiast ocamlc do kompilacji) oraz automatyczne
wygenerowanie dokumentacji (aby ją obejrzeć należy zrobić make doc; firefox doc/index.html).
# The base name of the executable
PROG=test
# The list of object files
CMOS=pierwszy.cmo lewy.cmo prawy.cmo ostatni.cmo
CMXS=$(CMOS:.cmo=.cmx)
# Choose your target: bytecode or native
$(PROG): $(PROG).byte
cp $< $@
# Special rule to create pierwszy.ml
pierwszy.ml: pierwszy.txt
echo "let tekst=\"\\" > $@
cat $< >> $@
echo '"' >> $@
OCAMLC=ocamlc
OCAMLOPT=ocamlopt
OCAMLDEP=ocamldep
OCAMLDOC=ocamldoc
INCLUDES= # all relevant -I options here
OCAMLFLAGS=$(INCLUDES) # add other options for ocamlc here
OCAMLOPTFLAGS=$(INCLUDES) # add other options for ocamlopt here
$(PROG).byte: $(CMOS)
$(OCAMLC) $(OCAMLFLAGS) -o $@ $^
$(PROG).native: $(CMXS)
$(OCAMLOPT) $(OCAMLOPTFLAGS) -o $@ $^
.SUFFIXES: .ml .mli .cmo .cmi .cmx
%.cmo: %.ml
$(OCAMLC) $(OCAMLFLAGS) -c $<
%.cmx: %.ml
$(OCAMLOPT) $(OCAMLOPTFLAGS) -c $<
%.cmi: %.mli
$(OCAMLC) $(OCAMLFLAGS) -c $<
.FORCE:
doc: .FORCE
mkdir -p doc
$(OCAMLDOC) -html -d doc *.mli *.ml
clean:
rm -f *.cm[iox] *.o
.depend:
$(OCAMLDEP) $(INCLUDES) *.mli *.ml > $@
depend:
rm .depend
$(MAKE) .depend
include .depend
Oczywiście, aby dopasować ten Makefile do innego projektu wystarczy zmienić pierwsze
dwie definicje (PROG i CMOS).
Przykładową zmianę z dodanym niestandardowym przepisem tworzenia jednego z plików .ml można
zobaczyć tu.
Podobny (choć gorszy) uniwersalny Makefile dla projektów ocamlowych znajduje się w dokumentacji do
Ocamla, w rozdziale 13, poświęconym generatorowi zależności ocamldep:
http://caml.inria.fr/pub/docs/manual-ocaml/manual027.html#htoc148
Oto naprawdę potężny Makefile do projektów w Ocamlu, który (prawie) wszystko robi sam:
http://hg.ocaml.info/release/ocaml-make/file/d4e96574afbe/OCamlMakefile
Jego autorem jest Markus Mottl, a tu jest opis:
http://www.ocaml.info/home/ocaml_sources.html#ocaml-make
Główna wada tego narzędzia jest taka jak wszystkich skomplikowanych narzędzi, które starają się być bardzo domyślne:
jeśli wszystko działa dobrze -- jesteśmy szczęśliwi; ale jeśli pojawi się najmniejszy problem, nie mamy
pojęcia jak go naprawić, bo nie wiemy jak działa nasze skomplikowane narzędzie.
Pełna dokumentacja programu make znajduje się np. na stronach GNU:
http://www.gnu.org/software/make/manual/
To samo można również znaleźć za pomocą komendy info make lub spod emacsa: M-x info ENTER m make ENTER.
| Załącznik | Wielkość |
|---|---|
| przyklad.zip | 2.28 KB |
| Makefile-3. | 1012 bajtów |
Tam gdzie dokonujemy pomiarów wielkości fizycznych, wyniki są obarczone pewnym błędem, np. 5m ± 10%.
Każdą taką przybliżoną wartość traktujemy jak zbiór możliwych wartości.
Zaimplementuj pakiet operacji arytmetycznych na takich przybliżonych wartościach zawierający:
Zakładamy przy tym implicite, że wszystkie argumenty typu float są liczbami rzeczywistymi
(tzn. są różne od infinity, neg_infinity i nan).
Natomiast w przypadku, gdy wynik nie jest liczbą rzeczywistą, powinien być odpowiednią z wartości: infinity, neg_infinity lub nan).
Przyjmij, że modyfikatory domykają wynikowe zbiory wartości, tzn. jeżeli, na przykład, wynikiem jest
przedział otwarty, to przyjmij, że zostaje on zamieniony na przedział domknięty.
Twoje rozwiązanie ma być umieszczone w pliku o nazwie arytmetyka.ml i pasować do specyfikacji
interfejsu arytmetyka.mli.
Uwaga:
arytmetyka.ml,| Załącznik | Wielkość |
|---|---|
| wpf-0-arytest.ml | 8.56 KB |
| wpf-0-arytmetyka.mli | 1.53 KB |
Drzewa lewicowe to ciekawa implementacja złączalnych kolejek priorytetowych.
Kolejki priorytetowe to struktury przechowujące dane obdarzone priorytetami, umożliwiające łatwy dostęp do elementu o najwyższym priorytecie.
(Tradycyjnie, im mniejsza liczba reprezentująca priorytet, tym wyższy priorytet. ;-)
Struktury te dostarczają następujących operacji:
Oczywiście po usunięciu elementu o najwyższym priorytecie, drugi w kolejności staje się tym najwyższym itd.
Kolejki złączalne umożliwiają dodatkowo łączenie dwóch kolejek w jedną.
Kolejki priorytetowe implementuje się zwykle za pomocą tzw. kopców, czyli struktur drzewiastych, które
spełniają tzw. warunek kopca, mówiący, że priorytet elementu zawartego w korzeniu każdego poddrzewa
jest mniejszy lub równy niż każdego innego elementu w tym poddrzewie.
Drzewa lewicowe to kopce binarne (czyli każdy węzeł może mieć 0, 1 lub dwóch potomków)
spełniające, oprócz warunku kopca, tzw. warunek lewicowości.
Warunek lewicowości mówi, że dla każdego węzła skrajnie prawa ścieżka zaczynająca się w danym
węźle jest najkrótszą ścieżką od tego węzła do liścia.
Dzięki temu w każdym drzewie lewicowym, tzw. prawa wysokość, czyli długość skrajnej prawej
ścieżki od korzenia do liścia, jest co najwyżej logarytmicznej wielkości, w porównaniu z liczbą elementów drzewa.
Dodatkowo, aby umożliwić efektywne wykonywanie operacji na drzewie, w każdym węźle przechowywana
jest prawa wysokość poddrzewa zaczepionego w tym węźle.
Najważniejszą operacją na drzewach lewicowych jest ich łączenie.
Pozostałe operacje wykonuje się bardzo prosto:
Łączenie drzew lewicowych też nie jest trudne.
Aby połączyć dwa niepuste drzewa lewicowe, ustawiamy jako pierwsze (\(d_1\)) to, które ma mniejszy
element w korzeniu, a jako drugie (\(d_2\)) to, które ma większy.
W korzeniu wynikowego drzewa na pewno będzie więc korzeń \(d_1\).
Teraz rekurencyjnie łączymy prawe poddrzewo \(d_1\) oraz całe drzewo \(d_2\), w wyniku dostając drzewo \(d_3\).
Jako wynik całej operacji łączenia \(d_1\) i \(d_2\) zwracamy drzewo \(d_4\), które wygląda następująco:
Korzeń \(d_4\) jest taki sam, jak korzeń \(d_1\).
Jedno z poddrzew \(d_4\) to lewe poddrzewo \(d_1\), a drugie to drzewo \(d_3\).
Przy tym prawym poddrzewem \(d_4\) zostaje to z nich, które ma mniejszą prawą wysokość.
Dzięki temu \(d_4\) pozostaje drzewem lewicowym.
Oczywiście przy konstrukcji drzewa \(d_4\) należy pamiętać o odpowiednim ustawieniu prawej wysokości.
Rysunkową wersję łączenia drzew lewicowych można zobaczyć np. tu:
http://www.cise.ufl.edu/~sahni/cop5536/sliddes/lec114.pdf.
W naszym zadaniu dla uproszczenia zakładamy, że dane składają się z samych priorytetów.
Używając drzew lewicowych zaimplementuj złączalną kolejkę priorytetową z następującymi operacjami:
type 'a queue
(** Typ złączalnej kolejki priorytetowej *)
val empty : 'a queue
(** Pusta kolejka priorytetowa *)
val add : 'a -> 'a queue -> 'a queue
(** [add e q] zwraca kolejkę powstałą z dołączenia elementu [e] do kolejki [q] *)
exception Empty
(** Wyjątek podnoszony przez [delete_min] gdy kolejka jest pusta *)
val delete_min : 'a queue -> 'a * 'a queue
(** Dla niepustej kolejki [q], [delete_min q] zwraca parę [(e,q')] gdzie [e]
jest elementem minimalnym kolejki [q] a [q'] to [q] bez elementu [e].
Jeśli [q] jest puste podnosi wyjątek [Empty]. *)
val join : 'a queue -> 'a queue -> 'a queue
(** [join q1 q2] zwraca złączenie kolejek [q1] i [q2] *)
val is_empty : 'a queue -> bool
(** Zwraca [true] jeśli dana kolejka jest pusta. W przeciwnym razie [false] *)
(specyfikacja ta znajduje się również w pliku leftist.mli)
Termin oddania zadania ustala prowadzący — 2 tygodnie od ogłoszenia.
| Załącznik | Wielkość |
|---|---|
| wpf-1-leftist.mli | 801 bajtów |
Zaimplementuj bibliotekę dla fanów origami do badania ile warstw ma w danym punkcie sprytnie poskładana kartka papieru.
Biblioteka powinna implementować interfejs origami.mli, podany poniżej
type point = float * float
(** Punkt na płaszczyźnie *)
type kartka = point -> int
(** Poskładana kartka: ile razy kartkę przebije szpilka wbita w danym punkcie *)
val prostokat : point -> point -> kartka
(** [prostokat p1 p2] zwraca kartkę, reprezentującą domknięty prostokąt
o bokach równoległych do osi układu współrzędnych i lewym dolnym rogu [p1]
a prawym górnym [p2]. Punkt [p1] musi więc być nieostro na lewo i w dół
od punktu [p2]. Gdy w kartkę tę wbije się szpilkę wewnątrz
(lub na krawędziach) prostokąta, kartka zostanie przebita 1 raz,
w pozostałych przypadkach 0 razy *)
val kolko : point -> float -> kartka
(** [kolko p r] zwraca kartkę, reprezentującą kółko domknięte o środku w punkcie [p] i promieniu [r] *)
val zloz : point -> point -> kartka -> kartka
(** [zloz p1 p2 k] składa kartkę [k] wzdłuż prostej przechodzącej przez
punkty [p1] i [p2] (muszą to być różne punkty). Papier jest składany
w ten sposób, że z prawej strony prostej (patrząc w kierunku od [p1] do [p2])
jest przekładany na lewą. Wynikiem funkcji jest złożona kartka. Jej
przebicie po prawej stronie prostej powinno więc zwrócić 0.
Przebicie dokładnie na prostej powinno zwrócić tyle samo,
co przebicie kartki przed złożeniem. Po stronie lewej -
tyle co przed złożeniem plus przebicie rozłożonej kartki w punkcie,
który nałożył się na punkt przebicia. *)
val skladaj : (point * point) list -> kartka -> kartka
(** [skladaj [(p1_1,p2_1);...;(p1_n,p2_n)] k = zloz p1_n p2_n (zloz ... (zloz p1_1 p2_1 k)...)]
czyli wynikiem jest złożenie kartki [k] kolejno wzdłuż wszystkich prostych
z listy *)
Rozwiązując to zadanie powinieneś skorzystać ze standardowych procedur wyższych rzędów przetwarzających listy.
W przeciwnym przypadku Twoje rozwiązanie zostanie ocenione trochę niżej.
Termin wykonania zadania ustala prowadzący -- 2 tygodnie po ogłoszeniu
| Załącznik | Wielkość |
|---|---|
| wpf-2-origami.mli | 1.62 KB |
Zadanie polega na zmodyfikowaniu biblioteki zbiorów pojedynczych elementów zaimplementowanych jako drzewa AVL (drzewa BST z wyważaniem).
Dzięki wyważaniu wysokość drzewa jest zawsze rzędu logarytm z liczby wierzchołków i dlatego wszystkie operacje wykonywane są w czasie
logarytmicznym (nawet operacja split, ale to jest trochę mniej oczywiste: wynika z tego, że koszt join jest w istocie
proporcjonalny do różnicy wysokości drzew, które łączymy.
A ponieważ na split składa się ciąg operacji join na coraz wyższych drzewach, ich koszty sumują się do wysokości drzewa razy pewna stała).
Wynikiem modyfikacji ma być biblioteka zbiorów liczb całkowitych oparta o przedziały.
Czyli elementami występującymi w drzewie muszą być przedziały, a nie pojedyncze liczby.
Przedziały muszą być rozłączne i w dodatku, aby uniknąć niejednoznaczności reprezentacji, przedziały w drzewie nie mogą być "sąsiednie",
czyli np. dwa przedziały [1..3] i [4..6] powinny być zastąpione przez jeden przedział [1..6].
W naszej bibliotece dopuszczamy przedziały jednoelementowe, np. [3..3].
Wszystkie operacje (poza fold, iter, elements oraz is_empty) mają wykonywać się w czasie \(O(\log n)\), gdzie \(n\) jest liczbą wierzchołków w drzewie.
Do zadania dołączona jest oryginalna specyfikacja i implementacja zbiorów oraz specyfikacja zbiorów przedziałów, której implementację należy przesłać jako plik o nazwie iSet.ml.
Komplet plików można też pobrać jako archiwum zip.
Termin oddania zadania ustala prowadzący — 2 tygodnie od ogłoszenia.
| Załącznik | Wielkość |
|---|---|
| wpf-3-pSet.mli | 2.72 KB |
| wpf-3-pSet.ml | 4.51 KB |
| wpf-3-iSet.mli | 2.75 KB |
| wpf-3-modyfikacja.zip | 4.39 KB |
Sortowanie topologiczne polega na rozszerzeniu skończonego częściowego porządku do porządku liniowego.
Mówiąc prościej, mając dany DAG (czyli graf skierowany bez cykli) należy przypisać wierzchołkom takie różne liczby naturalne (nadające kolejność tym wierzchołkom), żeby dla każdej krawędzi grafu jej źródło miało niższy numer niż jej cel.
Mówiąc jeszcze prościej, mając daną częściową informację o zależności, powiedzmy, czynności od siebie (np. buty wkładamy po skarpetkach, krawat po koszuli itp. ale kolejność wkładania skarpetek i koszuli może być dowolna) mamy wygenerować ścisłą kolejność wykonywania czynności (np. koszula, skarpetki, buty, krawat).
Konkretnie, należy zaprogramować implementację topol.ml załączonej specyfikacji topol.mli.
W implementacji można korzystać z modułu pMap (bardzo podobnego do pSet z poprzedniego zadania), którego specyfikacja i implementacja również są załączone. Wszystkie potrzebne pliki można także pobrać w postaci archiwum zip.
Termin oddania zadania ustala prowadzący — 2 tygodnie od ogłoszenia.
| Załącznik | Wielkość |
|---|---|
| wpf-4-topol.mli | 436 bajtów |
| wpf-4-pMap.mli | 3.39 KB |
| wpf-4-pMap.ml | 4.58 KB |
| wpf-4-topol.zip | 3.47 KB |
Slajdy do wykładów:
W poniższych zadaniach zdefiniuj opisane funkcje matematyczne.
Możesz, a nawet powinieneś, zdefiniować przydatne funkcje pomocnicze.
Opisz też sposób reprezentacji danych (np.\ punkt możemy reprezentować jako parę współrzędnych).
Rozwiązania poniższych zadań to proste procedury operujące na liczbach całkowitych.
Dla każdej procedury rekurencyjnej podaj jej specyfikację, tj. warunek wstępny i końcowy,
oraz uzasadnij jej poprawność.
Poprawność procedur rekurencyjnych można pokazać przez indukcję.
Napisz procedurę parzystość wyznaczającą stopień parzystości danej liczby całkowitej.
sqrt obliczającej sqrt x \( = \lfloor\sqrt{x}\rfloor\)rzadkie : int → int, która dla danej liczby naturalnej \(n\) zwróci najmniejszą rzadką liczbę naturalną większą od \(n\). Na przykład, dla \(n = 42 = 101010_2\) mamy \(\mathtt{rzadkie}\ 42 = 1000000_2 = 64\).
rzadkie : int → int, która dla danej liczby naturalnej \(n\) wyznaczy liczbę dodatnich liczb rzadkich, które nie przekraczają \(n\).rzadkie 42 = 20.
Rozwiązania poniższych zadań to proste procedury operujące na liczbach całkowitych.
Ich rozwiązanie wymaga zastosowania rekurencji ogonowej.
Dla każdej procedury rekurencyjnej podaj jej specyfikację, tj. warunek wstępny i końcowy,
oraz uzasadnij jej poprawność.
Poprawność procedur rekurencyjnych można pokazać przez indukcję.
Nie zapomnij o uzasadnieniu własności stopu.
p, że \(\mathtt{p}\ f\ n = b - a + 1\) (dla \(a\) i \(b\) jak wyżej).
Ćwiczenie programistyczne na listy:
last, której wynikiem jest ostatni element listy. head i tail, które dla zadanej listy \(l\) i liczby całkowitej \(n\)head i tail w Uniksie.)tail (head l n) 1?
shuffle: α list → α list → α list, która dla danych dwóch list postaci \([x_1; x_2; \dots; x_n]\) oraz \([y_1; y_2; \dots; y_m]\) wyznaczy listę postaci \([x_1; y_1; x_2; y_2; \dots]\). Jeżeli jedna z list jest dłuższa, to jej końcowe elementy trafiają na koniec listy wyikowej.shuffle [3; 2; 8; 1; 9; 3; 6] [5; 7; 0] = [3; 5; 2; 7; 8; 0; 1; 9; 3; 6].
Napisz procedurę tails : α list → α list list,
która dla danej listy tworzy listę wszystkich jej sufiksów, uporządkowaną wg ich długości (wybrać, czy rosnąco, czy malejąco).
podziel : int list → int list list, która dla danej listy postaciPrzykład: \(\mathtt{podziel}\, [2; 3; 1; 6; 5; 4; 7; 9; 10; 11; 8] = [[2; 3; 1]; [6; 5; 4]; [7]; [9; 10; 11; 8]]\).
trojki : int list → (int} * int * int) list,malo [-42, -12, -8, -1, -1, 5, 15, 60] = 2.
Możesz założyć, że dana lista \(l\) zawiera przynajmniej dwa elementy.
Rozpatrujemy tabele, których wiersze są uporządkowane ściśle rosnąco, a kolumny ściśle malejąco.
Napisz procedurę znajdz : int list list → int → (int * int) list, która znajduje wszystkie pozycje, na
jakich w takiej tabeli znajduje się podana liczba.
Napisz procedurę reprezentacja : int → int list, która dla danej liczby całkowitej
znajdzie jej przedstawienie w systemie o podstawie -2.
Lista wynikowa powinna zawierać cyfry w kolejności od mniej do bardziej znaczących.
Przyjmujemy, że lista pusta reprezentuje 0.
Napisz procedurę dekompresuj : int list → int list dekompresującą zadaną listę.
Możesz założyć, że lista skompresowana nie zawiera zer.
Podaj specyfikację (warunek początkowy i końcowy) wszystkich definiowanych procedur i uzasadnij zwięźle ich pełną poprawność.
W przypadku rekurencyjnych procedur iteracyjnych warunek początkowy musi zawierać niezmiennik iteracji.
dekompresuj [1; 3; 3; 9; 42; 3];;
- : int list = [1; 2; 2; 5; 11; 11; 2]
palindrom : α list → int,podziel : int → α list → α list list,Przykład: \(\mathtt{podziel}\ 3\ [1;2;3;4;5;6;7] = [[1; 4; 7]; [2; 5]; [3; 6]] \).
Jaś wymyślił sobie następującą zabawę.
Mając do dyspozycji pewien zestaw krążków zastanawia się, na jakiej głębokości zatrzymałby się ostatni z nich,
gdyby wrzucać je kolejno do rurki centralnie (czyli dokładnie w jej środek).
Każdy kolejny krążek po wrzuceniu spada dopóki się nie zaklinuje (czyli nie oprze się o walec, w którym wycięty jest
otwór o mniejszej średnicy niż średnica krążka), albo nie natrafi na przeszkodę w postaci innego krążka lub dna rurki.
Napisz procedurę krążki : int list → int list → int, która na podstawie listy średnic
otworów w kolejnych walcach tworzących rurkę, oraz listy średnic kolejno wrzucanych krążków obliczy głębokość, na której zatrzyma się
ostatni wrzucony krążek (lub 0 jeśli nie wszystkie krążki zmieszczą się w rurce).
mnóż : float list → float list → float list mnożącą wielomiany.
Uwaga: Pusta lista reprezentuje zerowy wielomian.
Ćwiczenie programistyczne na drzewa i inne struktury danych:
type α tree =
Node of α tree * α * α tree |
Leaf;;
Drzewo nazywamy ultralewicowym jeśli głębokości kolejnych liści (Leaf), od lewej do prawej, tworzą ciąg nierosnący.
Napisz procedurę ultraleft : α tree → bool, która sprawdza czy dane drzewo jest ultralewicowe.
type α tree =
Node of α tree * α * α tree |
Nil;;
Napisz procedurę liscie : α tree → α list, która tworzy listę wartości znajdujących się w liściach (Node) drzewa.
type expr =
And of expr * expr |
Or of expr * expr |
Not of expr |
Value of bool;;
Warianty And, Or i Not reprezentują odpowiednie operacje logiczne,
natomiast wariant Value reprezentuje wartość logiczną true lub false.
Napisz funkcję wartościowania : expr → int,
która dla danego wyrażenia policzy na ile różnych sposobów można przypisać wszystkim
wariantom Value wartości logiczne, tak aby wartość całego wyrażenia wyliczała się do true.
Przykład: wartościowania Or(Value true, Value false) = 3,
gdyż wyrażenie wyliczy się do true, w trzech przypadkach:
Or(Value true, Value true), Or(Value true, Value false), Or(Value false, Value true).
type 'a tree =
Node of 'a * 'a tree * 'a tree |
Leaf;;
Skosokość drzewa jest zdefiniowana w następujący sposób:
Leaf wynosi 0, Dla danego drzewa wolno nam w dowolnych jego węzłach zamieniać miejscami lewe i prawe poddrzewa.
Napisz procedurę \(\mathtt{skosokosc}: \alpha\ \mathtt{tree} \to \alpha\ \mathtt{tree}\), która przekształca dane drzewo tak, aby zminimalizować jego skosokość.
Na przykład, dla:
t = Node(5,
Node(4, Leaf, Node(2, Leaf, Leaf)),
Node(6,
Node(1, Leaf, Leaf),
Node(3, Leaf, Leaf)
)
)
skosokosc t = Node(5,
Node(6,
Node(1, Leaf, Leaf),
Node(3, Leaf, Leaf)
),
Node(4, Node(2, Leaf, Leaf), Leaf)
)
Skosokość tak uzyskanego drzewa wynosi 5.
type tree = Node of (int * tree list);;
Każdy pracownik charakteryzuje się pewną "towarzyskością" wyrażoną liczbą dodatnią.
Napisz program, który dobierze gości tak, aby:
Jak zapewnić, aby szef firmy był na przyjęciu?
type tree =
Node of tree * tree |
Leaf
.
Odległością między dwoma wierzchołkami (Node) w drzewie nazywamy minimalną
liczbę krawędzi jakie trzeba przejść z jednego wierzchołka do drugiego.
Średnicą drzewa nazwiemy maksymalną odległość między dwoma węzłami (Node) w drzewie.
Przyjmujemy, że średnica pustego drzewa (Leaf) jest równa 0.
Napisz procedurę średnica : tree → int, która oblicza średnicę danego drzewa.
type drzewo =
Node of α * α drzewo * α drzewo |
Leaf
Napisz procedurę środek : α drzewo → α drzewo, która znajduje w zadanym drzewie taki
węzeł (Node), dla którego maksymalna spośród jego odległości od liści jest jak najmniejsza.
Wynikiem tej procedury powinno być poddrzewo zakorzenione w wyznaczonym węźle.
Co się zmieni, jeżeli będziemy chcieli znaleźć wierzchołek, dla którego
minimalna spośród jego odległości do liścii jest jak największa?
type tree =
Node of int * tree * tree |
Leaf
Przekrojem drzewa nazwiemy dowolny taki zbiór jego wierzchołków (Node), że na każdej ścieżce
od korzenia do liścia (Leaf) znajduje się dokładnie jeden wierzchołek należący do przekroju.
Napisz procedurę cut : tree → int, która dla danego drzewa wyznaczy najmniejszą możliwą sumę liczb
znajdujących się w wierzchołkach tworzących przekrój danego drzewa.
type α tree =
Node of α tree * α * α tree |
Leaf;;
Zdefiniuj typ α przechadzka oraz procedury:
buduj : α tree → α przechadzka
w_lewo : α przechadzka → α przechadzka
w_prawo : α przechadzka → α przechadzka
w_gore : α przechadzka → α przechadzka
obejrzyj : α przechadzka → α
umożliwiające ,,przechadzanie'' się po drzewie i oglądanie go.
Wywołanie buduj d powinno konstruować przechadzkę, w której znajdujemy się w korzeniu drzewa d.
Wywołanie w_lewo p powinno konstruować przechadzkę powstałą przez przejście do lewego poddrzewa.
Analogicznie powinny działać procedury w_prawo i w_gore.
Procedura obejrzyj powinna zwrócić element przechowywany w wierzchołku drzewa, w którym aktualnie jesteśmy.
Podczas przechadzki po drzewie możemy przebywać jedynie w wierzchołkach Drzewo _.
Jeśli nastąpi próba wejścia do liścia Lisc lub wyjścia ,,ponad'' korzeń, należy podnieść wyjątek Poza_drzewem.
[D.L.Parnas, On the Criteria To Be Used in Decomposing Systems into Modules, CACM 12 (15), 1972]
Rozważmy następujący problem. Należy napisać program, który:
Podaj jak byś podzielił program na moduły.
Wymyśl zestaw realistycznych modyfikacji, jakie należy wprowadzić (a jeszcze lepiej poproś o to kolegę) i sprawdź, jak sprawuje się wymyślony przez Ciebie podział na moduły.
Zdefiniuj sygnatury/struktury odpowiadające podanym poniżej pojęciom.
Staraj się wykorzystać zdefiniowane wcześniej pojęcia.
max i min.
Ćwiczenia na procedury wyższych rzędów, ale bez standardowych procedur wyższych rzędów przetwarzających listy:
iterate 2 (function x -> x * (x+1)) 2
iterate 3 (function x -> x * (x+1)) 1
rozrysowując ramki.
W przypadku szybszego potęgowania funkcji co tak na prawdę jest obliczane szybciej: funkcja wynikowa, czy jej wartość?
Zaimplementuj procedurę odwrotnosc, której wynikiem dla parametru \(f\) będzie przybliżenie \(f^{-1}\) z dokładnością
zadaną przez stałą epsilon (czyli jeśli \(g = (\mathtt{odwrotnosc}\ f)\), to \(\forall x\ |g(x) - f^{-1}(x)| \le \mathtt{epsilon}\)).
Ćwiczenia na listy z elementami procedur wyższych rzędów:
exists, która dla danego predykatu i listy sprawdzi, czy na liście jest element spełniający predykat.non: (α → bool) → (α → bool).exists zdefiniuj procedurę forall,exists nadal powoduje, że przeglądane są tylko niezbędne elementy listy?
append za pomocą fold_right/fold_left.
map i flatten procedurę heads,sumy : int list → int list, która dla danej listy \([x_1;\dots; x_n]\)sumy [1; 5; 2; 7; 12; 10; 5] = [1; 6; 8; 15; 27; 37; 42].
codrugi : α list → α list, która z danej listy wybiera co drugi element (zaczynając od drugiego).codrugi [1; 2; 3; 4; 5] = [2; 4].
podzial : α list → α list list, która daną listę podzieli napodzial [3;2;2;5;7;5;4;4;3;1] = [[3;2];[2;5;7;5;4];[4;3;1]].
wzrost [3; 4; 0; -1; 2; 3; 7; 6; 7; 8] = [-1; 2; 3; 7]
wzrost [] = []
Napisz taką procedurę dopoki : α list → (α → bool) → (α → bool) → int list, że wynikiem
\(\mathtt{dopoki}\ [x_1; x_2; \dots; x_n]\ p\ q\) jest lista tych chwil w czasie, w których zachodzi \(p\) dopóki \(q\).
Na przykład, dla następujących \(p\) i \(q\):
| \(i\) | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| \(p\ x_i\) | false |
true |
true |
true |
false |
false |
true |
true |
true |
false |
false |
false |
| \(q\ x_i\) | false |
false |
true |
false |
true |
false |
false |
true |
false |
false |
true |
false |
Ekstremum oznacza minimum lub maksimum.
Napisz procedurę ekstrema :int list → int, która dla danej listy policzy ile jest na niej ekstremów.
Napisz procedurę inc : int list → int list, która dla danej listy będącej reprezentacją liczby \(x\),
wyznaczy reprezentację liczby \(x+1\).
Przyjmujemy, że lista pusta reprezentuje 0.
palteau : α list → int, która oblicza długość najdłuższego stałego (spójnego) fragmentu danej listy.
@ i filter uproszczoną wersję algorytmu quick-sort. Napisz procedurę usrednienie : float list → float list, która dla danej listy obliczy jej uśrednienie.
od_końca_do_końca : int list → int, która dla danej niepustejfold_left/fold_right) procedurę tails : α list → α list list,: (α list * int * int) list} reprezentuję listędekompresja : (α list * int * int) list → int → α, którakompresuj : int list → int list kompresującą zadaną listę.
kompresuj [1; 2; 2; 5; 11; 11; 2];;
- : int list = [1; 6; 9; 42; 3]
buduj_permutacje : α list list → α → α,buduj_permutacje \([c_0; \dots; c_k]\ x = x\).
let p = buduj_permutacje [[2; 1]; [8; 3; 4]; [5; 7; 6; 10]; [11]];;
map p [1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12];;
-: int list = [2; 1; 4; 8; 7; 10; 6; 3; 9; 5; 11; 12]
podzial : int list → int list list, która dla danej listy liczb całkowitychPrzykład:
podzial [1;3;0;-2;-2;-4;9] = [[1; 3]; [0]; [-2;-2;-4]; [9]]
prefiksy [0;1;2;3;0;5;6;0;1] = [[0]; [0;1;2;3;0]; [0;1;2;3;0;5;6;0]]
Ćwiczenia na drzewa z elementami procedur wyższych rzędów:
type 'a tree = Node of 'a tree * 'a * 'a tree | Leaf;;
let rec fold_tree f a t =
match t with
Leaf -> a |
Node (l, x, r) -> f x (fold_tree f a l) (fold_tree f a r);;
Mówimy, że drzewo ma kształt drzewa AVL, jeżeli dla każdego węzła drzewa wysokości jego obu poddrzew różnią się o co najwyżej 1.
Napisz procedurę: \(\mathtt{avl\_shaped} : \alpha\ \mathtt{tree} \to \mathtt{bool}\), która sprawdza czy dane drzewo ma kształt drzewa AVL.
Rozwiązując to zadanie nie wolno Ci tworzyć żadnych procedur rekurencyjnych.
Zamiast tego należy wykorzystać procedurę fold_tree lub inne standardowe procedury wyższych rzędów.
tree i procedura fold_tree:
type α tree = Node of α tree * α * α tree | Leaf;;
let rec fold_tree f a t =
match t with
Leaf -> a |
Node (l, x, r) -> f x (fold_tree f a l) (fold_tree f a r);;
Użyj procedury fold_tree do zaimplementowania:
float tree),map_tree --- odpowiednika procedury map dla drzew,tree i procedura fold_tree:
type 'a tree = Node of 'a * 'a tree list;;
let rec fold_tree f (Node (x, l)) =
f x (map (fold_tree f) l);;
Użyj procedury fold_tree do zaimplementowania:
map_tree --- odpowiednika procedury map dla drzew,widoczne : int drzewo → int, która dla zadanego drzewa (zawierającego wyłączniesprawdz o treści:
let sprawdz (a:int) =
let n = ???
in if a < n then -1 else if a = n then 0 else 1;;
W tej implementacji pod ??? została ukryta pewna liczba całkowita.
Napisz funkcje znajdz, która odwołując się do funkcji sprawdz, znajdzie tę liczbę całkowitą.
Podaj złożoność czasową i pamięciową rozwiązania.
Uwaga: Zakładamy, że typ int reprezentuje dowolne liczby całkowite.
W szczególności nie można zakładać, że typ int jest skończony i tym samym używać stałych takich jak max_int.
hanoi: int list → int → int, która dla zadanej konfiguracji krążków oraz słupka, na Słupki są reprezentowane jako liczby całkowite od 1 do 3.
Konfiguracja to lista numerów słupków, na których mają się znaleźć krążki, w kolejności od największych krążków do najmniejszych.
W poniższych zadaniach istotne jest posortowanie odpowiednich list:
Napisz procedurę słupki : int list → int, która dla danej listy początkowych wysokości słupków
obliczy minimalną liczbę uderzeń młotka potrzebnych do wyrównania wysokości słupków.
elementy : α list → int list → α list, która dla list \([x_1; x_2; \ldots, x_n]\) itrójkąt : int list → bool, która dla danej listy \([x_1; x_2; \dots; x_n]\) dodatnich liczb Podaj złożoność czasową i pamięciową swojego rozwiązania.
(Uwaga: Jeżeli liczby całkowite mają ograniczony zakres, to można to zadanie rozwiązać w stałym czasie.)
przedział : int list → int → int, która dla danej listy \([x_1;\dots; x_n]\), oraz dla liczby całkowitej Przykład: przedział [2; -2; 5; -1; 11; 8; 4; 5; 8; 7] 2 = 6.
przekładaniec, której wynikiem jest taka permutacja \([x_{p_1}; x_{p_2}; \dots; x_{p_n}]\) danej listy,pierwsze : int list → int list, która zwróci taką jej spójną podlistę, że:
Rozwiązując poniższe zadania zwróć uwagę na efektywność algorytmów.
W większości zadań należy użyć techniki kosztu zamortyzowanego.
rotate z wykładu).init : int → α max_queue -- tworzy nową pustą kolejkę z określonym parametrem \(k\), put : α max_queue → α → α max_queue -- wkłada element do kolejki, get_max : α max_queue → α -- zwraca maksimum z \(k\) ostatnich elementów włożonych do kolejki. Wszystkie operacje na kolejce powinny działać w stałym czasie zamortyzowanym.
kmax : int → int list → int list, dla której wynikiem kmax \(k\) \(l\) jest podlistawidoczne, która obliczy ile elementów ciągu jest widocznych z kolejnych jego elementów.widoczne [1; 8; 5; 6; 4] = [1; 3; 2; 3; 1].
różnica : int list → (int * int), która dla danej listy wyznaczy taką parę liczb \((i,j)\), że:
Jeżeli takie \(i\) i \(j\) nie istnieją, to poprawnym wynikiem jest (0,0).
Liczby reprezentujemy jako listy zer i jedynek.
Zaimplementuj dodawanie w systemie Zeckendorfa.
Zaprojektuj i zaimplementuj podane funktory:
t konstruujet).
Uwaga: Zadania oznaczone [SICP] są mocno inspirowane zadaniami i materiałem z tejże książki, choć nie ma w niej mowy o funktorach.
Ćwiczenia na konstrukcje imperatywne:
Counter o następującej sygnaturze:
module type COUNTER = sig
type counter
val make : unit -> counter
val inc : counter -> int
val reset : unit -> unit
end;;
Procedura make tworzy nowy licznik o początkowej wartości 0.
Procedura inc zwiększa licznik o 1 i zwraca jego nową wartość.
Procedura reset ustawia wartość wszystkich liczników na 0.
Podaj złożoność czasową i pamięciową ciągu \(m\) operacji, spośród których \(n\) to operacje make.
cykl [|2; 1; 0; 5; 6; 4; 3; 8; 7|] = 4
type α drzewo =
Puste |
Wezel of α * α drzewo * α drzewo * α drzewo ref;;
Puste.)fastryguj : α drzewo → unit, która sfastryguje dane drzewo.
w_lewo : α drzewo → unit, która w każdym węźle drzewa ustawia referencję tak,Wezel) znajdujący się na tej samej głębokości w drzewie i najbliżej w lewo od danego węzła.Puste.
potomek : α drzewo → unit, która w każdym węźle ustawi referencję na najgłębiej położony węzełWezel) będący jego potomkiem.
diagonal: (float * float) array → float, która wyznaczy najkrótszą z przekątnych prostokątów.
minmax : int array -> int * int, która znajduje minimum i maksimum w takiej tablicy.
Rozwiązując to zadanie nie wolno Ci tworzyć żadnych własnych procedur rekurencyjnych.
Wolno natomiast korzystać z pętli i innych konstrukcji imperatywnych.
Podaj złożoność czasową i pamięciową swojego rozwiązania.
Skarbonki są ponumerowane od 0 do \(n-1\).
Na podstawie tablicy \(a\) (rozmiaru \(n\)), takiej, że \(a.(i) = \) numer skarbonki, w której znajduje się kluczyk do skarbonki
numer \(i\), oblicz minimalną liczbę skarbonek, które należy rozbić, tak aby dostać się do zawartości wszystkich skarbonek.
Zaprojektuj algorytm, który na podstawie opisu tego która małpkaxtrzyma którą, oraz na podstawie opisu łapek puszczanych w
kolejnych chwilach, dla każdej małpki wyznaczy moment, kiedy spadnie ona na ziemię.
kolory: int * int list → int,
Plansza do gry Hex
Dwaj gracze, biały i czarny, grają w grę hex.
Zajmują oni na przemian wolne pola na planszy. Zaczyna gracz biały.
Gracz biały stara się połączyć swoimi polami lewy i prawy brzeg planszy, a gracz czarny górny i dolny brzeg planszy.
Wygrywa gracz, który jako pierwszy połączy odpowiednie brzegi planszy, w ten sposób, że z jednego brzegu na drugi
będzie można przejść przez sąsiadujące pola zajęte przez danego gracza.
Napisz procedurę hex : int → (int * int) list → (bool * int), która na podstawie wielkości planszy \(n\),
oraz listy współrzędnych (rząd, pozycja w rzędzie) kolejno zajmowanych przez graczy pól określi, czy wygrał gracz biały
(true), czy czarny (false) i w którym ruchu.
Napisz procedurę biura : int → int * int list → int, która na podstawie liczby pracowników \(n\) oraz listy par pracowników
posiadających nawzajem swoje numery telefonów \(l=[(a_1,b_1);\dots; (a_m,b_m)]\), wywołana jako \(\mathtt{biura}\ n\ l\), obliczy liczbę biurowców.
Rozwiązując to zadanie przyjmij, że \(m=O(n)\).
przedzialy: int → (int * int * int) list → int, która dla danego \(n\) oraz ciągu trójek oblicza takie \(k\).
coto i udowodnij to.
x := 0;
y := 1;
while !y <= n do
x := !x - !y;
y := !y - !x
done;
length a -- badać wielkość tabicy n, Napisz procedurę zamiany : int array → unit, która posortuje daną tablicę
(tj. przekształci ją w permutację identycznościową) wykonując minimalną możliwą liczbę zamian.
Podaj niezmienniki pętli oraz odpowiednie funkcje miary.
Nie muszą one mieć postaci formuł, ale muszą być ściśle określone.
Udowodnij pełną poprawność programu.
let north = 0 and east = 1 and south = 2 and west = 3;;
Układ ulic jest dany w formie trójwymiarowej tablicy wartości logicznych ulice: \(\mathtt{ulice}.(i).(j).(k)\)
określa, czy z punktu o współrzednych \((i,j)\) można przejechać w kierunku k jedną jednostkę odległości.
Można założyć, że tablica ta uniemożliwia wyjechanie poza obszar \(\{0,\dots,m-1\} \times \{0,\dots,n-1\}\).
Członkowie Klubu Prawoskrętnych Kierowców na skrzyżowaniach jeżdżą prosto lub skręcają w prawo.
Sprawdź, czy z punktu \((0,0)\) prawoskrętny kierowca może dojechać do punktu \((m-1, n-1)\).
Wyznacz podział pracowników na grupy.
e wartości logicznych; w grafie mamy krawędź \(u\!\!\to\!\!v\) wtw., gdy \(\mathtt{e}.(u).(v)\)). Napisz procedurę zdominowani : bool array array -> int, która na podstawie tablicy opisującej graf
obliczy liczbę wierzchołków, dla których istnieją wierzchołki je dominujące.
(int * bool) array array. Miasto zostało całkowicie zalane.
Żeby je osuszyć należy w kotlinie rozmieścić pewną liczbę pomp.
Każda z pomp wypompowuję wodę aż do osuszenia kwadratu jednostkowego, na którym się znajduje.
Osuszenie dowolnego kwadratu pociąga za sobą obniżenie poziomu wody lub osuszenie kwadratów jednostkowych, z
których woda jest w stanie spłynąć do kwadratu, na którym znajduje się pompa.
Woda może przepływać między kwadratami, które stykają się bokami.
Wyznacz minimalną liczbę pomp koniecznych do osuszenia miasta.
Teren nie należący do miasta nie musi być osuszony.
Miasto nie musi tworzyć spójnego obszaru.
pierwsze, która dla danej liczby całkowitej \(n\) (\(n>1\)) wyznaczadomino : (α * α) list → (α * α) list. Chcemy zwiedzić wszystkie te miejscowości, jednak nie w dowolnej kolejności.
Mamy daną listę ograniczeń postaci \([(i_1, j_1); \dots; (i_k,j_k)]\).
Ograniczenie \((i,j)\) oznacza, że miasto \(i\) musi być zwiedzone przed miastem \(j\).
Możesz założyć, że ograniczenia da się spełnić.
Na początku wyruszamy z miasta 1, a kończymy podróż w mieście \(n\).
Wyznacz minimalną drogę jaką trzeba przejechać, żeby zwiedzić wszystkie miasta.
sklejanie : int list → int obliczającą minimalną liczbę operacji ,,sklejania'', które należy wykonać, aby ciąg był rosnący.sklejanie [3; 5; 4; 2; 7] = 1.
Napisz procedurę klej : int list → int, która na podstawie długości
kawałków obliczy minimalną ilość kleju (w ml) potrzebną do sklejenia wszystkich kawałków.
Po ulicach miasta kursuje autobus. Zaczyna on trasę przy skrzyżowaniu \((1, 1)\), a kończy przy skrzyżowaniu \((n, m)\).
Ponadto autobus może jechać ulicami tylko w kierunku wschodnim i/lub północnym.
Przy niektórych skrzyżowaniach znajdują się pasażerowie oczekujący na autobus.
Rozmieszczenie pasażerów jest dane w postaci prostokątnej tablicy a \(n \times m\) liczb całkowitych --
\(a.(i-1).(j-1)\) oznacza liczbę pasażerów czekających przy skrzyżowaniu \((i,j)\).
Napisz procedurę autobus, która na podstawie tablicy a obliczy maksymalną liczbę pasażerów, których może zabrać autobus.
Zakładamy, że w autobusie zmieści się dowolna liczba pasażerów.
type drzewo = Leaf | Node of int * drzewo * drzewo
Waga drzewa to suma wag rozgałęzień i siedzących na nich wróbli.
Drzewo nazwiemy zrównoważonym, jeśli w każdym rozgałęzieniu waga dwóch poddrzew będzie taka sama.
Rzucając celnie kamieniem w rozgałęzienie drzewa możemy przegonić wszystkie wróble z poddrzewa zaczepionego w tym rozgałęzieniu.
Napisz funkcję rownowaga : drzewo → bool, która dla danego drzewa określa, czy da się tak rzucać kamieniami w drzewo,
aby stało się ono zrównoważone. Podaj złożoność czasową i pamięciową swojego rozwiązania.
Dla przykładu, aby zrównoważyć następujące drzewo:
Node (3, Node (5, Node (1, Leaf, Leaf), Node (7, Leaf, Leaf)), Node (2, Leaf, Leaf))
wystarczy raz rzucić kamieniem (w jego lewe poddrzewo).
prostokąt : int array → int → int, która dla zadanej tablicy \(a\) oraz liczby \(k\) Wynikiem procedury powinien być obwód tego prostokąta.
Podaj złożoność czasową i pamięciową swojego rozwiązania.
Prostokąt o dolnym lewym rogu \((x_1,y_1)\) i górnym prawym \((x_2,y_2)\) zawiera wszystkie elementy tablicy \(a.(i).(j)\) dla
\(x_1 \le i \le x_2\), \(y_1 \le j \le y_2\), oraz ma obwód \(2 \cdot (x_2 - x_1 + 1) + 2 \cdot (y_2 - y_1 + 1)\).
prostokat : int array array → int → (int * int) * (int * int), która dla tablicy \(a\) i Opis drzewa ma postać struktury listowej, w której każdy węzeł to lista jego synów.
Napisz procedurę koraliki, która na podstawie opisu drzewa obliczy minimalny koszt koralików
potrzebnych do wykonania jego modelu.
Uwaga: Dwa kolory wystarczą do wykonania modelu, ale nie dają minimalnego kosztu.
Możesz założyć, że optymalna liczba kolorów nie przekracza \(\log_2 n\), gdzie \(n\) to liczba koralików.
Chcemy zoptymalizować liczbę i długość kawałków sznurka użytych do wykonania modelu drzewa:
Poniższe zadania rozwiązuje się (mniej lub bardziej) zachłannie.
Dla każdego z nich, oprócz programu, należy uzasadnić poprawność rozwiązania zachłannego.
nawiasy, która obliczy minimalną liczbę nawiasów które należy obrócić, tak aby uzyskać poprawne wyrażenie nawiasowe.
type nawias = Otwierający | Zamykający
val nawiasy : nawias list → int
wakacje : int → int → int list → int, która na podstawie liczb k i r, oraz Ciąg jednoelementowy i pusty są również naprzemienne.
Napisz procedurę $\mathtt{naprzemienny} : \mathtt{int~array} \to \mathtt{int}$, która mając dany ciąg liczb całkowitych
w postaci tablicy obliczy długość najdłuższego jego naprzemiennego podciągu.
Na przykład, \texttt{naprzemienny [|4;8;6;5;3;4;5;3;7;9;5;7|] = 8}.
harmonogram która obliczy optymalną kolejność wykonywania zaległych zobowiązań.podzial : int → int list, która dla danej nieujemnej liczby całkowitejpodzial 42 = [1; 2; 5; 13; 21].
Jasio bawi się jednym z samochodzików leżących na podłodze.
Oprócz Jasia w pokoju cały czas przebywa mama.
Gdy Jasio chce się bawić jakimś innym samochodzikiem i jeśli ten samochodzik jest na podłodze, to sięga po niego.
Jeśli jednak samochodzik znajduje się na półce, to musi mu go podać mama.
Mama podając Jasiowi jeden samochodzik, może przy okazji zabrać z podłogi dowolnie wybrany przez siebie
inny samochodzik i odłożyć go na półkę (tak, aby na podłodze nie brakło miejsca).
Mama bardzo dobrze zna swoje dziecko i wie dokładnie, którymi samochodzikami Jasio będzie chciał się bawić.
Dysponując tą wiedzą mama chce zminimalizować liczbę przypadków, gdy musi podawać Jasiowi samochodzik z półki.
W tym celu musi bardzo rozważnie odkładać samochodziki na półkę.
Napisz procedurę samochodziki, która dla danej liczby k oraz listy samochodzików, którymi zechce się
bawić Jasio, obliczy minimalną liczbę razy, kiedy mama będzie musiała Jasiowi podać samochodzik.
ruchy : int array → (int * int * int) list, która dla danej tablicy a wyznaczyRozwiązanie każdego z poniższych zadań wymaga użycia jednej z trzech technik:
back-trackingu, programowania dynamicznego lub zachłannego.
Pytanie której?
[15; 11; 7; 5; 3; 1].
Napisz procedurę ciąg : int → int list, która dla danego \(n \ge 1\) znajdzie najkrótszy
taki ciąg \(x_0, x_1, ..., x_k\), który spełnia powyższe warunki, oraz \(x_k = n\).
Wynikiem procedury powinna być lista \([x_0; ...; x_k]\).
rozklad : int * int → int list, która dla danych liczb \(l\) i \(m\) wyznaczypodciagi : int list → int list list, która rozbije dany ciąg liczb na minimalną liczbę podciągów (ściśle) rosnących.
podzial : int list → int → int list list, która podzieli daną listę na \(k\) spójnych fragmentów Na przykład podzial [1; 4; 2; 2; 4; 1; 2; 4] 3 = [[1; 4; 2]; [2; 4]; [1; 2; 4]].
koszt : int array → int, która oblicza minimalny koszt posortowania tablicy \(p\).
type expr =
NWD of expr * expr |
NWW of expr * expr |
Number of int
Drzewa takie reprezentują wyrażenia.
NWD oznacza największy wspólny dzielnik, a NWW najmniejszą wspólną wielokrotność dwóch podwyrażeń.
Argumentami Number są liczby nieujemne.
\(\mathtt{Number}\ x\), dla \(x>0\) oznacza liczbę \(x\).
\(\mathtt{Number}\ 0\) oznacza miejsce, w które należy wstawić dodatnią liczbę.
Napisz procedurę \(\mathtt{wstaw}: \mathtt{expr} * \mathtt{int} \to \mathtt{int}\), która dla danego drzewa \(t\) i
dodatniej liczby całkowitej \(n\) znajdzie taką najmniejszą dodatnią liczbę całkowitą \(x\), że gdy wstawimy \(\mathtt{Number}\ x\)
w miejsce wszystkich wystąpień \(\mathtt{Number}\ 0\) w \(t\), to wartością wyrażenia będzie \(n\).
Jeżeli taka liczba \(x\) nie istnieje, to poprawnym wynikiem jest 0.
Na przykład, dla \(t = \mathtt{NWW(NWW(Number 2, Number 0), NWD(Number 0, Number 49))}\) i \(n = 42\), \(\mathtt{wstaw}\ t\ n = 21\).
type choinka =
Koniuszek |
Galaz of choinka |
Rozgalezienie of choinka * choinka;;
opisujące kształt choinki.
Rozgalezienie, Galaz i Koniuszek) możemy umieszczać świeczki.Rozgalezienie, Galaz i Koniuszek) wieszamy bombki. Bombki należy zawiesić tak, aby choinka nie chwiała się, tzn. dla każdego rozgałęzienia łączny ciężar bombek
zawieszonych po lewej i po prawej stronie musi być taki sam.
Pytanie czy jest to możliwe, a jeżeli tak, to na ile sposobów?
Napisz procedurę, która dla danej choinki obliczy na ile sposobów można powiesić na niej bombki.
szukaj : char array → char array → int, która znajduje najdłuższyokresy: int list → int list, której wynikiem dla danej listy \([x_1; x_2; \dots; x_n]\) Zauważmy, że każdy ciąg ma okres całkowity, co najwyżej jest to \(k=n\).
Na przykład, okresem całkowitym ciągu \([1;3;2;1;1;3;2;1;1;3;2;1]\) jest 4, a okresem całkowitym ciągu \([1;3;2;1;1]\) jest 5.
Napisz procedurę okres : int array → int, która dla danego ciągu wyznaczy jego okres całkowity.
Napisz procedurę, która dla danego (w postaci listy) napisu obliczy listę długości maksymalnych
okresów kolejnych jego prefiksów.
podsłowo : char array → (int * int),Dla pustej tablicy poprawnym wynikiem jest (0,0).