Is-sajf li għadda ħadt sehem fih
Bħala parteċipant fil-Google Summer of Code 2019, għamilt proġett fil-librerija
F'din il-kariga ser nitkellem dwar l-implimentazzjoni tiegħi ta 'algoritmu għall-iċċekkjar ta' graff għall-bipartiteness f'Haskell. Anke jekk l-algoritmu huwa wieħed mill-aktar bażiċi, l-implimentazzjoni tiegħu sabiħa fi stil funzjonali ħaditli diversi iterazzjonijiet u kienet teħtieġ ħafna xogħol. Bħala riżultat, ikkonċentejt fuq implimentazzjoni bi transformers monad.
Dwar lili nnifsi
Jisimni Vasily Alferov, jien student tar-raba' sena f'San Pietruburgu HSE. Aktar kmieni fil-blog ktibt
Dwar l-implimentazzjoni tal-algoritmu
Daħla
L-istudenti li qed jipparteċipaw fil-programm huma mħeġġa bil-qawwa biex jagħmlu blog. Huma pprovdewni pjattaforma għall-blog
Tista 'tinstab Pull Request bil-kodiċi inkwistjoni
Tista' taqra dwar ir-riżultati tax-xogħol tiegħi (bl-Ingliż)
Din il-kariga tassumi li l-qarrej huwa familjari mal-kunċetti bażiċi fl-ipprogrammar funzjonali, għalkemm ser nipprova nfakkar it-termini kollha użati meta jasal iż-żmien.
Iċċekkjar graphs għal bipartiteness
Algoritmu għall-iċċekkjar ta 'graff għal bipartiteness normalment jingħata f'kors dwar algoritmi bħala wieħed mill-algoritmi tal-graff l-aktar sempliċi. L-idea tiegħu hija sempliċi: l-ewwel aħna b'xi mod inpoġġu vertiċi fis-sehem tax-xellug jew tal-lemin, u meta jinstab tarf konfliġġenti, aħna nasserixxu li l-graff mhix bipartita.
Ftit aktar dettall: l-ewwel inpoġġu xi vertiċi fis-sehem tax-xellug. Ovvjament, il-ġirien kollha ta 'dan il-vertiċi għandhom ikunu fil-lobu tal-lemin. Barra minn hekk, il-ġirien kollha tal-ġirien ta 'dan il-vertiċi għandhom ikunu fil-lobu tax-xellug, eċċ. Aħna nkomplu jassenjaw ishma għal vertiċi sakemm għad hemm vertiċi fil-komponent konness tal-vertiċi li bdejna bih li ma nkunux assenjati l-ġirien. Imbagħad nirrepetu din l-azzjoni għall-komponenti kollha konnessi.
Jekk ikun hemm tarf bejn vertiċi li jaqgħu fl-istess partizzjoni, mhuwiex diffiċli li ssib ċiklu fard fil-graff, li huwa magħruf ħafna (u pjuttost ovvjament) impossibbli f'graff bipartite. Inkella, għandna partizzjoni korretta, li jfisser li l-graff huwa bipartite.
Tipikament, dan l-algoritmu jiġi implimentat bl-użu
Għalhekk, wasalna għall-iskema li ġejja. Aħna jaqsmu l-vertiċi tal-graff bl-użu ta 'tfittxija fil-fond u nassenjaw ishma lilhom, billi nbiddlu n-numru tas-sehem hekk kif nimxu tul it-tarf. Jekk nippruvaw nassenjaw sehem lil vertiċi li diġà għandu sehem assenjat, nistgħu ngħidu b'mod sikur li l-graff mhix bipartita. Fil-mument li l-vertiċi kollha jiġu assenjati sehem u ħaresna lejn it-truf kollha, għandna partizzjoni tajba.
Purità tal-kalkoli
F'Haskell nassumu li l-kalkoli kollha huma nadif. Madankollu, kieku dan kien tassew il-każ, ma jkollna l-ebda mod kif nipprintjaw xejn fuq l-iskrin. Fuq kollox, nadif il-kalkoli huma tant għażżien li m'hemmx wieħed nadif raġunijiet biex tikkalkula xi ħaġa. Il-kalkoli kollha li jseħħu fil-programm huma b'xi mod sfurzati "mhux nadif" monad IO.
Monads huma mod biex tirrappreżenta l-kalkoli effetti f'Haskell. Li jispjega kif jaħdmu huwa lil hinn mill-ambitu ta 'din il-kariga. Deskrizzjoni tajba u ċara tista’ tinqara bl-Ingliż
Hawnhekk irrid nirrimarka li filwaqt li xi monadi, bħal IO, huma implimentati permezz tal-maġija tal-kompilatur, kważi l-oħrajn kollha huma implimentati f'softwer u l-kalkoli kollha fihom huma puri.
Hemm ħafna effetti u kull wieħed għandu l-monada tiegħu. Din hija teorija qawwija ħafna u sabiħa: il-monadi kollha jimplimentaw l-istess interface. Se nitkellmu dwar it-tliet monadi li ġejjin:
- Jew ea huwa kalkolu li jirritorna valur tat-tip a jew jitfa' eċċezzjoni tat-tip e. L-imġieba ta 'din il-monada hija simili ħafna għall-immaniġġjar tal-eċċezzjoni f'lingwi imperattivi: żbalji jistgħu jinqabdu jew jgħaddu. Id-differenza ewlenija hija li l-monad hija kompletament loġikament implimentata fil-librerija standard f'Haskell, filwaqt li l-lingwi imperattivi normalment jużaw mekkaniżmi tas-sistema operattiva.
- L-Istat sa huwa kalkolu li jirritorna valur tat-tip a u għandu aċċess għal stat mutabbli tat-tip s.
- Forsi a. Il-monade Forsi tesprimi komputazzjoni li tista 'tiġi interrotta fi kwalunkwe ħin billi tirritorna Xejn. Madankollu, se nitkellmu dwar l-implimentazzjoni tal-klassi MonadPlus għat-tip Forsi, li jesprimi l-effett oppost: huwa kalkolu li jista 'jiġi interrott fi kwalunkwe ħin billi jirritorna valur speċifiku.
Implimentazzjoni tal-algoritmu
Għandna żewġ tipi ta' dejta, Graph a u Bigraph ab, l-ewwel wieħed jirrappreżenta graphs b'vertiċi ttikkettjati b'valuri tat-tip a, u t-tieni jirrappreżenta graphs bipartite b'vertiċi tan-naħa tax-xellug ittikkettjati b'valuri tat-tip a u tal-lemin. -vertiċi tal-ġenb ttikkettjati b'valuri tat-tip b.
Dawn mhumiex tipi mil-librerija Alga. Alga m'għandhiex rappreżentazzjoni għal graffs bipartiti mhux diretti. Għamilt it-tipi bħal dan għaċ-ċarezza.
Ikollna bżonn ukoll funzjonijiet helper bil-firem li ġejjin:
-- Список соседей данной вершины.
neighbours :: Ord a => a -> Graph a -> [a]
-- Построить двудольный граф по графу и функции, для каждой вершины
-- выдающей её долю и пометку в новой доле, игнорируя конфликтные рёбра.
toBipartiteWith :: (Ord a, Ord b, Ord c) => (a -> Either b c)
-> Graph a
-> Bigraph b c
-- Список вершин в графе
vertexList :: Ord a => Graph a -> [a]
Сигнатура функции, которую мы будем писать, выглядит так:
type OddCycle a = [a]
detectParts :: Ord a => Graph a -> Either (OddCycle a) (Bigraph a a)
Huwa faċli li wieħed jara li jekk matul it-tfittxija fil-fond l-ewwel sibna tarf konfliġġenti, iċ-ċiklu fard jinsab fuq nett tal-munzell ta 'rikors. Għalhekk, biex nirrestawrawha, għandna bżonn naqtgħu kollox mill-munzell ta 'rikorsi sal-ewwel okkorrenza tal-aħħar vertiċi.
Aħna nimplimentaw tfittxija fil-fond billi nżommu firxa assoċjattiva ta 'numri ta' ishma għal kull vertiċi. Il-munzell ta 'rikorsjoni se jinżamm awtomatikament permezz tal-implimentazzjoni tal-klassi Functor tal-monada li għażilna: ser ikollna bżonn biss li npoġġu l-vertiċi kollha mill-mogħdija fir-riżultat ritornat mill-funzjoni rikorsiva.
L-ewwel idea tiegħi kienet li nuża l-Either monad, li tidher li timplimenta eżattament l-effetti li għandna bżonn. L-ewwel implimentazzjoni li ktibt kienet viċin ħafna għal din l-għażla. Fil-fatt, kelli ħames implimentazzjonijiet differenti f'punt wieħed u eventwalment issetilja fuq ieħor.
L-ewwel, irridu nżommu firxa assoċjattiva ta 'identifikaturi ta' ishma - din hija xi ħaġa dwar l-Istat. It-tieni, jeħtieġ li nkunu kapaċi nieqfu meta jinstab kunflitt. Dan jista 'jkun jew Monad għal Jew, jew MonadPlus għal Forsi. Id-differenza ewlenija hija li Jew jista 'jirritorna valur jekk il-kalkolu ma jkunx twaqqaf, u Forsi jirritorna biss informazzjoni dwar dan f'dan il-każ. Peress li m'għandniex bżonn valur separat għas-suċċess (diġà huwa maħżun fl-Istat), nagħżlu Forsi. U fil-mument meta għandna bżonn ngħaqqdu l-effetti ta 'żewġ monadi, joħorġu
Għaliex għażilt tip daqshekk kumpless? Żewġ raġunijiet. L-ewwelnett, l-implimentazzjoni tirriżulta li hija simili ħafna għal imperattiva. It-tieni, irridu nimanipulaw il-valur tar-ritorn f'każ ta 'kunflitt meta nirritornaw lura minn rikorsi biex nirrestawraw il-linja fard, li hija ħafna aktar faċli li tagħmel fil-Monade Forsi.
Għalhekk niksbu din l-implimentazzjoni.
{-# LANGUAGE ExplicitForAll #-}
{-# LANGUAGE ScopedTypeVariables #-}
data Part = LeftPart | RightPart
otherPart :: Part -> Part
otherPart LeftPart = RightPart
otherPart RightPart = LeftPart
type PartMap a = Map.Map a Part
type OddCycle a = [a]
toEither :: Ord a => PartMap a -> a -> Either a a
toEither m v = case fromJust (v `Map.lookup` m) of
LeftPart -> Left v
RightPart -> Right v
type PartMonad a = MaybeT (State (PartMap a)) [a]
detectParts :: forall a. Ord a => Graph a -> Either (OddCycle a) (Bigraph a a)
detectParts g = case runState (runMaybeT dfs) Map.empty of
(Just c, _) -> Left $ oddCycle c
(Nothing, m) -> Right $ toBipartiteWith (toEither m) g
where
inVertex :: Part -> a -> PartMonad a
inVertex p v = ((:) v) <$> do modify $ Map.insert v p
let q = otherPart p
msum [ onEdge q u | u <- neigbours v g ]
{-# INLINE onEdge #-}
onEdge :: Part -> a -> PartMonad a
onEdge p v = do m <- get
case v `Map.lookup` m of
Nothing -> inVertex p v
Just q -> do guard (q /= p)
return [v]
processVertex :: a -> PartMonad a
processVertex v = do m <- get
guard (v `Map.notMember` m)
inVertex LeftPart v
dfs :: PartMonad a
dfs = msum [ processVertex v | v <- vertexList g ]
oddCycle :: [a] -> [a]
oddCycle c = tail (dropWhile ((/=) last c) c)
Il-blokk fejn huwa l-qalba tal-algoritmu. Se nipprova nispjega x'qed jiġri ġewwa fih.
- inVertex hija l-parti ta 'depth-first search fejn inżuru l-vertiċi għall-ewwel darba. Hawnhekk nassenjaw numru ta 'sehem lill-vertiċi u nħaddmu onEdge fuq il-ġirien kollha. Dan huwa wkoll fejn nirrestawraw il-munzell tas-sejħa: jekk msum irritorna valur, aħna nimbottaw vertex v hemmhekk.
- onEdge hija l-parti fejn inżuru t-tarf. Huwa msejjaħ darbtejn għal kull tarf. Hawnhekk niċċekkjaw jekk il-vertiċi fuq in-naħa l-oħra nżarux, u nżuruh jekk le. Jekk iżuru, niċċekkjaw jekk it-tarf huwiex konfliġġenti. Jekk hu, nirritornaw il-valur - il-parti ta' fuq nett tal-munzell ta' rikorsi, fejn il-vertiċi l-oħra kollha mbagħad jitqiegħdu mar-ritorn.
- processVertex jiċċekkja għal kull vertiċi jekk tkunx ġiet miżjura u taħdem inVertex fuqu jekk le.
- dfs imexxi processVertex fuq il-vertiċi kollha.
Dak kollox.
Storja tal-kelma INLINE
Il-kelma INLINE ma kinitx fl-ewwel implimentazzjoni tal-algoritmu; dehret aktar tard. Meta ppruvajt insib implimentazzjoni aħjar, sibt li l-verżjoni mhux INLINE kienet notevolment aktar bil-mod fuq xi graffs. Meta wieħed iqis li semantikament il-funzjonijiet għandhom jaħdmu l-istess, dan issorprendi ħafna. Saħansitra barrani, fuq magna oħra b'verżjoni differenti ta 'GHC ma kien hemm l-ebda differenza notevoli.
Wara li qattajt ġimgħa naqra l-output tal-GHC Core, stajt nirranġa l-problema b'linja waħda ta 'INLINE espliċita. F'xi punt bejn GHC 8.4.4 u GHC 8.6.5 l-ottimizzatur waqaf jagħmel dan waħdu.
Ma stennejtx li niltaqa 'ma' ħmieġ bħal dan fl-ipprogrammar Haskell. Madankollu, anke llum, l-ottimizzaturi kultant jagħmlu żbalji, u huwa xogħolna li nagħtuhom ħjiel. Pereżempju, hawnhekk nafu li l-funzjoni għandha tkun inlined minħabba li hija inlined fil-verżjoni imperattiva, u din hija raġuni biex tagħti ħjiel lill-kompilatur.
X'ġara wara?
Imbagħad implimentajt l-algoritmu Hopcroft-Karp ma 'monadi oħra, u dak kien it-tmiem tal-programm.
Grazzi għal Google Summer of Code, ksibt esperjenza prattika fl-ipprogrammar funzjonali, li mhux biss għenitni nagħmel apprendistat f'Jane Street is-sajf ta' wara (m'inix ċert kemm hu magħruf dan il-post anke fost l-udjenza infurmata ta' Habr, iżda huwa wieħed tal-ftit fejn tista’ sajf biex tidħol fi programmazzjoni funzjonali), iżda introduċietni wkoll fid-dinja mill-isbaħ tal-applikazzjoni ta’ din il-paradigma fil-prattika, differenti b’mod sinifikanti mill-esperjenza tiegħi fil-lingwi tradizzjonali.
Sors: www.habr.com