Wir lösen Yandex.Interview-Aufgaben in einem funktionalen Stil

Vor einigen Monaten erschien im Yandex-Blog ein Artikel, in dem die Passage des algorithmischen Abschnitts des Interviews erörtert wurde. In diesem Artikel wurde unter anderem ein Link zu einem speziellen Wettbewerb gegeben, der Aufgaben enthÀlt, die denen Àhneln, die Yandex seinen Kandidaten anbietet.


Nachdem ich mich im System registriert hatte, wurde ich sofort auf die FÀhigkeit aufmerksam, Probleme auf Haskell zu lösen. Tatsache ist, dass ich, obwohl ich gerne in dieser Sprache programmiere, nicht weiter fortgeschritten bin als die Implementierung von Aufgaben aus verschiedenen Kursen von Online-Bildungsplattformen. Nachdem ich entschieden hatte, dass ihre Lösung eine interessante Herausforderung sein kann und mein Niveau als Entwickler verbessern wird, fuhr ich fort, sie zu lösen.


Wen interessiert es, was letztendlich daraus wurde? Willkommen bei Cat.


A. Steine ​​und Schmuck


Es werden zwei Zeilen mit lateinischen Kleinbuchstaben angegeben: Zeichenfolge J und Zeichenfolge S. Die in Zeichenfolge J enthaltenen Zeichen sind „Juwelen“ und in Zeichenfolge S sind „Steine“. Es muss ermittelt werden, wie viele Zeichen aus S gleichzeitig „Juwelen“ sind. Einfach ausgedrĂŒckt, mĂŒssen Sie ĂŒberprĂŒfen, wie viele Zeichen von S in J enthalten sind.

Die erste Aufgabe ist ein AufwĂ€rmen, wir werden es „auf der Stirn“ lösen. Wir definieren die FunktionjuwelCount :: String -> String -> Int , die unter Verwendung der Faltung der vom zweiten Argument ĂŒbergebenen Liste alle FĂ€lle des in der ersten Liste verarbeiteten Elements zusammenfasst. FĂŒr diese Zwecke definieren wir die elemInt- Funktion basierend auf der elem- Funktion, die im Gegensatz zur letzten nicht True oder False, sondern die Zahl 0 oder 1 zurĂŒckgibt. In der Hauptfunktion mĂŒssen Sie nur zwei Zeilen lesen, sie an die entsprechende Funktion ĂŒbergeben und das Ergebnis drucken. Das Urteil des Testsystems ist in Ordnung, wir gehen zur zweiten Aufgabe ĂŒber.


jeweleryCount :: String -> String -> Int jeweleryCount j = foldr ((+).(elemInt j)) 0 where elemInt sx = fromEnum $ elem xs main :: IO () main = do j <- getLine s <- getLine print $ jeweleryCount js 

Der Quellcode zum Lösen dieser und anderer Aufgaben ist auch im Github-Repository verfĂŒgbar .


B. Aufeinanderfolgende Einheiten


Es ist erforderlich, die lÀngste Folge von Einheiten im BinÀrvektor zu finden und seine LÀnge auszudrucken.

Um dieses Problem zu lösen, implementieren wir eine rekursive Funktion, die die ĂŒbertragene Liste durchlĂ€uft und die LĂ€nge der erforderlichen Sequenz berechnet. Mit den Argumenten der Funktion ĂŒbergeben wir zusĂ€tzlich zur Liste selbst die aktuelle maximale LĂ€nge und die Anzahl der aufeinanderfolgenden Einheiten beim aktuellen Aufruf. Zuerst definieren wir die Rekursionsbasis auf der leeren Liste und dann den Rekursionsschritt selbst.


Um die Eingabedaten zu lesen, definieren wir die Funktion getUserInputs :: IO [Char] , in der wir zuerst die Zahl n - die GrĂ¶ĂŸe der Liste - lesen und dann mit dem Kombinator replicateM eine Funktion erhalten, die die Funktion << get> getLine n-mal aufruft und die Ergebnisse zu einer Liste zusammenfĂŒhrt .


 import Control.Monad (replicateM) onesCount :: [Char] -> Int onesCount xs = onesCount' xs 0 0 where onesCount' "" max curr | max > curr = max | otherwise = curr onesCount' (x:xs) max curr | x == '1' = onesCount' xs max $ curr + 1 | curr > max = onesCount' xs curr 0 | otherwise = onesCount' xs max 0 getUserInputs :: IO [Char] getUserInputs = do n <- read <$> getLine :: IO Int replicateM n $ head <$> getLine main :: IO () main = do xs <- getUserInputs print $ onesCount xs 

Wir senden die Entscheidung, das Urteil ist in Ordnung. Wir gehen weiter.


C. Doppelte Entfernung


Es wird ein Array von 32-Bit-Ganzzahlen angegeben, die in nicht abnehmender Reihenfolge angeordnet sind. Sie möchten alle Wiederholungen daraus entfernen.

Beginnen wir mit einer einfachen Implementierung. Wir definieren eine Anfangsfunktion, die eine Zahl liest, druckt und in die E / A-Monade eingeschlossen zurĂŒckgibt. Wir definieren auch die Funktion deleteDoubles :: Int -> Int -> IO () , die eine Zahl liest und nur druckt, wenn sie nicht dem zweiten Argument entspricht (wir ĂŒbergeben dort die im vorherigen Schritt gelesene Zahl). Danach ruft sich die Funktion rekursiv auf und fĂ€hrt damit mit der nĂ€chsten Nummer im Eingabestream fort. Die Rekursionsbasis ist die Anzahl der zu lesenden Zahlen. Wir werden das erste Argument ĂŒbergeben.


 import Control.Monad initial :: IO Int initial = do a <- read <$> getLine print a return a deleteDoubles :: Int -> Int -> IO() deleteDoubles 0 _ = return () deleteDoubles ta = do b <- read <$> getLine unless (a == b) $ print b deleteDoubles (t-1) b main :: IO () main = do t <- read <$> getLine unless (t < 1) $ initial >>= deleteDoubles (t-1) 

Wir senden die Lösung, sie besteht alle Tests und es scheint, dass wir mit der nÀchsten Aufgabe fortfahren können, aber meiner Meinung nach ist der rekursive Aufruf der in der E / A-Monade arbeitenden Funktion eher verwirrend als prÀgnant. Versuchen wir es zu verbessern.


Beachten Sie, dass Sie im Allgemeinen zuerst die gesamte Liste der Zahlen lesen können (wir verwenden den replicateM-Kombinator, der bereits mit der zweiten Aufgabe vertraut ist), ihn dann an eine reine Funktion ĂŒbergeben, die alle Wiederholungen herausfiltert, und schließlich das Ergebnis drucken.


 import Control.Monad deleteDoubles' _ [] = [] deleteDoubles' prev (x:xs) | prev /= x = x:(deleteDoubles' x xs) | otherwise = deleteDoubles' x xs deleteDoubles (x:xs) = x:deleteDoubles' x xs getUserInputs :: Int -> IO [Int] getUserInputs t = replicateM t $ read <$> getLine main :: IO () main = do t <- read <$> getLine unless (t < 1) $ (deleteDoubles <$> getUserInputs t) >>= mapM_ print 

Ich sende eine Lösung, und die erste EnttĂ€uschung ist, dass das Programm den 193-Test nicht besteht, da die Grenze des verwendeten Speichers ĂŒberschritten wird. Der Hauptfehler besteht darin, die gesamte Liste in den gesamten Speicher einzulesen. Wir werden versuchen, dies zu vermeiden und einen bestimmten Hybrid aus der ersten und zweiten Version implementieren.


Beachten Sie, dass das Entfernen von Duplikaten etwas an eine linksassoziative Faltung erinnert: Bei jedem Schritt berechnen wir eine Funktion, die abhĂ€ngig vom aktuell gelesenen Element und einem Teil des Ergebnisses im vorherigen Schritt zum Drucken entscheidet und dann zum nĂ€chsten Wertepaar ĂŒbergeht.


Eine Funktion, die das Ergebnis abhĂ€ngig von ihren Argumenten druckt oder nicht druckt und danach ihr zweites Argument zurĂŒckgibt, das in die E / A-Monade eingeschlossen ist, ist recht einfach. Nennen wir es Schritt:


 step :: Int -> Int -> IO Int step fst snd = unless (fst == snd) (print snd) >> return snd 

Wir haben herausgefunden, ob abhĂ€ngig von den ĂŒbergebenen Werten gedruckt wird oder nicht, aber wie organisiert man das Lesen? Dazu verwenden wir die monadische Faltungsfunktion foldM :: (faltbar t, Monade m) => (b -> a -> mb) -> b -> ta -> mb , die auf die Liste der Lesefunktionen anwendbar ist.
Nach Art der Funktion foldM stellen wir fest, dass bei jedem Schritt das „Auspacken“ des Ergebnisses der vorherigen Anwendung der Funktion unter der Haube von foldM selbst erfolgt. Daher mĂŒssen wir bei jedem Schritt nur eine monadische Berechnung des aktuellen Listenelements starten (tatsĂ€chlich die nĂ€chste Nummer lesen), indem wir den Bindungsoperator ( >> = ) verwenden und diese zusammen mit der vorherigen Nummer an Schritt ĂŒbergeben. Als Ergebnis erhalten wir das folgende Programm


 step :: Int -> Int -> IO Int step fst snd = unless (fst == snd) (print snd) >> return snd initial :: IO Int initial = do a <- read <$> getLine print a return a getUserInputs t = replicate t $ read <$> getLine main :: IO () main = do t <- read <$> getLine unless (t < 1) $ do init <- initial foldM_ ((=<<) . step) init $ getUserInputs (t-1) 

D. Erzeugung von Klammersequenzen


Gegeben eine ganze Zahl n. Es ist erforderlich, alle korrekten Klammersequenzen der LĂ€nge 2 ⋅ n abzuleiten, die lexikografisch geordnet sind (siehe https://ru.wikipedia.org/wiki/Lexographic_order ).
In der Aufgabe werden nur Klammern verwendet.
Es ist ratsam, eine Lösung zu erhalten, die in einer Zeit arbeitet, die proportional zur Gesamtzahl der korrekten Klammersequenzen in der Antwort ist, und gleichzeitig eine SpeicherkapazitÀt proportional zu n verwendet.

Diese Aufgabe wird, wie viele andere, bei der es notwendig ist, Sequenzen abzuleiten, die bestimmte Bedingungen erfĂŒllen (z. B. Aufgaben des MĂŒnztauschs, des Arrangierens von acht Königinnen und anderer, die hier ausfĂŒhrlicher gelesen werden können ), mit der Listenmonade kurz und bĂŒndig gelöst. Kurz gesagt, dieser Ansatz basiert auf der monadischen Bindung fĂŒr Listen, deren Bedeutung darin besteht, die fĂŒr jedes Element der Liste ausgefĂŒhrten Operationen zusammenzufĂŒgen.


Definieren Sie eine rekursive Funktion, die ':: Int -> Int -> [[Char]] generiert. Dabei wird die Anzahl der Klammern als zweites Argument und die Anzahl der offenen Klammern, die bereits als erstes Argument festgelegt wurden, verwendet. FĂŒr den Rekursionsschritt benötigen wir zwei Hilfsfunktionen: möglich - gibt eine Liste der Klammern zurĂŒck, die im nĂ€chsten Schritt platziert werden können, und Schritt - ruft die Funktion generate 'mit den erforderlichen Parametern rekursiv auf.


 import Control.Monad(mapM_) generate :: Int -> [String] generate = generate' 0 where generate' _ 0 = [[]] generate' an = [x:xs | x <- possible, xs <- step x] where step '(' = generate' (a + 1) (n - 1) step ')' = generate' (a - 1) (n - 1) possible | n == a = ")" | a == 0 = "(" | otherwise = "()" main :: IO () main = do n <- read <$> getLine let result = generate $ n * 2 mapM_ putStrLn result 

Wir senden die Lösung und verstehen, dass wir die EinschrĂ€nkung der vom Programm verwendeten Speichermenge nicht berĂŒcksichtigt haben. Die Lösung besteht den 14. Test nicht, da die Grenze des verwendeten Speichers ĂŒberschritten wurde.


Wir Ă€ndern die Funktion generate 'so, dass die gesamte Liste der korrekten Klammersequenzen nicht erstellt, sondern sofort auf dem Bildschirm angezeigt wird. Dazu mĂŒssen wir der Funktion das dritte Argument hinzufĂŒgen - ein Fragment der Sequenz, die fĂŒr den aktuellen Schritt erstellt wurde. Ich stelle fest, dass wir in dieser Implementierung die Sequenz in umgekehrter Reihenfolge erstellen werden - dies ermöglicht es uns, den Listenkonstruktor ( :) anstelle des teureren Verkettungsoperators ( ++ ) zu verwenden.


 import Control.Monad(mapM_) generate :: Int -> IO() generate = generate' "" 0 where generate' xs _ 0 = putStrLn $ reverse xs generate' xs an | n == a = step ')' | a == 0 = step '(' | otherwise = step '(' >> step ')' where step '(' = generate' ('(':xs) (a + 1) (n - 1) step ')' = generate' (')':xs) (a - 1) (n - 1) main :: IO () main = do n <- read <$> getLine generate $ n * 2 

E. Anagramme


Es werden zwei Zeilen angegeben, die aus lateinischen Kleinbuchstaben bestehen. Es ist erforderlich zu bestimmen, ob diese Zeilen Anagramme sind, d. H. Sie unterscheiden sich nur in der Zeichenfolge.

Um dieses Problem zu lösen, werden wir zĂ€hlen, wie oft ein Buchstabe in jeder Zeile vorkommt, und die Ergebnisse vergleichen. Wir verstehen sofort, dass Standardlisten fĂŒr uns nicht geeignet sind, und es ist erforderlich, eine Datenstruktur zu verwenden, die es uns ermöglicht, ĂŒber den Index effektiv auf das Element zuzugreifen. Es gibt verschiedene Arten von Daten, die unsere Bedingungen erfĂŒllen wĂŒrden, aber wir werden das unverĂ€nderliche Standardarray Data.Array verwenden (es gibt immer noch mindestens verschiedene verĂ€nderbare Arrays sowie Data.Vector ).


Um die notwendigen Arrays zu konstruieren, verwenden wir die Funktion hist :: (Ix a, Num b) => (a, a) -> [a] -> Array ab , die gemĂ€ĂŸ der ĂŒbertragenen Liste der Elemente und dem Bereich, zu dem diese Elemente gehören sollen, ein Array bildet. Hier wird die Anzahl der Wiederholungen von Elementen aus der Liste gespeichert. Diese Funktion ist zwar nicht im Data.Array-Modul enthalten, wird jedoch hĂ€ufig als Beispiel fĂŒr die Verwendung einer anderen, bereits Bibliotheksfunktion, accumArray, angegeben. Wir können nur die Implementierung kopieren und main schreiben - der Vorteil des Gleichheitsvergleichs fĂŒr Array Char Int ist bereits definiert. Ich mache Sie auf eine nette Funktion aufmerksam - als Index können wir nicht nur ganze Zahlen verwenden, sondern jeden Vertreter der Klasse Ix . In unserem Fall spielt Char eine natĂŒrliche Rolle in dieser Rolle.


 import Data.Array hist :: (Ix a, Num b) => (a,a) -> [a] -> Array ab hist bnds is = accumArray (+) 0 bnds [(i, 1) | i<-is, inRange bnds i] main = do arr1 <- hist ('a','z') <$> getLine arr2 <- hist ('a','z') <$> getLine if (arr1 == arr2) then print 1 else print 0 

F. FĂŒhren Sie k sortierte Listen zusammen


Bei k Arrays nicht negativer Ganzzahlen, die in nicht abnehmender Reihenfolge sortiert sind, von denen jede 100 nicht ĂŒberschreitet. Es ist erforderlich, das Ergebnis ihrer Fusion zu konstruieren: ein Array, das in nicht abnehmender Reihenfolge sortiert ist und alle Elemente der ursprĂŒnglichen k Arrays enthĂ€lt.
Die LĂ€nge jedes Arrays ĂŒberschreitet 10 ⋅ k nicht.
Versuchen Sie, die Lösung fĂŒr die Zeit k ⋅ log (k) ⋅ n zum Laufen zu bringen, wenn wir annehmen, dass die Eingabearrays die LĂ€nge n haben.

Das ZusammenfĂŒhren von zwei sortierten Listen ist eine klassische Listenaufgabe und wird in vielen Kursen zur Haskell-Programmierung behandelt. Zum Beispiel kann es wie folgt gelöst werden.


 merge :: [Int] -> [Int] -> [Int] merge [] ys = ys merge xs [] = xs merge (x:xs) (y:ys) | x < y = x:merge xs (y:ys) | otherwise = y:merge (x:xs) ys 

Nun, wir können zwei Listen zusammenfĂŒhren. Und was sollen wir mit der Liste der Listen machen? Falten Sie es mit dieser Funktion! So werden wir alle Listen zu einer zusammenfassen und mĂŒssen sie nur ausdrucken.


Lösung
 import Control.Monad merge :: [Int] -> [Int] -> [Int] merge [] ys = ys merge xs [] = xs merge (x:xs) (y:ys) | x < y = x:merge xs (y:ys) | otherwise = y:merge (x:xs) ys mergeLists :: [[Int]] -> [Int] mergeLists = foldl merge [] getUserInputs :: Int -> IO [[Int]] getUserInputs t = replicateM t $ do n <- getLine return $ tail $ read <$> words n main :: IO () main = do k <- read <$> getLine lists <- getUserInputs k let res = mergeLists lists mapM_ (putStrLn . show) res 

Diese Lösung weist jedoch zwei schwerwiegende Probleme auf: Der Rechenaufwand ist höher als der erforderliche - O (k ^ 2 ⋅ n) anstelle von O (k ⋅ log (k) ⋅ n) , und es wird viel zusĂ€tzlicher Speicher verwendet. Infolgedessen besteht diese Lösung den Test Nr. 17 nicht, da die Grenze des verwendeten Speichers ĂŒberschritten wurde - 17,27 MB anstelle der zulĂ€ssigen 10 MB.


Wir werden zwar nicht darauf achten, dass die an die Eingabe gelieferten Zahlen zu einem begrenzten Wertebereich gehören, und wir suchen weiterhin nach Lösungen fĂŒr einen allgemeineren Fall.


Der nĂ€chste Schritt besteht darin, zu versuchen, den im ursprĂŒnglichen Artikel vorgeschlagenen Ansatz mit einer Analyse dieser Aufgaben umzusetzen. Ich möchte Sie daran erinnern, dass es auf der Verwendung einer Datenstruktur basiert, die eine effiziente Möglichkeit zum Extrahieren des Mindestelements bietet. WĂ€hlen Sie als solche Struktur Data.Set aus . Wir initialisieren Set mit der Liste der ersten Elemente, extrahieren und drucken dann bei jedem Schritt das minimale Element und fĂŒgen dann das nĂ€chste Element aus der entsprechenden Liste hinzu. ZusĂ€tzlich benötigen wir eine Data.Sequence- Struktur, um die Listen selbst zu speichern. Es wurde aus GrĂŒnden ausgewĂ€hlt, dass bei jedem Schritt sowohl ein schneller Zugriff auf die Liste ĂŒber ihren Index (den die Liste nicht bereitstellen kann) als auch das Element dieses Elements geĂ€ndert werden mĂŒssen, ohne dass die gesamte Struktur kopiert werden muss (die im Allgemeinen keine unverĂ€nderlichen Daten bereitstellen kann) . Array ).


Somit haben wir folgendes Programm:


Lösung
 import qualified Data.Sequence as Seq import qualified Data.Set as Set import Control.Monad import Data.Foldable mergeLists :: Set.Set (Int, Int) -> Seq.Seq [Int] -> IO () mergeLists set seq | Set.null set = return () | otherwise = do let ((val, idx), set') = Set.deleteFindMin set print val if null (Seq.index seq idx) then mergeLists set' seq else mergeLists (Set.insert (head (Seq.index seq idx), idx) set') (Seq.adjust tail idx seq) getUserInputs :: Int -> IO [[Int]] getUserInputs t = replicateM t $ do n <- getLine return $ tail $ read <$> words n main :: IO () main = do t <- read <$> getLine lists <- getUserInputs t let init_seq = Seq.fromList (filter (not . null) lists) let init_heap = Set.fromList (zipWith (,) (toList (fmap head init_seq)) [0..]) mergeLists init_heap $ tail <$> init_seq 

Wir senden die Lösung und stellen fest, dass das Programm zwar viel weniger Speicher verbraucht (10,26 MB anstelle von 17,27 MB beim 17. Test), das Limit jedoch immer noch nicht erreicht hat. Der Grund dafĂŒr liegt in der Tatsache, dass wir bei dieser Entscheidung auf die eine oder andere Weise die gesamten Eingabedaten in den Speicher lesen mĂŒssen. Versuchen wir dies mit Hilfe der dritten Lösung fĂŒr dieses Problem zu vermeiden - Sortieren durch ZĂ€hlen.


Wir haben bereits die Anzahl der eingehenden Zeichen gezÀhlt, als wir das vorherige Anagrammproblem gelöst haben. Ebenso wie bei der Lösung werden wir Data.Array verwenden . Zuerst implementieren wir die Funktion addToArray :: Array Int Int -> [Int] -> Array Int Int , die ein Array basierend auf dem vorhandenen bildet, indem wir die Werte an den Indizes erhöhen, die den Werten aus der Liste entsprechen.


 addToArray :: Array Int Int -> [Int] -> Array Int Int addToArray acc elems = accum (+) acc [(i, 1) | i<-elems] 

Dann werden wir den uns bekannten Ansatz fĂŒr das Problem des Entfernens von Wiederholungen verwenden - unter Verwendung der monadischen Faltung, wobei die Funktion addToArray nacheinander auf k Quellarrays angewendet wird . Und ... wir erhalten das gleiche Ergebnis von 10,26 MB beim 17. Test. Und dann ist es Zeit, sich daran zu erinnern, dass foldl (dessen Analogold foldM ist ) gemĂ€ĂŸ der akzeptierten Reduktionsreihenfolge zuerst die gesamte Kette verschachtelter AusdrĂŒcke erweitert und erst dann mit ihrer aktiven Berechnung fortfĂ€hrt. Wie Sie wissen, implementiert das Data.List- Modul zur BekĂ€mpfung dieser Tatsache die Funktion foldl ' , die die Funktion seq :: a -> b -> b verwendet , die zuerst das erste Argument in die Normalform des schwachen Kopfes umwandelt, dh es auf den Ă€ußeren Teil reduziert - den Wert der Funktion oder des Konstruktors und gibt dann den zweiten zurĂŒck ( https://www.ibm.com/developerworks/ru/library/l-haskell4/index.html ). Wir haben keine andere Wahl, als die Funktion foldM 'unabhĂ€ngig zu implementieren.


 foldM' :: (Monad m) => (a -> b -> ma) -> a -> [b] -> ma foldM' _ z [] = return z foldM' fz (x:xs) = do z' <- fzx z' `seq` foldM' fz' xs 

Infolgedessen hat sich die Menge des verwendeten Speichers beim 17. Test fast halbiert und betrug 5,64 MB! Obwohl der 17. und 18. Test erfolgreich bestanden wurden, bestand diese Implementierung den 19. Test nicht aus demselben Grund, aus dem das Speicherlimit ĂŒberschritten wurde - 10,25 MB.


Okay, mach weiter - wir haben Data.Array.Unboxed noch nicht ausprobiert. Diese Art von Arrays ist insofern bemerkenswert, als sie im Gegensatz zum Standard die Werte selbst speichern können, anstatt auf sie zu verweisen ( https://wiki.haskell.org/Arrays#Unboxed_arrays ). Aus diesem Grund benötigen solche Arrays weniger Speicherplatz und sind effizienter. Um sie zu verwenden, mĂŒssen wir nur die Import- und Funktionstypen Ă€ndern, da Data.Array und Data.Array.Unboxed eine Schnittstelle unverĂ€nderlicher IArray- Arrays implementieren .


Wir senden eine Lösung - der Speicherverbrauch hat sich um das 4,5-fache auf 2,26 MB verringert, aber das Zeitlimit nicht ĂŒberschritten - die AusfĂŒhrungszeit betrug 1,09 Sekunden. Womit könnte dies verbunden sein? Gemessen an der Tatsache, dass die AusfĂŒhrungszeit der verbleibenden Tests gleich bleibt, liegt der Grund meines Erachtens nicht darin, dass sich das Array ohne Box als langsamer als das mit Box herausstellte, sondern insbesondere das Testsystem. Es scheint, dass die Aufgabe unterbrochen wird, sobald eine der EinschrĂ€nkungen verletzt wird. In sehr seltenen FĂ€llen besteht diese Implementierung jedoch immer noch den 19. Test mit einem Ergebnis von 0,98 Sekunden, scheitert jedoch an Test Nummer 20, auch weil das Zeitlimit ĂŒberschritten wurde.


Danach habe ich versucht, das unsichere Analogon der Accum-Funktion zu verwenden, das theoretisch schneller sein sollte, verschiedene Puffermethoden ( hSetBuffering :: Handle -> BufferMode -> IO () -Funktion), verÀnderbare IOArray- Arrays, aber keine dieser Methoden brachte Ergebnisse .


Ich bin nicht geneigt zu glauben, dass die Grenzen fĂŒr Haskell zu eng festgelegt sind, und ich hoffe, dass es noch eine Lösung gibt, die alle Tests besteht. Im Projekt-Repository habe ich verschiedene Versionen des Codes zur Lösung dieses Problems veröffentlicht (mit Array und IOArray). Vielleicht ist dies der Ausgangspunkt fĂŒr eine Lösung, die alle Tests besteht.


Fazit


Trotz der Tatsache, dass mir nur fĂŒnf von sechs Aufgaben erlegen waren, erledigte ich meine Hauptaufgabe - das Üben der funktionalen Programmierung. Nicht zuletzt spielten strenge EinschrĂ€nkungen des Ressourcenverbrauchs des Programms eine Rolle, was uns dazu zwang, nach immer neuen AnsĂ€tzen zur Lösung von Problemen zu suchen. Ich hoffe, dass ihre Beschreibung fĂŒr diejenigen nĂŒtzlich sein wird, die gerade ihre Reise in die funktionale Programmierung beginnen


War der funktionale Ansatz zur Lösung solcher Probleme geeignet? Ehrlich gesagt habe ich einen doppelten Eindruck. Einerseits erwiesen sich die Lösungen fĂŒr die meisten Probleme als sehr prĂ€zise, ​​und die Ausdrucksmittel von Haskell selbst sowie seine umfangreiche Standardbibliothek spielten dabei eine bedeutende Rolle. Andererseits kann man nur zugeben, dass die Verwaltung der verbrauchten Ressourcen in den meisten FĂ€llen ein bestimmtes Problem sein kann, das es nicht ermöglicht, das Problem unter den gegebenen EinschrĂ€nkungen zu lösen.

Source: https://habr.com/ru/post/de470790/


All Articles