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ń katalogcd /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 symboliczneln 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.forward
Poziomy 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
emacs
emacs
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?
Kompilator ocamla możemy wywołać jako program o nazwie ocamlc lub ocamlopt.
Ocamlc generuje kod bytecode'u (pliki *.byte), który nie jest bezpośrednio wykonywany, ale interpretowany. Taki kod jest niezależny od maszyny, na której jest wykonywany, a więc jest przenośny. Jego interpretacją zajumuje się program ocamlrun, dla użytkownika nie jest to jednak widoczne. Po prostu uruchamiamy plik skompilowany.
Ocamlopt generuje kod dla konkretnej architektury i systemu (kod natywny, pliki *.native). Nie jest on przenośny, ale działa szybciej.
Obu kompilatorom przekazujemy podobne zestawy argumentów:
Przykład: ocamlc leftist.mli leftist.ml test.ml -o test
Kompilująć program w ten sposób, zawsze kompilujemy wszystkie pliki źródłowe. W przypadku bardzo małych projektów, może to być akceptowalne.
Jednak już w przypadku projektów średniej wielkości właściwe jest kompilowanie osobno wszystkich modułów (które tego wymagają) i łączenie ich kodu w jeden plik wykonywalny.
Skompilowane interfejsy modułów (pliki *.mli) to pliki o rozszerzeniu *.cmi.
Implementacje modułów skompilowane do kodu natywnego mają postać plików z rozszerzeniem *.o (standardowy format object-code) oraz *.cmx (pliki typowe dla Ocamla).
Implementacje modułów skompilowane do bytecode'u maję postać plików z rozszerzeniem *.cmo.
Chcąc skompilować tylko jeden plik, interfejsu lub implementacji modułu, wystarczy podać odpowiedniemu kompilatorowi opcję -c.
Typowym narzędziem uniksowym do kompilowania projektów jest program make wraz z plikiem Makefile opisującym
zależności między plikami. Jednak do kompilacji małych lub średnich projektów w Ocamlu lepiej użyć programu ocamlbuild.
Założenia: wszystkie potrzebne pliki źródłowe są w bieżącym katalogu.
Wystarczy wykonać ocamlbuild program.native lub ocamlbuild program.byte. Ocamlbuild sam zbada zależności między modułami i skompiluje dokładnie to co jest niezbędne. Wszystkie pliki generowane automatycznie będą umieszczone w katalogu _build,
a w bieżącym katalodu pojawi się dowiązanie do skompilowanego programu.
Więcej o programie ocamlbuild: https://ocaml.org/learn/tutorials/ocamlbuild/
Programowanie to sport zespołowy. Duże programy są tworzone przez zespoły, lub wiele współpracujących zespołów. Bywa i tak, że nikt nie ma wiedzy o całym systemie, a każdy zna tylko jego fragmenty. Jak sensownie funkcjonować w takich realiach? Oto kilka zasad dobrego warsztatu programisty.
Dla kogo piszemy kod? Dla komputera? Ależ skąd! Piszemy dla siebie i swoich współpracowników. Inaczej, po skompilowaniu pierwszej wersji programu, możnaby kod źródłowy wyrzucić. Tymczasem program, który jest używany, będzie wielokrotnie modyfikowany, przez nas i naszych kolegów. Modyfikacje same są zwykle niewielkie, ale wymagają przeczytania większego fragmentu kodu. Wniosek: kod źródłowy jest więcej razy czytany, niż pisany. Powinniśmy więc go tak pisać, żeby się łatwo czytało i modyfikowało, a nie żeby jak najszybciej stworzyć pierwszą wersję.
Wcięcia muszą być! Kod bez wcięć nie będzie oceniany. Wszytkie szczegóły dotyczące wcięć wynikają z tego po co one są: wcięcia mają ułatwić szybkie dopasowanie początku i końca konstrukcji składniowej.
Wielkość wcięć: od 2 do 4, w zależności od smaku, ale konsekwentnie stała. Minimalna wielkość wcięcia to 2, bo inaczej "schodki" nie są widoczne. Maksymalna to 4, bo inaczej kod robi się strasznie szeroki.
W pozostałych szczegółach, możecie dobrać Państwo styl, który najbardziej Wam odpowiada, byle konsekwentnie. Tutaj można znaleć przykładowy dobry styl.
Robimy odstępy wokół operatorów binarnych i po znakach interpunkcyjnych. Nie robimy odstępów po wewnętrznej stronie nawiasów. Na przykład: 2 + 2 * 4, [2; 3; 4], (2, 3 - 4).
Odstępy w pionie (puste wiersze) też są ważne. Między definicjami procedur robimy wiersz lub dwa odstępu. Wewnątrz dłuższych procedur możemy też robić pojedyncze wiersze odstępu dla podkreślenia struktury kodu.
W podejściu do komentarzy można spotkać dwie skrajności:
Oba podejścia są złe. Owszem, kod ma być przejrzysty i ma być widać co się w nim dzieje, ale zawsze są pewne aspekty, które z niego nie wynikają, np. co jest celem? po co coś się dzieje? co zakładamy? Z drugiej strony, umieszczanie w komentarzach "relacji na żywo" z obliczeń mija się z celem i tylko utrudnia czytanie kodu. Podstawowa zasada brzmi: Komentarze mają ułatwić czytanie i zrozumienie kodu. Odnosi się to tak samo do innych programistów, z którymi współpracujemy, jak i do nas samych tylko za jakiś czas. Oto kilka typowych miejsc, w których warto umieszczać komentarze:
Identyfikatory powinny być znaczące. Jeśli z nazwy identyfikatora wynika czym jest argument, to często nie trzeba nic więcej o nim pisać w komentarzach.
Identyfikatory (nazwy stałych i procedur) powinny zaczynać się z małej litery. Wieloczłonowe nazwy proponuję łączyć podkreśleniem: taki_sobie_identyfikator.
Program, który jest rozwijany podlega ciągłym zmianom. Jak sprawdzić, czy kolejne zmiany nie wprowadzają nowych błędów do programu? Testować! Warto utrzymywać bogaty zestaw testów, które po każdej zmianie w programie możemy łatwo uruchomić. Testy nie zagwarantują nam poprawności kodu, ale odpowiedni zestaw testów potreafi wykryć większość błędów.
Pisząc kod, powinniśmy odrazu tworzyć dla niego testy. Dla każdej funkcjonalności / procedury piszemy testy. Mamy jakąś elementarną procedurę wykorzystywaną w naszym kodzie -- piszemy dla niej testy. Mamy procedurę integrującą kilka procedur pomocniczych w coś większego -- piszemy dla niej testy. Tak jak procedury tworzą hierarchię, tak testy tworzą swoistą piramidę. Powiniśmy mieć wiele prostych testów dla elementarnych procedur (unit testy), oraz mniejszą liczbę testów-scenariuszy symulujących sposób użycia całego systemu.
Jak pisać testy w Ocamlu? Najlepiej wykorzystać operację assert <warunek>;. (Uwaga na ";". Narazie nie wnikamy w imperatywność.) Sprawdza ona, czy warunek jest spełniony i jeśli nie, przerywa obliczenia. Gdy uruchamiamy kod "dla użytkownika", możemy wyłączyć sprawdzanie asercji opcją -noassert. Testy mogą być po prostu asercjami na poziomie definicji (tj. nie zagnieżdżonymi wewnątrz definicji). Umieszczanie asercji wewnątrz definicji procedur też ma sens, ale to już coś innego niż testowanie.
Dwie najpopularniejsze techniki pisania kodu, mające na celu wyeliminowanie błędów, to code reviews i pair-coding. Pair-coding polega na pisaniu razem w parach. Jedna osoba pisze kod, a druga śledzi i komentuje ten proces. Obie muszą rozumieć co się dzieje i być przekonane, że powstający kod jest poprawny. Code review polega na tym, że kod, lub zmiana w kodzie, napisana przez jednego programistę jest przeglądana przez innego programistę. Przeglądający może zgłaszać zastrzeżenia i wskazywać co należy poprawić. Zasadniczo reviewer nie wprowadza poprawek sam, z wyjątkiem drobiazgów. Tę właśnie technikę zastosujemy na Laboratorium.
Jeśli pracujemy w zespole wieloosobowym nad programem składającym się z wielu plików, to jak synchroizować wyniki swojej pracy? Do tego właśnie służą systemy kontroli wersji. System kontroli wersji pamięta:
Starsze systemy kontroli wersji (RCS) wymagały blokowania plików w celu wprowadzania zmian w nich. Nowsze systemy kontroli wersji (Subversion, Git) pozwalają na równoczesne wprowadzanie zmian w tych samych plikach przez wielu programistów. W praktyce dobrze się to sprawdza. Jednak zmiany takie nie zawsze można automatycznie scalić. Czasami konieczna jest interwencja człowieka.
Git jest aktualnie chyba najpopularniejszym systemem kontroli wersji. Git jest systemem rozproszonym. Na każdym komputerze biorącym udział w tworzeniu programu znajduje się repozytorium zawierające kompletną (choć niekoniecznie aktualną) informację o historii wersji programu. Repozytoria mogą wymieniać między sobą informacje o wprowadzanych zmianach. Zwykle każdy programista ma jedno repozytorium lokalne na swoim komputerze, oraz istnieje jedno zdalne repozytorium, służące synchronizowaniu zmian.
W Git'ie konkretna wersja programu to commit. Commit ma poprzedni commit i wiadomo jakie zmiany względem niego wprowadza. Jeden poprzedni commit może mieć wiele następnych commitów (fork). Możliwy jest też commit o dwóch poprzednich commitach (merge), scalający zmiany wprowadzone od ich wspólnego poprzednika.
Zwykle myślimy w kategoriach gałęzi (branch), a nie commit'ów. Branch to wskaźnik na konkretny commit. W miarę wprowadzania zmian do kodu, wskaźnik ten jest przesuwany na kolejne commity. Zwykle mamy główną gałąź (trunk/master) oraz wiele gałęzi pobocznych, tworzonych na potrzeby opracowania poszczególnych zmian i łączonych potem z główną gałęzią.
Git bardzo dobrze wspiera przeglądy kodu. Typowy cykl opracowania zmiany w kodzie wygląda następująco:
Istnieje wiele serwisów oferujących przechowywanie zdalnego repozytorium, np. GitHub, BitBucket. Można też do tego wykorzystać współdzielony system plików, choć właściwa kontrola praw dostępu wymaga pewnie utworzenia mnóstwa grup. Jeżeli będziecie Państwo przechowywać swoje programy zaliczeniowe w repozytoriach, to proszę zadbać, żeby nie były one publicznie dostępne (przynajmniej dopóki nie minie ostateczny termin oddawania programó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-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.
Rozwiązania
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).maksima
, która dla danych liczb \(l\) i \(p\) (\(l \le p\)) obliczy ile lokalnych maksimów ma funkcja \(f\) w przedziale \([l, p]\).
Ć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).
wysun : int list -> int list
, która dla danej listy posortowanej niemalejąco wybierze wszystkie jej elementy występującewysun [1;2;2;3;4;5;5;6;6;6;7] = [2;2;5;5;6;6;6;1;3;4;7]
bonifacy : int -> int list -> int
, która dla liczby naturalnej n (n ≥ 0) oraz co najmniej trzyelementowej zerojedynkowej listy \([b_0; \dots; b_{k−1}]\) obliczy n-tą liczbę wyznaczonego przez tę listę ciągu Bonifacego.
zsumuj
, która dla danej niemalejącej listy dodatnich liczb całkowitychpodziel : 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]]\).
mieszaj
, która wywołana dla danej listy, obliczy listę powstałą przeztrojki : 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.
fit 42 [-28, -25, -15, -1, 4, 8, 15, 60] = 1
, ponieważ \(|42 - 28 - 15| = 1\).
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ę 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).
Rozwiązania
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 | Null;;
Drzewo nazywamy ultralewicowym jeśli głębokości kolejnych liści (Null
), 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.
potnij
, która dla danego predykatu \(p\) typu \(\alpha \to \mathtt{bool}\), oraz drzewa \(t\) typu \(\mathtt{\alpha\ tree}\), zwróci następujący wynik.potnij p t
powinno zwrócić listę tych drzew (w dowolnej kolejności).
rozcięcie
, która znajduje taką krawędź w danym drzewie binarnym, że po jej usunięciu drzewotype tree = Node of tree * int * tree | Null;;
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 | Null;;
Skosokość drzewa jest zdefiniowana w następujący sposób:
Null
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, Null, Node(2, Null, Null)), Node(6, Node(1, Null, Null), Node(3, Null, Null) ) ) skosokosc t = Node(5, Node(6, Node(1, Null, Null), Node(3, Null, Null) ), Node(4, Node(2, Null, Null), Null) )
Skosokość tak uzyskanego drzewa wynosi 6.
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 | Null
.
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 (Null
) jest równa 0.
Napisz procedurę średnica : tree → int
, która oblicza średnicę danego drzewa.
Rozwiązania
type 'a drzewo = Node of 'a * 'a drzewo * 'a drzewo | Null
Napisz procedurę polowa:'a drzewo -> 'a drzewo
, która znajduje w zadanym drzewie taki węzeł (Node
), który jest przodkiem jak najmniejszej liczby liści, ale przynajmniej połowy wszystkich liści drzewa.
type tree = Node of int * tree * tree | Leaf;;
Napisz procedurę \(\mathtt{odchudzanie}: \mathtt{tree} \to \mathtt{tree}\), która mając dane drzewo binarne, zwraca drzewo powstałe przez usunięcie z niego takich poddrzew
(zastępując Node(...)
przez Leaf
) aby suma elementów w powstałym drzewie była jak największa.
type drzewo = Node of α * α drzewo * α drzewo | Null
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 | Null
Przekrojem drzewa nazwiemy dowolny taki zbiór jego wierzchołków (Node
), że na każdej ścieżce
od korzenia do liścia (Null
) 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 list;;
Napisz procedurę \(\mathtt{liscie} : \alpha\ \mathtt{tree} \to \mathtt{int~list}\), która dla danego drzewa liczy ile w tym drzewie jest liści na poszczególnych głębokościach i zwraca listę liczb postaci \([g_0; g_1; \dots; g_h]\), gdzie \(g_i\) to liczba liści znajdujących się na głębokości \(i\), a \(h\) to wysokość drzewa.
type α tree = Node of α * α tree list;;
Ścieżkę w drzewie nazwiemy rosnącą, jeżeli wartości w węzłach na tej ścieżce, w kierunku od korzenia w dół, są rosnące.
Napisz procedurę rosnaca: int tree → int list
, która znajdzie w danym drzewie najdłuższą ścieżkę rosnącą i zwróci wartości znajdujące się na niej (w kolejności od korzenia w dół). (Uwaga: szukana ścieżka nie musi zaczynać się w korzeniu i nie musi kończyć się w liściu.)
Rozwiązania
type α tree = Node of α * α tree list;;
Napisz procedurę nieparzyste: α tree → int
, która dla danego drzewa policzy ile jest w nim poddrzew zawierających nieparzystą liczbę wierzchołków.
type tree = Node of tree * int * tree | Null;;
Napisz procedurę czapeczka : tree → tree → int
, która obliczy na ilu poziomach, idąc od góry,
dane drzewa są identyczne, tzn. na tych poziomach drzewa mają ten sam kształt, a w odpowiadających sobie wierzchołkach są takie same liczby.
[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.
sup
i inf
.
sup
i inf
.sig type α tree = Node of α tree * α * α tree | Null type α walk make : α tree → α walk goleft : α walk → α walk goright : α walk → α walk goup : α walk → α walk lookup : α walk → α tree exception OutsideTree end
Wywołanie make d
powinno konstruować przechadzkę, w której znajdujemy się w korzeniu drzewa d
.
Wywołanie goleft p
powinno konstruować przechadzkę powstałą przez przejście do lewego poddrzewa.
Analogicznie powinny działać procedury goright
i goup
.
Procedura lookup
powinna zwrócić poddrzewo zaczepione w wierzchołku drzewa, w którym aktualnie jesteśmy.
Podczas przechadzki po drzewie możemy przebywać jedynie w wierzchołkach Node _
.
Jeśli nastąpi próba wejścia do Null
'a lub wyjścia ,,ponad'' korzeń, należy podnieść wyjątek OutsideTree
.
Ć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ść?
Rozwiązania
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]
.
zlecenia [(-1, 1); (2, 2); (3, 3); (4, 2); (10, 2)] = [1; 2; 4; 5; 2]}
.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]]
.prextrema [-2; 1; 0; 1; 3; 2; -1; 5; 4; -3; 2; 1; 7] = [-2; 1; 3; 5; -3; 7]
wzrost [3; 4; 0; -1; 2; 3; 7; 6; 7; 8] = [-1; 2; 3; 7] wzrost [] = []
type kolor = Czerwona | Zielona
.kulki [Czerwona, Zielona, Zielona, Zielona, Zielona, Czerwona, Czerwona, Zielona] = 2
.
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.
Rozwiązania
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.
plateau : α 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]
prawie [3; 2; -2; 0; 42; 22; -12; 15; -4] = 4
.[2; -2; 0; 42]
i [22; -12; 15; -4]
.
winda: int → int list → int
, która mając dane \(w\) oraz listę ciężarów kolejnych osób czekających na windę policzy minimalną liczbę kursów windy potrzebnych do obsłużenia czekających.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]]
monotons [2; 3; 3; 5; 0; -1; -7; 1; 3; 5; 5; 2; 4; 6] = 4
.[2; 3; 3]
, [5; 0; -1; -7]
, [1; 3; 5; 5]
, [2; 4; 6]
.
prefiksy [0;1;2;3;0;5;6;0;1] = [[0]; [0;1;2;3;0]; [0;1;2;3;0;5;6;0]]
Na przykład: transpose [[1; 2]; [5; 3]; [0; 1]] = [[1; 5; 0]; [2; 3; 1]]
Rozwiązania
type symbol = | Liczba of int | Plus | Minus | Razy | Podziel | Nawias_Otw | Nawias_Zam
Masz daną listę symboli tworzących poprawne wyrażenie arytmetyczne. Napisz procedurę \(\mathtt{kalkulator}: \mathtt{symbol~list} \to \mathtt{int}\), która obliczy wartość wyrażenia.
Przyjmij, że, o ile nawiasy nie wymuszają innej kolejności, operacje arytmetyczne wykonujemy od lewej do prawej.
Przykład:
kalkulator [Nawias_Otw; Liczba 5; Plus; Liczba 4; Razy; Liczba 2; Plus; Liczba 3; Nawias_Zam; Razy; Liczba 2] = 42
Ćwiczenia na drzewa z elementami procedur wyższych rzędów:
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:
preorder/postorder : 'a tree -> 'a list
, która przekształca dane drzewo w listę jego elementów w porządku pre-order/post-order (zwróć uwagę na efektywność).bin_tree
i procedura fold_bin_tree
:
type 'a bin_tree = Node of 'a bin_tree * 'a * 'a bin_tree | Null;; let rec fold_bin_tree f a t = match t with Null -> a | Node (l, x, r) -> f x (fold_bin_tree f a l) (fold_bin_tree f a r);;
Użyj procedury fold_bin_tree
do zaimplementowania:
float tree
),map_bin_tree
-- odpowiednika procedury map_tree
dla drzew binarnych, sprawdz
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
.
Rozwiązania
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). Napisz procedurę \(\mathtt{zlewki}:\mathtt{float~list} \to \mathtt{int} \to \mathtt{float}\),
która dla danej listy zawierającej liczby \([|x_1; \dots; x_n|]\) oraz liczby \(k\) określa
określa ile jest wody w najmniej wypełnionym niepustym kubeczku po wykonaniu \(k\) operacji przelewania.
type pora = Wiosna | Lato | Jesień | Zima
.pory [|Lato; Wiosna; Zima; Jesień; Lato; Zima; Wiosna; Jesień; Lato|] = 6
.Jesień, Zima, Wiosna, Lato
.
Przykład: Poniżej podane są temperatury i czym będzie pokryta sadzawka (od góry) w kolejne dni:
Napisz procedurę \(\mathtt{sadzawka}: \mathtt{int~list} \to \mathtt{int~list}\), która dla danej listy temperatur zwróci listę reprezentującą kolejne warstwy pokrywające sadzawkę po tym okresie – liczby ujemne reprezentują lód, dodatnie wodę, a ich wartość bezwzględna grubość warstwy.
Dla powyższego przykładu, poprawnym wynikiem jest \([-6, 4, -2]\).
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]
.
Napisz procedurę \texttt{prostokat: int list → int}, która dla danej tablicy wyznaczy maksymalną powierzchnię prostokąta (o bokach równoległych do boków figury), który można wpisać w figurę opisaną przez tablicę.
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
Twoja procedura powinna działać w stałej pamięci dodatkowej.
Natomiast może modyfikować daną tablicę.
type α tree = Node of α * α tree * α tree * α tree ref | Null;;
Null
.)fastryguj : α tree → unit
, która sfastryguje dane drzewo.
w_lewo : α tree → unit
, która w każdym węźle drzewa ustawia referencję tak, aby wskazywały na węzeł (Node
) znajdujący się na tej samej głębokości w drzewie i najbliżej w lewo od danego węzła. W przypadku skrajnie lewych węzłów, referencja powinna wskazywać na Null
.
potomek : α tree → unit
, która w każdym węźle ustawi referencję na najgłębiej położony węzeł (Node
) będący jego potomkiem.
Node(x, left, right, _)
wartości w poddrzewie left
są mniejsze lub równe x
, a wartości w poddrzewie right
są większe równe x
.fastryga: α tree → unit
, która mając dane drzewo porządkowe tak ustawi w nim referencje, że węzły zawierające takie same wartości będą połączone w listy cykliczne.diagonal: (float * float) array → float
, która wyznaczy najkrótszą z przekątnych prostokątów.
type 'a heap = Node of 'a heap * 'a * 'a heap | Null
Sam stóg nie jest dany, natomiast mamy daną tablicę z liczbami znajdującymi się w węzłach, w kolejności zgodnej z obejściem infiksowym stogu.
(Obejście infiksowe, to obejście zgodnie z rekurencyjną zasadą: najpierw lewe poddrzewo, potem korzeń a potem prawe poddrzewo.)
Napisz procedurę \(\mathtt{rekonstrukcja}: \mathtt{int~array} \to \mathtt{int~heap}\), która mając daną tablicę z obejściem stogu odtworzy go.
rotacja\,:\,char array -> int -> unit
, która rotuje daną tablicę o zadaną liczbę miejsc cyklicznie w prawo, tzn.:rotacja [|'a'; 'l'; 'a'; 'm'; 'a'; 'k'; 'o'; 't'; 'a'|] 4}
[|'k'; 'o'; 't'; 'a'; 'a'; 'l'; 'a'; 'm'; 'a'|]
.
slowa [|'a'; 'b'; 'b'; 'a' ;'b'; 'a'; 'c'; 'a'; 'b'; 'a'; 'b'; 'a'; 'b'|] = 6
.
minmax : int array -> int * int
, która znajduje minimum i maksimum w takiej tablicy.
Napisz procedurę \(\mathtt{rejs}: \mathtt{int} \to (\mathtt{int} * \mathtt{int})\ \mathtt{list} \to \mathtt{int}\), która mając daną liczbę \(k\) oraz informacje o dostępności kolegów Tomka zwróci maksymalną długość rejsu. Jeżeli rejsu nie da się zorganizować, to poprawnym wynikiem jest 0.
Na przykład: \(\mathtt{rejs}\ 2\ [(12, 36); (48, 100); (28, 70); (50, 80); (0, 69); (25, 30)] = 42\).
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.
Rozwiązując to zadanie nie wolno Ci tworzyć żadnych własnych procedur rekurencyjnych.
Zamiast tego powinieneś użyć konstrukcji imperatywnych.
type 'a option = None | Some of 'a type 'a elem = {v: 'a; mutable next: 'a lista} and 'a lista = 'a elem option
type elem = { x: int; mutable prev: elem list };; type lista = elem list;;
Napisz procedurę \(\mathtt{ustaw} : \mathtt{lista} \to \mathtt{unit}\), która dla danej listy \([x_1; x_2; \dots; x_n]\) typu lista
, tak ustawia pola prev
, aby (dla \(i=1,2,\dots, n\)) prowadziło ono z elementu \(x_i\) do listy zaczynającej się w elemencie \(x_{n-i+1}\).
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łpka trzyma 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
,Pola niezerowe stykające się bokami tworzą jedno złoże. Odwierty znajdujące się w danym złożu pozwalają pobrać tyle gazu ile pól obejmuje złoże, po czym ilość gazu we wszystkich polach złoża maleje o 1. Może to spowodować zmniejszenie lub podzielenie się złoża. Dalej odwierty pozwalają pobierać gaz z tak zmienionych złóż.
Napisz procedurę \(\mathtt{gaz}: \mathtt{int~array~array} \to \mathtt{int}\), która obliczy łączną ilość gazu, jaką wydobędą odwierty.
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.
i := 0; d := 1; s := 1; while !s <= !x do d := !d + 2; s := !s + !d; i := !i + 1 done;
\(\{ !i = \lfloor{\sqrt{!x_0}}\rfloor \}\)
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)\).
Masz daną prostokątną tablicę zawierającą dodatnie liczby całkowite. Jest to mapa sadzawki, a liczby reprezentują moment, w którym lód w danym miejscu pęka.
Napisz procedurę \(\mathtt{sadzawka}: \mathtt{int~array~array} \to (\mathtt{int} * \mathtt{int}) \to \mathtt{int}\), która mając daną taką tablicę oraz współrzędne miejsca, w którym bawi się Jaś, wyznaczy moment, w którym Jaś straci możliwość wyjścia na brzeg (bo albo zostanie odcięty od brzegu spękanym lodem, albo wpadnie do wody).
Możesz założyć, że:
bool array array
.true
reprezentują ląd, a false
wodę. Wyspy na oceanie są wyspami rzędu 1.
Na wyspach rzędu 1 mogą znajdować się jeziora rzędu 1, na nich mogą znajdować się wyspy rzędu 2 itd.
Ogólnie:
Pola wody przylegające do siebie bokami lub rogami należą do tego samego jeziora (lub oceanu).
Pola lądu przylegające do siebie bokami należą do tej samej wyspy.
Napisz procedurę \(\mathtt{wyspa}: \mathtt{bool\ array\ array} \to \mathtt{int}\), która dla danej mapy zwróci maksymalny rząd wyspy na mapie.
Jeżeli na oceanie nie ma żadnej wyspy, to procedura powinna zwrócić 0.
Przykład: 69.793N, 108.241W.
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.
Failure
.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.
Napisz procedurę \(\mathtt{energia}: \mathtt{int} \to \mathtt{int~array} \to \mathtt{int} \to \mathtt{int~array}\), która na podstawie liczby \(k\), tablicy \(t\) oraz energii \(e\) wyznaczy orbity, na których powinny znajdować się elektrony w atomie bajtonium tak, żeby ich łączna energia wynosiła \(e\). Wynikiem powinna być tablica \(k\) numerów powłok z elektronami, uporządkowana rosnąco. Jeżeli uzyskanie danej energii nie jest możliwe, wynikiem powinna być pusta tablica (\([||]\)).
Przykład: wynikiem energia 4 [|2; 8; 12; 12; 20|] 42
powinno być [|0; 1; 2; 4|]
lub [|0; 1; 3; 4|]
, natomiast energia 4 [|2; 8; 12; 12; 20|] 60 = [||]
.
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.
Napisz procedurę \(\mathtt{segment}: \mathtt{int~array} \to \mathtt{int}\), która dla danej tablicy \(t\) wyznaczy największy rząd występującego w niej segmentu Fibonacciego.
Jeżeli nie występuje w niej żaden taki segment, to poprawnym wynikiem jest zero.
Przykład: segment [| 1; 3; 2; 3; 4; 5; 3; 6|] = 7
. Segment Fibonacciego rzędu 7, to \([|3; 2; 3; 4; 5|]\) (\(Fib_7 = 13\)).
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.
Napisz funkcję \(\mathtt{liczba\_prob} : \mathtt{int} \to \mathtt{int} \to \mathtt{int}\) taką, że \(\mathtt{liczba\_prob}\ n\ k\) zwróci najmniejszą (pesymistycznie) liczbę prób zrzucania jaj potrzebną do wyznaczenia \(p\) mając do dyspozycji \(k\) jaj.
Inaczej mówiąc, szukamy takiej strategii wyznaczenia \(p\), która w najgorszym przypadku będzie wymagać jak najmniejszej liczby zrzucań jajek.
Uwagi:
Na przykład: \(\mathtt{liczba\_prob}\ n\ 1 = n + 1\), \(~~\mathtt{liczba\_prob}\ 4\ 2 = 3\).
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
.
Równoważne zadanie pojawiło się na konkursie USACO Open Gold w 2009\,r. i zostało opisane w
Delcie.
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).
moc [(2, 5, 10), (10, 12, 4), (-1, 2, 8), (6, 9, 10), (9, 15, 20), (4, 7, 8), (0, 12, 100), (8, 10, 4), (-2, 2, 6)] (1, 11) = 42
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)\).
Przed wjazdem na most zostaną ustawione dwa ograniczenia: maksymalny dopuszczalny ciężar pojazdu\(C\) jaki może wjechać na most, oraz minimalna odległość\(D\) jaką należy zachować między pojazdami.
Jeśli dwie kolejne podpory znajdują się w odległości\(X\), to odcinki łączącego je mostu muszą mieć wytrzymałość przynajmniej \(\lceil \frac{X}{D}\rceil \cdot C\).
Ograniczenie\(C\) będzie równe minimalnej wytrzymałości całego mostu.
Twoim zadaniem jest wyznaczenie minimalnej możliwej wartości ograniczenia\(D\), jakie można uzyskać odpowiednio rozmieszczając podpory.
Napisz procedurę\(\mathtt{most}: \mathtt{int~array} \to \mathtt{int} \to \mathtt{int}\), która mając dane wytrzymałości odcinków mostu oraz liczbę podpór\(K\) wyznaczy minimalną możliwą wartość ograniczenia\(D\).
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}.
Napisz procedurę \(\mathtt{ciag} : \mathtt{int} \to \mathtt{int~list}\),
która dla danej dodatniej liczby całkowitej $n$ wyznaczy najkrótszy taki
ciąg \((x_1, x_2, \dots, x_k)\), że \(x_k = n\).
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]
.
ile [(1, 4); (7, 9); (-2, 3); (4, 9); (9, 12); (0, 5); (3, 7)] (-1, 11) = 4
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?
Napisz procedurę \(\mathtt{lamiglowka} : \mathtt{int~list} \to \mathtt{int}\), która dla danej listy \(l\) wyznaczy minimalną długość listy jaką można uzyskać.
Na przykład, \(\mathtt{lamiglowka}\ [4; 3; 4; 5; 12; 2; 3; 1; 6; 1] = 4\).
Dysponujesz \(k\) kotami (\(k \le n\)).
Twoim zadaniem jest takie rozmieszczenie kotów w korytarzu, żeby złapały jak najwięcej myszy.
Każdy kot może pilnować ustalonego przez Ciebie spójnego fragmentu korytarza (na przykład, może mieć założoną smycz odpowiedniej długości, przymocowaną do podłogi pośrodku pilnowanego fragmentu korytarza).
Fragmenty korytarza pilnowane przez różne koty nie mogą zachodzić na siebie (żeby koty się nie pobiły a smycze nie poplątały), choć mogą się stykać.
Niektóre fragmenty korytarza mogą pozostać niepilnowane przez żadnego kota.
Kot, który pilnuje fragmentu korytarza od \(i\) do \(j\) metrów od wejścia (dla \(i < j\)), na którym znajduje się \(s = a.(i) + a.(i+1) + \cdots + a.(j-1)\) myszy, złapie: \(\max(s - (j-i-1)^2, 0)\) myszy.
Na przykład, dla \(k=2\) oraz \(a = [|1; 5; 1; 4; 3; 2; 7; 0|]\) poprawnym wynikiem jest 14, jeden kot może pilnować fragmentu korytarza od 1 do 4 metrów od wejścia (łapiąc 6 z 10 myszy), a drugi może pilnować fragmentu korytarza od 4 do 7 metrów od wejścia (łapiąc 8 z 12 myszy).
Napisz procedurę \(\mathtt{myszy}: \mathtt{int} \to \mathtt{int~array} \to \mathtt{int}\), która dla danej liczby \(k \ge 0\) oraz tablicy \(a\), wyznaczy maksymalną liczbę myszy, jakie złapią koty.
Rozwiązanie tego zadania zostało omówione w Delcie
[15; 11; 7; 5; 3; 1]
.
Napisz procedurę \(\mathtt{ciag} : \mathtt{int} \to \mathtt{int~list}\), która dla danej dodatniej liczby całkowitej \(n\) wyznaczy najkrótszy taki ciąg \((x_1, x_2, \dots, x_k)\), że \(x_k = n\).
Na przykład \texttt{ciag 42} powinno zwrócić \([1; 2; 42]\) lub \([1; 21; 42]\).
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]\).
Napisz procedurę \(\mathtt{ciag} : \mathtt{int} \to \mathtt{int~list}\), która dla danej dodatniej liczby całkowitej \(n\) wyznaczy najkrótszy taki ciąg \((x_1, x_2, \dots, x_k)\), że \(x_k = n\).
Możesz założyć, że dane są procedury:
rozklad : int * int → int list
, która dla danych liczb \(l\) i \(m\) wyznaczy szukany ciąg mianowników \([s_1;\dots;s_k]\).
Napisz procedurę \(\mathtt{rozklady} : \mathtt{int} \to \mathtt{int}\), która dla danej liczby \(x\)
policzy na ile różnych sposobów można przedstawić \(x\) jako sumę różnych (dodatnich) liczb Fibonacciego.
Na przykład \(\mathtt{rozklady}\ 42 = 6\).
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]]
.
autobusy [|1; 0; 2; 1; 2; 0; 1; 2; 6; 0; 6; 0|] [|2; 6; 8; 11|] = 7}
.
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\).
Rozwiązania
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.
Napisz procedurę \(\mathtt{minrot}: \mathtt{char~array} \to \mathtt{int}\),
która dla danej tablicy znaków \(a\) zwróci taką liczbę \(i\), że rotacja tablicy \(a\) o \(i\) pozycji jest najmniejsza w porządku leksykograficznym,
spośród wszystkich jej rotacji cyklicznych.
podsłowo : char array → (int * int)
,Dla pustej tablicy poprawnym wynikiem jest (0,0).