Browse Source

add tests for exact match of grabbed sol/assignment

mj-deploy
Standa Lukeš 4 years ago
parent
commit
4093f6b63e
  1. 36
      frontend/src/tests/example_assignments/28-Z1-1.html
  2. 51
      frontend/src/tests/example_assignments/31-3-3.html
  3. 28
      frontend/src/tests/example_assignments/32-Z1-1.html
  4. 65
      frontend/src/tests/example_solutions/28-Z1-1.html
  5. 50
      frontend/src/tests/example_solutions/31-3-3.html
  6. 27
      frontend/src/tests/example_solutions/32-Z1-1.html
  7. 46
      frontend/src/tests/grabber.test.ts

36
frontend/src/tests/example_assignments/28-Z1-1.html

@ -0,0 +1,36 @@
<p>
Kevin otevřel obálku (kterou našel ve schránce) a vyndal z&nbsp;ní všechny papíry. Jeden
ho obzvláště zaujal (ten od KSP). Ihned si všiml, že (na začátku) obsahuje nějak
mnoho závorek. Tak by ho zajímalo, jestli tam (autoři zadání) neudělali nějakou
chybu. Pomůžete mu (s&nbsp;ověřením)?</p>
<p>
Pro danou posloupnost symbolů <tt>(</tt> a <tt>)</tt>, tedy otevíracích
a zavíracích závorek, najděte od začátku co nejdelší úsek, který je platným
uzávorkováním – tedy každá závorka má svoji do páru.</p>
<p>
<i>Tvar vstupu:</i>
Na prvním řádku dostanete číslo&nbsp;<span class="math">N</span>. Na dalších <span class="math">N</span>&nbsp;řádcích budou jednotlivé
testovací případy.</p>
<p>
<i>Tvar výstupu:</i>
Pro každý testovací případ vypište na řádek délku nalezeného uzávorkování.</p>
<p>
Slibujeme, že <span class="math">N</span> bude nejvýše <span class="math">1 000</span>, a každý řádek bude mít nejvýše <span class="math">10<sup>5</sup></span>&nbsp;symbolů.</p>
<div class="leftfloat" style="width: 48%;">
<i>Ukázkový vstup:</i>
<pre>3
(()())())()
((()())())()
(()()))()()()()()
</pre>
</div>
<div class="rightfloat" style="width: 48%;">
<i>Ukázkový výstup:</i>
<pre>8
12
6
</pre>
</div>
<div class="clearfloat"></div>
<p>
Uzavírající závorka na deváté pozici nemá svůj protějšek. Druhý řádek je celý platné uzávorkování.</p>

51
frontend/src/tests/example_assignments/31-3-3.html

@ -0,0 +1,51 @@
<p>
Na podlaze se nachází sesypaný hrách s&nbsp;popelem a&nbsp;mezi tím poskakuje několik
holoubků a&nbsp;vrabčáků. Chtějí Popelce pomoct vysbírat všechen hrách. Pro každou
kuličku hrachu chceme zjistit, zda ji nějaký ptáček dokáže sezobnout a&nbsp;přenést
do ošatky. Holoubci i&nbsp;vrabčáci se však pohybují každý jinak. Vrabčáci poskakují
rovně dopředu, dozadu, doleva i&nbsp;doprava, zato holoubci chodí šikmo do všech
čtyř stran.</p>
<p>
Celou situaci si lze představit jako rozmístění figurek na šachovnici.
Holoubci představují černé střelce, vrabčáci černé věže a&nbsp;kuličky hrachu pak
bílé pěšáky. Pro každou bílou figurku chceme zjistit, zda ji dokážeme v&nbsp;jednom
tahu nějakou černou figurkou vyhodit.</p>
<p>
<i>Formát vstupu:</i> Na prvním řádku vstupu se nachází dvě čísla <span class="math">B</span>&nbsp;a&nbsp;<span class="math">C</span>, a&nbsp;to
počet kuliček hrachu (neboli bílých figurek) a&nbsp;počet ptáčků (neboli černých
figurek). Na dalších <span class="math">B</span>&nbsp;řádcích se nachází souřadnice kuliček hrachu jako
dvojice čísel oddělených mezerou, číslo řádku a&nbsp;číslo sloupce udávajících, kde
se kulička nachází. Na dalších <span class="math">C</span>&nbsp;řádcích se pak nachází pozice ptáčků – každý
takový řádek obsahuje znak <tt>H</tt> nebo <tt>V</tt> a&nbsp;dvojici čísel udávajících
řádek a&nbsp;sloupec, kde ptáček stojí (opět vše oddělené mezerami). Znak&nbsp;<tt>H</tt>
značí holoubka (pohybuje se jako střelec) a&nbsp;znak&nbsp;<tt>V</tt> vrabčáka (pohybuje se
jako věž). Figurky na vstupu mohou být seřazené náhodně.</p>
<p>
<i>Formát výstupu:</i> Na <span class="math">B</span>&nbsp;řádků výstupu vypište (ve stejném pořadí, jako na
vstupu) pro každou kuličku hrachu <tt>ANO</tt> nebo <tt>NE</tt> podle toho, jestli
existuje nějaký holoubek nebo vrabčák, který může tuto kuličku hrachu jedním
tahem sezobnout.</p>
<div class="leftfloat" style="width: 48%;">
<i>Ukázkový vstup:</i>
<pre>4 3
0 1
0 2
0 3
2 2
V 0 0
H 1 3
V 3 3
</pre>
</div>
<div class="rightfloat" style="width: 48%;">
<i>Ukázkový výstup:</i>
<pre>ANO
ANO
NE
ANO
</pre>
<img class="rightfloat image" src="http://localhost/h/ulohy/31/t3133.png" alt="Šachovnice s&nbsp;příkladem" title="Šachovnice s&nbsp;příkladem">
</div>
<div class="clearfloat"></div>
<i>Poznámka:</i>
BUG

28
frontend/src/tests/example_assignments/32-Z1-1.html

@ -0,0 +1,28 @@
<p>
Kevin se po prázdninách zase vrací do školy. Jako obvykle dostal seznam pomůcek,
které si má obstarat. Malé sešity, velké sešity, pravítko, pastelky, barvy na
výtvarku, učebnice na češtinu a dějepis a možná i novou aktovku. To vypadá na
velký nákup!</p>
<p>
Kevin se tedy vydal do obchodu. Jak dává věci do košíku, píše si i seznam cen. V
tom si ale uvědomí, že má jen <span class="math">P</span> korun. Kolik nejméně z <span class="math">N</span> věcí v košíku musí
Kevin vyhodit, aby mu <span class="math">P</span> korun stačilo?</p>
<p>
<i>Tvar vstupu:</i> Na prvním řádku se nachází počet věcí v košíku <span class="math">N</span> a počet korun <span class="math">P</span>, které Kevin může
utratit. Na druhém řádku je pak seznam cen věcí v košíku, tedy <span class="math">N</span> kladných čísel
oddělených mezerami.</p>
<p>
<i>Tvar výstupu:</i> Vypište jedním číslem nejmenší počet věcí, které musí Kevin
z&nbsp;košíku vyhodit.</p>
<div class="leftfloat" style="width: 48%;">
<i>Ukázkový vstup:</i>
<pre>5 59
8 20 30 1999 40
</pre>
</div>
<div class="rightfloat" style="width: 48%;">
<i>Ukázkový výstup:</i>
<pre>2
</pre>
</div>
<div class="clearfloat"></div>

65
frontend/src/tests/example_solutions/28-Z1-1.html

@ -0,0 +1,65 @@
<p>
Nejprve si vyřešme jednodušší úlohu: jak o&nbsp;nějaké posloupnosti závorek
poznáme, jestli je (celá) správně uzávorkovaná? Určitě musí obsahovat
stejný počet levých a&nbsp;pravých závorek – jinak těžko mohou tvořit páry.</p>
<p>
Ovšem to nestačí. Uvažte třeba posloupnost <tt>())(</tt>. Ta obsahuje dvě
levé a&nbsp;dvě pravé závorky, ale korektním uzávorkováním určitě není.
Na třetí pozici se pokoušíme ukončit závorku „dřív, než začala“.</p>
<p>
Jak takovou situaci poznáme? Podíváme-li se na první tři znaky naší
posloupnosti, vidíme, že obsahují jednu levou závorku a&nbsp;dvě pravé.
Obecně problém nastane, pokud existuje v&nbsp;posloupnosti nějaké místo
takové, že v&nbsp;úseku od začátku po toto místo je více pravých závorek
než levých. Pak alespoň jedna z&nbsp;těch pravých nemá před sebou žádnou
levou, se kterou bychom ji mohli spárovat.</p>
<p>
Pokud posloupnost obsahuje stejně levých a&nbsp;pravých závorek a nenastane
problém popsaný výše, pak už si snadno rozmyslíte, že je vždy jde
správně spárovat, a&nbsp;tedy uzávorkování je korektní.</p>
<div class="center"><img class="image" src="http://localhost/img/hippo_table_pilot.png" alt="Ilustrace: Hroší pilot" title="Ilustrace: Hroší pilot"></div>
<p>
Nyní k původní úloze. Představme si, že si chceme označit všechna
místa v&nbsp;posloupnosti, kde může končit korektní uzávorkování (začínající
na začátku). Třeba pro příklad ze zadání to budou následující místa
(označena hvězdičkou): <tt>(()())*()*)()</tt>.</p>
<p>
Jak je najdeme? Budeme postupně procházet posloupnost od začátku a&nbsp;průběžně
si počítat, kolik levých a&nbsp;pravých závorek už jsme viděli. Pokud narazíme
na místo, kde jsme napočítali víc pravých než levých, můžeme rovnou skončit.
Už víme, že takové uzávorkování korektní není, a&nbsp;přidáním libovolných dalších
závorek na konec už nemůžeme napravit chybějící závorky nalevo od aktuální
pozice.</p>
<p>
Pokud tato situace ještě nenastala a&nbsp;objevíme místo, před kterým je počet
levých a&nbsp;pravých závorek stejný, označíme si jej. Podle toho, co jsme si
řekli výše, uzávorkování končící na tomto místě je korektní.</p>
<p>
Ukažme si průběh algoritmu na předchozím příkladu:</p>
<div class="center">
<table class="centertable borderedtable">
<tbody><tr><td></td><td> (</td><td> (</td><td> ) </td><td> (</td><td> ) </td><td> ) </td><td> (</td><td> ) </td><td> ) </td><td> (</td><td> )</td></tr>
<tr><td>Pozice </td><td> 1 </td><td> 2 </td><td> 3 </td><td> 4 </td><td> 5 </td><td> 6 </td><td> 7 </td><td> 8 </td><td> 9 </td><td> 10 </td><td> 11</td></tr>
<tr><td>Počet levých </td><td> 1 </td><td> 2 </td><td> 2 </td><td> 3 </td><td> 3 </td><td> 3 </td><td> 4 </td><td> 4 </td><td> 4 </td><td> - </td><td> -</td></tr>
<tr><td>Počet pravých </td><td> 0 </td><td> 0 </td><td> 1 </td><td> 1 </td><td> 2 </td><td> 3 </td><td> 3 </td><td> 4 </td><td> 5 </td><td> - </td><td> -</td></tr>
<tr><td>Rozdíl </td><td> 1 </td><td> 2 </td><td> 1 </td><td> 2 </td><td> 1 </td><td> 0 </td><td> 1 </td><td> 0 </td><td> -1 </td><td> - </td><td> -</td></tr>
<tr><td>Akce </td><td> </td><td> </td><td> </td><td> </td><td> </td><td> * </td><td> </td><td> * </td><td> <tt>X</tt> </td><td> </td><td></td></tr>
</tbody></table>
</div>
<p>
Hvězdička znamená označení místa s&nbsp;korektním uzávorkováním
a&nbsp;<tt>X</tt> konec prohledávání, když žádné další uzávorkování korektní být
nemůže.</p>
<p>
Na konci jen vypíšeme nejpravější označené místo. Všimněme si, že
si nemusíme ukládat všechna označená místa, stačí si vždy pamatovat
poslední dosud nalezené. Další drobné zjednodušení je místo dvou
počítadel použít jen jedno, uchovávající rozdíl počtu dosud načtených
levých a&nbsp;pravých závorek (řádek „rozdíl“ v&nbsp;tabulce výše). Pak označujeme,
když je toto počítadlo rovné nule, a&nbsp;končíme, pokud klesne pod nulu.</p>
<p><a href="http://localhost/z/ulohy/28/28Z11-1.py">Program (Python 3) – počítání obou druhů závorek</a></p>
<p><a href="http://localhost/z/ulohy/28/28Z11-2.py">Program (Python 3) – stačí nám počítat jen levé</a></p>
<p class="author"><a href="http://localhost/kontakty/organizatori/">Filip Štědronský</a></p>
<p>
<i>Pokud máte pocit, že jste zde dříve viděli něco jiného, nemýlíte se. Věříme, že toto řešení pro vás bude stravitelnější.</i></p>
<hr class="clearfloat">

50
frontend/src/tests/example_solutions/31-3-3.html

@ -0,0 +1,50 @@
<p>
Pro každou z&nbsp;bílých figurek chceme zjistit, zda je ohrožována aspoň jednou
černou. Můžeme zkusit vyzkoušet pro každou dvojici bílé a černé figurky, zda se
ohrožují. Těchto dvojic je ovšem až kvadraticky mnoho a navíc se nám může stát, že
i když by se dvojice ohrožovala, tak se mezi nimi nachází další figurka, která
černé figurce z&nbsp;naší dvojice překáží ve výhledu a znemožňuje jí bílou figurku
sebrat. Protože ověřit, zda dvojici nepřekáží ve výhledu další figurka, trvá
lineárně dlouho, toto řešení je <span class="math">O(n<sup>3</sup>)</span>.</p>
<p>
Jelikož černé figurky jsou pouze věže a střelci, bílé figurky mohou být
ohroženy pouze z&nbsp;dohromady osmi směrů, kterými se černé dokážou pohybovat.
Řešení můžeme vylepšit tím, že budeme zjišťovat, zda je bílá figurka ohrožena,
pro každý směr zvlášť. Například pokud chceme pro každou bílou figurku zjistit,
zda je ohrožena zleva nebo zprava, stačí nám uvažovat pouze ty figurky, které
leží na stejném řádku. Navíc každá figurka má na svém řádku nejvýše dva
sousedy, jednoho zleva, druhého zprava. Stačí nám uvažovat jen tyto
sousedy, protože budou všem ostatním figurkám na řádku překážet ve výhledu. Pro
každou z&nbsp;lineárně mnoho bílých figurek tedy v&nbsp;lineárním čase najdeme ty
figurky, které s&nbsp;nimi sdílí řádek (pro ostatní směry ty, které sdílí sloupec či
diagonálu), v&nbsp;lineárním čase najdeme mezi nimi dva nejbližší sousedy bílé
figurky a pro ty vyzkoušíme, zda figurku neohrožují, to jest zda se jedná o&nbsp;černé figurky a jestli jsou typu, který se umí v&nbsp;tomto směru pohybovat. Celkově je toto řešení
<span class="math">O(n · (n + n))</span>, tedy <span class="math">O(n<sup>2</sup>)</span>.</p>
<p>
Abychom uměli rychle zjistit, které figurky leží na stejném řádku, můžeme si
nejprve všechny figurky seřadit podle jejich souřadnice řádku. V&nbsp;seřazeném seznamu figurek
leží figurky na stejném řádku vedle sebe. Pokud bychom navíc vzali
všechny figurky na stejném řádku a seřadili je podle souřadnice sloupce a
uložili do nějakého pole, budeme je mít uspořádané v&nbsp;tom pořadí, v&nbsp;jakém leží
na řádku za sebou. Pro libovolnou figurku na řádku tedy umíme najít její
sousedy prostě tak, že se podíváme v&nbsp;tomto uspořádaném poli na index o&nbsp;jedna
nižší a o&nbsp;jedna vyšší.</p>
<p>
Tato dvě setřídění můžeme provést zároveň. Primárně figurky setřídíme podle
souřadnice řádku, sekundárně pak podle souřadnice sloupce. Takové uspořádání,
kde třídíme primárně podle jednoho kritéria a sekundárně podle jiného, se
nazývá <i>lexikografické.</i> Nyní nám stačí projít setříděné pole a pro každou
bílou figurku se podívat na její dva sousedy a rozhodnout, zda ji neohrožují.
Také musíme dát pozor na to, že sousední figurky v&nbsp;setříděném poli figurek se
nemusí nacházet na stejném řádku. Pokud je soused bíle figurky na jiném řádku,
znamená to, že v&nbsp;daném směru se už na stejném řádku nenachází žádné figurky.
Také si všimněme, že tímto uspořádáním najdeme všechny takové figurky, které
bíle ohrožují ne jen z&nbsp;jednoho směru z&nbsp;osmi, ale ze dvou (zprava i zleva).</p>
<p>
Kompletní řešení pro každou ze čtyř „os“ (doleva-doprava, nahoru-dolů,
diagonálně doprava nahoru, diagonálně doprava dolů) lexikograficky figurky
uspořádá, uspořádané pole v&nbsp;lineárním čase projde a pro každou bílou figurku určí, zda
je ohrožována. Toto řešení má tedy časovou složitost <span class="math">O(n <span class="nomath">log</span> n)</span>.</p>
<p><a href="http://localhost/h/ulohy/31/3133.c">Program (C)</a></p>
<p class="author"><a href="http://localhost/kontakty/organizatori/">Kuba Pelc</a></p>
<hr class="clearfloat">

27
frontend/src/tests/example_solutions/32-Z1-1.html

@ -0,0 +1,27 @@
<p>
K&nbsp;tomu, abychom za věci utratili co možná nejméně, potřebujeme do košíku
postupně vybírat věci od té nejlevnější po tu nejdražší.</p>
<p>
Pole cen tedy vzestupně setřídíme a budeme jej postupně procházet, přičemž
si budeme pamatovat součet cen věcí, které při průchodu potkáme. Jakmile
při procházení přesáhne tento součet koruny vyhrazené na nákup, tak podle
pozice v&nbsp;poli víme, kolik věcí si můžeme koupit.</p>
<p>
Jelikož se však zadání ptá na počet věcí, které si koupit nemůžeme, tak jako
řešení vypíšeme počet věcí od pozice, kde jsme skončili s&nbsp;procházením, do konce
pole cen.</p>
<p>
Řešení je jistě správné, určitě se totiž nemůže stát, že bychom si mohli koupit
víc věcí, než nám vypíše algoritmus. Vyhodili jsme z&nbsp;košíku ty nejdražší věci,
které jsme mohli, takže nám jich určitě nestačilo vyhodit méně.</p>
<p>
Rychlost řešení ovlivní hlavně to, jak rychle dokážeme ceny setřídit. Existují
různé třídící algoritmy a pokud použijete nějaký algoritmus zabudovaný přímo
v&nbsp;programovacím jazyce, běží většinou v&nbsp;čase <span class="math">O(n <span class="nomath">log</span> n)</span>. Pokud jste si
implementovali vlastní jednoduché třídění, pravděpodobně bude mít časovou
složitost <span class="math">O(n<sup>2</sup>)</span>. O&nbsp;třídících algoritmech si můžete přečíst
v&nbsp;naší <a href="http://localhost/viz/kucharky/trideni">kuchařce o&nbsp;třídění</a>.</p>
<p><a href="http://localhost/z/ulohy/32/32Z11.py">Program (Python 3)</a></p>
<p><a href="http://localhost/z/ulohy/32/32Z11.cpp">Program (C++)</a></p>
<p class="author"><a href="http://localhost/kontakty/organizatori/">Tom Sláma</a></p>
<hr class="clearfloat">

46
frontend/src/tests/grabber.test.ts

@ -1,7 +1,8 @@
import * as g from '../ksp-task-grabber'
import { readFileSync } from 'fs';
import { readFileSync, readdirSync } from 'fs';
import { resolve } from 'path';
import type { TaskDescriptor, TasksFile } from '../tasks';
import { action_destroyer } from 'svelte/internal';
const node_fetch: any = require('node-fetch')
@ -41,9 +42,9 @@ describe('tasks assignment', () => {
for (const t of tasks.tasks) {
if (t.type != "open-data") continue;
test(`${t.id}`, async () => {
const assignment = await g.grabAssignment((t as any).taskReference)
expect((t as any).taskReference).toEqual(assignment.id)
test.concurrent(`${t.id}`, async () => {
const assignment = await g.grabAssignment(t.taskReference)
expect(t.taskReference).toEqual(assignment.id)
expect(assignment.points).toBeGreaterThanOrEqual(1)
expect(assignment.description.trim()).toBeTruthy()
expect(assignment.name.trim()).toBeTruthy()
@ -66,7 +67,7 @@ describe('tasks solutions', () => {
continue
}
test(`${t.id}`, async () => {
test.concurrent(`${t.id}`, async () => {
const sol = await g.grabSolution(t.taskReference)
expect(t.taskReference).toEqual(sol.id)
expect(sol.description.trim()).toBeTruthy()
@ -74,3 +75,38 @@ describe('tasks solutions', () => {
})
}
})
describe('task assignment (exact match)', () => {
const assignmentDir = resolve(__dirname, "example_assignments")
const assignmentFiles = readdirSync(assignmentDir)
for (const a of assignmentFiles) {
const [_, id] = /(.*?)\.html/.exec(a)!
test(id, async () => {
const assignment = await g.grabAssignment(id)
const expected = readFileSync(resolve(assignmentDir, a)).toString()
try {
expect(assignment.description.trim()).toEqual(expected.trim())
} catch(e) {
console.warn(assignment.description)
throw e
}
})
}
})
describe('task solution (exact match)', () => {
const assignmentDir = resolve(__dirname, "example_solutions")
const assignmentFiles = readdirSync(assignmentDir)
for (const a of assignmentFiles) {
const [_, id] = /(.*?)\.html/.exec(a)!
test(id, async () => {
const solution = await g.grabSolution(id)
const expected = readFileSync(resolve(assignmentDir, a)).toString()
try {
expect(solution.description.trim()).toEqual(expected.trim())
} catch(e) {
console.log(solution.description)
throw e
}
})
}
})