Aprajafalva csatornahálózata

Használható segédanyagok: Haskell könyvtárainak dokumentációja, (lokális!) Hoogle, a tárgy honlapja és a BE-AD rendszerbe feltöltött beadandók. Ha bármilyen kérdés, észrevétel felmerül, azt a felügyelőknek kell jelezni, nem a diáktársaknak!

A feladatok egymásra épülnek, ezért érdemes ezeket a megadásuk sorrendjében megoldani, de legalább megérteni az aktuális feladatot megelőző feladatokat! A függvények definíciójában lehet, sőt javasolt is alkalmazni a korábban definiált függvényeket (függetlenül attól, hogy sikerült-e azokat megadni).

Tekintve, hogy a tesztesetek, bár odafigyelés mellett íródnak, nem fedik le minden esetben a függvény teljes működését, határozottan javasolt még külön próbálgatni a megoldásokat beadás előtt, vagy megkérdezni a felügyelőket!

Részpontszámokat csak az elégséges szint elérése után lehet kapni!

A feladat összefoglaló leírása

Aprajafalva csatornahálózata az évek során eléggé kapkodva készült el, komolyabb tervezés sosem volt. Ezen a kaotikus állapoton szeretnénk most segíteni a meglévő csatornák feltérképezésével, elemzésével, illete egy új hálózat javaslatának kidolgozásával.

A csatornahálózatot vezetékek alkotják, amelyek részekből állnak és azok pedig csomópontokat kötnek össze. Egy csomópontot mindig annak a törpnek a nevével azonosítunk, amelynek a közelében található. A vezetékeket mint a csomópontok listáját ábrázoljuk. Ezeket az elnevezéseket és definíciókat fogalmazzuk meg az alábbi típusszinonimákkal:

type Joint = String
type Pipe  = [Joint]

Aprajafalán az idők során számos ilyen vezeték épült ki, melyek egymástól mindig függetlenek, azaz sosem találkoznak, nincs közös részük sem. Az viszont előfordulhat, hogy két csomópont között több, egymástól független vezetékszakasz is kiépítésre került a falu hosszú történelme során.

Példák

Tekintsünk néhány példát a vezetékekre:

pipe1 :: Pipe
pipe1 = ["Hami", "Trefi", "Torpilla", "Dulifuli", "TorpDerito"]
pipe2 :: Pipe
pipe2 = ["Epitorpesz", "Dulifuli", "TorpDerito"]
pipe3 :: Pipe
pipe3 = ["Almoska", "Torpilla", "TorpDerito"]
pipe4 :: Pipe
pipe4 = ["Gyapjas", "Gyengi", "Trefi"]
pipe5 :: Pipe
pipe5 = ["Dulifuli", "Torpilla", "TorpDerito"]

Valamint vezetékek halmazára:

pipes1 :: [Pipe]
pipes1 = [pipe1, pipe2, pipe3, pipe4, pipe5]

SmurfVillage660a3c43915bc4d382ac13ccc76d3ab9.png

pipes2 :: [Pipe]
pipes2 = [pipe1, pipe2, pipe3, pipe4]

SmurfVillage15276a5e8d615c9a72e377ee8e1c4509.png

Szennyvíz csatorna-e? (1 pont)

Sajnos a vezetékek felmérése során figyelmetlenek voltak a törpök, ezért előfordulhat, hogy nem minden vezeték a csatornahálózathoz tartozik.

Egy csatornahálózathoz tartozó vezeték arról ismerhető fel, hogy mindig a falu határában található “TorpDerito” nevű helyen végződik.

isSewagePipe :: Pipe -> Bool

Test>
True :: Bool
Test>
False :: Bool
Test>
⊥₁ :: Bool
⊥₁: isSewagePipe: Empty pipe
CallStack (from HasCallStack):
  error, called at ./SmurfVillage.lhs:130:21 in main:SmurfVillage

Vezetékek szakaszokra bontása (2 pont)

Bontsunk egy vezetéket szakaszokra! Egy szakasz a vezetéknek azon legrövidebb része, amely két csomópontot köt össze úgy, hogy közben nem halad át másik csomóponton.

Egy szakaszt az alábbi hármassal reprezentálunk:

type Thickness = Int
type Section   = (Joint, Joint, Thickness)

ahol az aktuális szakasz az első csomópontból indul és egészen a másodikig tart, a harmadik mező pedig a szakasz csővastagságát tárolja (ez egyelőre minden szakaszon egységesen 1).

decomposeIntoSections :: Pipe -> [Section]

Test>
[("Hami", "Trefi", 1), ("Trefi", "Torpilla", 1), ("Torpilla", "Dulifuli", 1), ("Dulifuli", "TorpDerito", 1)] :: [Section]
Test>
[("Almoska", "Torpilla", 1), ("Torpilla", "TorpDerito", 1)] :: [Section]
Test>
[] :: [Section]
Test>
[] :: [Section]

Az egyforma szakaszok összevonása – első lépés (2 pont)

Vonjuk össze a párhuzamos szakaszokat! Az első lépésben szakaszok egy listáját szeretnénk egyesíteni a következők szerint:

joinSections :: [Section] -> Section

Test>
("Almoska", "Torpilla", 1) :: Section
Test>
("Almoska", "Torpilla", 2) :: Section
Test>
("Almoska", "Torpilla", 4) :: Section
Test>
⊥₁ :: Section
⊥₁: joinSections: cannot be joined
CallStack (from HasCallStack):
  error, called at ./SmurfVillage.lhs:178:56 in main:SmurfVillage

Az egyforma szakaszok összevonása – második lépés (3 pont)

Az összevonást minden olyan esetben tudjuk még folytatni, ha két szakasz a vastagságukban tér el. Egy összevonás eredményeképpen kapott új szakasz vastagsága az összevontak vastagságának összege.

mergeSections :: [Section] -> [Section]

Test>
[("Dulifuli", "TorpDerito", 1), ("Hami", "Trefi", 1), ("Torpilla", "Dulifuli", 1), ("Trefi", "Torpilla", 1)] :: [Section]
Test>
[("Almoska", "Torpilla", 1), ("Dulifuli", "TorpDerito", 1), ("Hami", "Trefi", 1), ("Torpilla", "Dulifuli", 1), ("Torpilla", "TorpDerito", 1), ("Trefi", "Torpilla", 1)] :: [Section]
Test>
[("Dulifuli", "TorpDerito", 2), ("Epitorpesz", "Dulifuli", 1), ("Hami", "Trefi", 1), ("Torpilla", "Dulifuli", 1), ("Trefi", "Torpilla", 1)] :: [Section]
Test>
[("Almoska", "Torpilla", 1), ("Dulifuli", "TorpDerito", 2), ("Dulifuli", "Torpilla", 1), ("Epitorpesz", "Dulifuli", 1), ("Gyapjas", "Gyengi", 1), ("Gyengi", "Trefi", 1), ("Hami", "Trefi", 1), ("Torpilla", "Dulifuli", 1), ("Torpill" ++ […, ……], "TorpDe" ++ […, ……], 2), ("Trefi", "Torpil" ++ […, ……], 1)] :: [Section]

Hálózatterv készítése (1 pont)

type Network = [Section]

Ha kiszűrjűk a vezetékek közül azokat, amelyeket nem a szennyvíz elvezetésére használnak, azokat felbontjuk szakaszokra, majd egyesítjük a párhuzamos szakaszokat, akkor megkapjuk Aprajafalva új csatornahálózatának tervét.

planFor :: [Pipe] -> Network

Test>
[("Almoska", "Torpilla", 1), ("Dulifuli", "TorpDerito", 2), ("Dulifuli", "Torpilla", 1), ("Epitorpesz", "Dulifuli", 1), ("Hami", "Trefi", 1), ("Torpilla", "Dulifuli", 1), ("Torpilla", "TorpDerito", 2), ("Trefi", "Torpilla", 1)] :: Network
Test>
[("Almoska", "Torpilla", 1), ("Dulifuli", "TorpDerito", 2), ("Epitorpesz", "Dulifuli", 1), ("Hami", "Trefi", 1), ("Torpilla", "Dulifuli", 1), ("Torpilla", "TorpDerito", 1), ("Trefi", "Torpilla", 1)] :: Network

Előző csomópontok (1 pont)

Adott hálózat esetében meg tudjuk keresni, hogy egy csomópontba honnan futnak be vezetékek. Ezek azok a csúcsok lesznek, amelyeket egy szakasz köt a vizsgált csomóponthoz.

Megjegyzés: Csak a közvetlen megelőző csomópontokat keressük meg!

precedingJoints :: Network -> Joint -> [Joint]

Test>
["Hami"] :: [Joint]
Test>
["Almoska", "Dulifuli", "Trefi"] :: [Joint]
Test>
[] :: [Joint]

Megépíthető-e? (3 pont)

Minden csatornahálózat — még Aprajafalván is — akkor jó, ha megépíthető, vagyis, ha nem tartalmaz köröket. Azaz minden benne szereplő csomópontra igaz, hogy nem lehet hozzá visszajutni a hálózat szakaszait követve, figyelembevéve azok irányát is.

Készítsük el a isConstructible függvényt, amely a teljes hálózat, illetve kiindulási csomópontok egy listája alapján eldönti, hogy a hálózatot a kapott csomópontokig (pontosabban a hálózatnak azon része, melyet el tudunk érni a csomópontból, az egyes szakaszokon visszafelé haladva) vizsgálva megépíthető-e az adott rész!

Figyeljük meg, hogy például az isConstructible pipes2 ["Trefi"] hívással az alábbi csomópontok, illetve a hozzájuk tartozó szakaszok megépíthetőségét szeretnénk vizsgálni: "Trefi", "Hami", "Gyengi", "Gyapjas".

A teljes hálózat megépíthetőségét úgy tudjuk vizsgálni, hogy a kiindulási csomópontok listájába egyedül a "TorpDerito" helyet vesszük fel.

Tipp: Rekurzívan induljunk el visszafelé az éleken (itt a precedingJoints segíthet), jegyezzük fel az egyes utakon érintett csomópontokat (erre alkalmas a listaként megadott második paraméter). Ha olyannal találkozunk, amit egyszer már feljegyzetünk, akkor megállapíthatjuk, hogy kört találtunk.

isConstructible :: Network -> [Joint] -> Bool

Test>
False :: Bool
Test>
True :: Bool

Pontozás (elmélet + gyakorlat)