نحل مهام Yandex.Intview بأسلوب وظيفي

قبل بضعة أشهر ، ظهر مقال في مدونة ياندكس يناقش مرور قسم الخوارزمية في المقابلة. من بين أشياء أخرى ، تم إنشاء رابط في هذه المقالة إلى مسابقة خاصة تحتوي على مهام مشابهة لتلك التي تقدمها ياندكس لمرشحيها.


بعد التسجيل في النظام ، انجذبت انتباهي على الفور من خلال القدرة على حل المشكلات في Haskell. والحقيقة هي أنه على الرغم من أنني مولع بالبرمجة بهذه اللغة ، إلا أنني لم أتقدم أكثر من تنفيذ المهام من مختلف الدورات التعليمية على الإنترنت. بعد أن قررت أن حلها يمكن أن يكون تحديا مثيرا وسوف يزيد من مستواي كمطور ، فقد شرعت في حلها.


الذي يهتم في النهاية جاء منه ، مرحبا بكم في القط.


أ. الحجارة والمجوهرات


يتم إعطاء سطرين من أحرف لاتينية صغيرة: السلسلة J والسلسلة S. الأحرف الموجودة في السلسلة J هي "جواهر" ويتم تضمينها في السلسلة S "أحجار". من الضروري تحديد عدد الأحرف من حرف S في نفس الوقت "جواهر". ببساطة ، تحتاج إلى التحقق من عدد الشخصيات من S الموجودة في J.

المهمة الأولى هي عملية الاحماء ، سنحلها "على الجبهة". نعرّف الدالة jewelleryCount :: String -> String -> Int ، والتي تلخص جميع حالات العنصر الجاري معالجتها في القائمة الأولى ، باستخدام الإلتفاف الخاص بالقائمة التي تم تمريرها بواسطة الوسيطة الثانية. لهذه الأغراض ، نعرّف الدالة elemInt بناءً على دالة elem ، والتي ، على عكس الأخيرة ، لن تُرجع غير صحيح أو خطأ ، لكن الرقم 0 أو 1. في الوظيفة الرئيسية ، تحتاج فقط إلى قراءة سطرين ، وتمريرهما إلى الوظيفة المقابلة وطباعة النتيجة. الحكم على نظام الاختبار على ما يرام ، ونحن ننتقل إلى المهمة الثانية.


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 

شفرة المصدر لحل هذه وغيرها من المهام متاحة أيضا في مستودع جيثب.


وحدات متتالية


يجب العثور على أطول سلسلة من الوحدات في المتجه الثنائي وطباعة طوله.

لحل هذه المشكلة ، ننفذ دالة تكرارية تمر عبر القائمة المنقولة وتحسب طول التسلسل المطلوب. باستخدام وسيطات الوظيفة ، بالإضافة إلى القائمة نفسها ، سنقوم بتمرير الحد الأقصى للطول الحالي وعدد الوحدات المتتالية في المكالمة الحالية. أولاً ، نعرّف قاعدة العودية في القائمة الفارغة ، ثم خطوة العودية نفسها.


لقراءة بيانات المدخلات ، نحدد الدالة getUserInputs :: IO [Char] ، والتي نقرأ فيها أولاً الرقم n - حجم القائمة ، ثم باستخدام Combinator replicateM ، نحصل على دالة تستدعي الوظيفة <<get> getLine n times ودمج النتائج في قائمة .


 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 

نرسل القرار ، والحكم على ما يرام. نحن نمضي قدما.


جيم مكررة إزالة


يتم تقديم صفيف من أعداد صحيحة 32 بت مرتبة بترتيب غير تنازلي. تريد إزالة جميع التكرار منه.

لنبدأ بتنفيذ بسيط. نحدد وظيفة أولية تقرأ رقمًا وتطبعها وتعيدها ملفوفة في IO monad. نحدد أيضًا وظيفة deleteDoubles :: Int -> Int -> IO () ، التي تقرأ رقمًا وتطبعه فقط إذا كانت غير مساوية للوسيطة الثانية (سنقوم بتمرير الرقم الذي تمت قراءته في الخطوة السابقة هناك). بعد ذلك ، تقوم الدالة باستدعاء نفسها بشكل متكرر وبالتالي تنتقل إلى الرقم التالي في دفق الإدخال. قاعدة العودية هي عدد الأرقام المراد قراءتها ، وسنمررها في الوسيطة الأولى.


 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) 

نرسل الحل ، وهو يجتاز جميع الاختبارات ، ويبدو أنه يمكننا الانتقال إلى المهمة التالية ، ولكن في رأيي ، فإن الدعوة المتكررة للوظيفة التي تعمل في mono IO هي أكثر إرباكًا من كونها موجزة. دعنا نحاول تحسينه.


لاحظ أنه ، عمومًا ، يمكنك أولاً قراءة قائمة الأرقام بأكملها (سنستخدم أداة النسخ المتماثل لـ CombateM المألوفة بالفعل بالمهمة الثانية) ، ثم نمررها إلى وظيفة خالصة تقوم بتصفية جميع التكرار ، ثم طباعة النتيجة نهائيًا.


 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 

أقوم بإرسال حل ، وتتمثل خيبة الأمل الأولى في أن البرنامج لا يجتاز اختبار 193 بسبب تجاوز حد الذاكرة المستخدمة. الخطأ الرئيسي هو قراءة القائمة بأكملها في الذاكرة ككل. سنحاول تجنب ذلك وسنطبق مجموعة مختلطة من الإصدارين الأول والثاني.


تجدر الإشارة إلى أن مهمة إزالة التكرارات تذكر إلى حد ما الإلتفاف الأيسر: في كل خطوة نحسب دالة ، وفقًا للعنصر الحالي المقروء وبعض نتائجه ، في الخطوة السابقة تقرر الطباعة ، ثم تنتقل إلى الزوج التالي من القيم.


إن الوظيفة التي تقوم بطباعة أو عدم طباعة النتيجة اعتمادًا على الوسائط الخاصة بها ، وبعد ذلك تقوم بإرجاع الوسيطة الثانية الخاصة بها ، والملفوفة في IO monad ، بسيطة للغاية ، دعنا نسميها الخطوة:


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

اكتشفنا ما إذا كنت ستطبع أم لا ، اعتمادًا على القيم التي تم تمريرها ، ولكن كيف ننظم القراءة؟ للقيام بذلك ، نستخدم دالة الالتفاف أحادية اللون foldM :: (Foldable t، Monad m) => (b -> a -> mb) -> b -> ta -> mb ، والذي ينطبق على قائمة وظائف القراءة.
حسب نوع دالة foldM ، نلاحظ أنه في كل خطوة يحدث "تفريغ" نتيجة التطبيق السابق للوظيفة تحت غطاء foldM نفسه. وبالتالي ، في كل خطوة نحتاج فقط إلى بدء حساب أحادي لعنصر القائمة الحالي (في الواقع ، اقرأ الرقم التالي) باستخدام عامل الربط ( >> = ) وتمريره مع الرقم السابق إلى الخطوة. نتيجة لذلك ، نحصل على البرنامج التالي


 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. توليد تسلسل قوس


إعطاء عدد صحيح n. يجب اشتقاق كل متواليات القوس الصحيحة بطول 2 ⋅ n ، مرتبة معجميًا (انظر https://ru.wikipedia.org/wiki/Lexographic_order ).
يتم استخدام الأقواس فقط في المهمة.
يُنصح بالحصول على حل يعمل في وقت يتناسب مع العدد الإجمالي لتسلسلات القوس الصحيحة في الاستجابة ، وفي الوقت نفسه يستخدم سعة ذاكرة تتناسب مع n.

هذه المهمة ، مثلها مثل العديد من المهام الأخرى ، والتي لا بد من اشتقاق تسلسلات تفي بشروط معينة (على سبيل المثال ، يمكن قراءة مهمة تبادل العملات المعدنية ، وترتيب ثماني الملكات وغيرها ، بمزيد من التفاصيل هنا ) ، يتم حلها بإيجاز باستخدام قائمة monad. باختصار ، يعتمد هذا النهج على الربط الأحادي للقوائم ، ومعنى ذلك هو الجمع بين مجموعة العمليات المنفذة على كل عنصر من عناصر القائمة.


حدد دالة متكررة تنشئ ':: Int -> Int -> [[Char]] ، والتي تأخذ عدد الأقواس المراد وضعها كوسيطة ثانية ، وعدد الأقواس المفتوحة التي تم ضبطها بالفعل لتكون الوسيطة الأولى. بالنسبة لخطوة العودية ، نحتاج إلى وظيفتين مساعدتين: ممكن - إرجاع قائمة من الأقواس التي يمكن وضعها في الخطوة التالية ، والخطوة - إجراء مكالمة متكررة إلى وظيفة إنشاء 'مع المعلمات اللازمة.


 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 

نرسل الحل ، ونفهم أننا لم نأخذ في الاعتبار القيود التي تم فرضها على مقدار الذاكرة المستخدمة من قبل البرنامج - الحل لا يجتاز الاختبار الرابع عشر بسبب تجاوز حد الذاكرة المستخدمة.


نقوم بتعديل وظيفة "إنشاء" بحيث بدلاً من إنشاء القائمة الكاملة لتسلسلات القوس الصحيحة ، يتم عرضها فورًا على الشاشة. للقيام بذلك ، سيتعين علينا إضافة الوسيطة الثالثة إلى الوظيفة - جزء من التسلسل الذي تم إنشاؤه للخطوة الحالية. لاحظت أنه في هذا التطبيق ، سنقوم ببناء التسلسل بالترتيب العكسي - سيتيح لنا ذلك استخدام مُنشئ القائمة ( :) بدلاً من عامل تسلسل أكثر تكلفة ( ++ ).


 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. الجناس الناقصة


يتم إعطاء سطرين ، تتكون من أحرف لاتينية صغيرة. يجب تحديد ما إذا كانت هذه الخطوط عبارة عن جناس ، أي أنها تختلف فقط في تسلسل الأحرف.

لحل هذه المشكلة ، سنحسب عدد المرات التي يحدث فيها حرف في كل صف ومقارنة النتائج. نحن نتفهم على الفور أن القوائم القياسية ليست مناسبة لنا ، ومن الضروري استخدام بنية بيانات تسمح لنا بالوصول الفعال إلى العنصر بواسطة فهرسه. هناك عدة أنواع من البيانات التي ترضي شروطنا ، لكننا سنستخدم مجموعة البيانات غير القابلة للتغيير القياسية Data.Array (لا تزال هناك على الأقل صفيفات قابلة للتغيير على الأقل ، وكذلك Data.Vector ).


لإنشاء الصفائف اللازمة ، نستخدم الدالة hist :: (Ix a، Num b) => (a، a) -> [a] -> Array ab ، والتي ، وفقًا لقائمة العناصر المنقولة والنطاق الذي يجب أن تنتمي إليه هذه العناصر ، تشكل صفيفًا ، الذي يخزن عدد من تكرار العناصر من القائمة. هذه الوظيفة ، على الرغم من عدم تضمينها في الوحدة النمطية Data.Array ، غالبًا ما يتم تقديمها كمثال لاستخدام دالة أخرى ، بالفعل مكتبة ، رايس آراي. يمكننا فقط نسخ تنفيذه وكتابته الرئيسية - تم تحديد فائدة المقارنة من أجل Array Char Int بالفعل. ألفت انتباهك إلى ميزة واحدة لطيفة - كمؤشر ، لا يمكننا استخدام الأعداد الصحيحة فقط ، ولكن أيضًا أي ممثل للفئة التاسعة . في حالتنا ، يلعب تشار دورًا طبيعيًا في هذا الدور.


 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 

واو دمج ك فرز القوائم


بالنظر إلى صفائف k من الأعداد الصحيحة غير السالبة المصنفة بترتيب غير تنازلي ، لا يتجاوز كل منها 100. يجب إنشاء نتيجة دمجها: صفيف مرتبة بترتيب غير تنازلي يحتوي على جميع عناصر صفائف k الأصلية.
لا يتجاوز طول كل صفيف 10 ⋅ k.
حاول جعل الحل يعمل للوقت k ⋅ log (k) ⋅ n ، إذا افترضنا أن صفيفات الإدخال بطول n.

دمج قائمتين مفروَّتين مهمة قائمة كلاسيكية وتغطى في العديد من الدورات التدريبية على برمجة Haskell. على سبيل المثال ، يمكن حلها على النحو التالي.


 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 

حسنًا ، يمكننا دمج قائمتين. وماذا يجب أن نفعل مع قائمة القوائم؟ صنفه مع هذه الوظيفة! وبالتالي ، سنجمع كل القوائم في قائمة واحدة ، وسنضطر إلى طباعتها فقط.


قرار
 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 

ومع ذلك ، يحتوي هذا الحل على مشكلتين خطيرتين - التعقيد الحسابي أعلى من المطلوب واحد - O (k ^ 2 ⋅ n) بدلاً من O (k ⋅ log (k) ⋅ n) ، بالإضافة إلى أنه يستخدم الكثير من الذاكرة الإضافية. نتيجة لذلك ، فشل هذا الحل في اختبار الرقم 17 نظرًا لتجاوز حد الذاكرة المستخدمة - 17.27 ميجابايت بدلاً من 10 ميغابايت المسموح بها.


على الرغم من أننا لن نلاحظ حقيقة أن الأرقام الموردة للمدخلات تنتمي إلى مجموعة محدودة من القيم ، ونحن نواصل البحث عن حلول لحالة أكثر عمومية.


والخطوة التالية هي محاولة تنفيذ النهج المقترح في المقالة الأصلية مع تحليل هذه المهام. اسمحوا لي أن أذكرك بأنه يعتمد على استخدام بنية البيانات التي توفر طريقة فعالة لاستخراج الحد الأدنى للعنصر. مثل هذه البنية ، حدد Data.Set . نقوم بالتهيئة مع قائمة العناصر الأولى ، ثم في كل خطوة سنقوم باستخراج العنصر الأدنى وطباعته ، ثم نضيف العنصر التالي من القائمة المقابلة. بالإضافة إلى ذلك ، سنحتاج إلى بنية Data.Sequence لتخزين القوائم نفسها. تم اختياره لأسباب من الضروري في كل خطوة الوصول إلى القائمة بسرعة من خلال فهرسها (والذي لا يمكن أن توفره القائمة) ، ولتغيير عنصر هذا العنصر دون الحاجة إلى نسخ الهيكل بأكمله (والذي لا يمكن عمومًا تقديم بيانات غير قابلة للتغيير) . صفيف ).


وبالتالي ، لدينا البرنامج التالي:


قرار
 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 

نرسل الحل ونكتشف أنه على الرغم من أن البرنامج بدأ يستهلك ذاكرة أقل بكثير (10.26 ميجابايت بدلاً من 17.27 ميجابايت في الاختبار السابع عشر) ، إلا أنه لم يلب الحد. يكمن سبب ذلك في حقيقة أنه من خلال هذا القرار ، بطريقة أو بأخرى ، يتعين علينا قراءة بيانات الإدخال بالكامل في الذاكرة. دعونا نحاول تجنب ذلك بمساعدة الحل الثالث لهذه المشكلة - الفرز عن طريق العد.


لقد قمنا بالفعل بعدد عدد الأحرف الواردة عند حل مشكلة الجناس الناقص السابقة. أيضا ، كما هو الحال في حلها ، سوف نستخدم Data.Array . أولاً ، نطبق الدالة addToArray :: Array Int Int -> [Int] -> Array Int Int ، والتي تشكل صفيفًا يستند إلى واحد موجود عن طريق زيادة القيم الموجودة في المؤشرات التي تتوافق مع القيم من القائمة.


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

بعد ذلك ، سوف نستخدم الطريقة المعروفة لنا في مشكلة إزالة التكرارات - باستخدام الإلتواء الأحادي ، مع تطبيق وظيفة addToArray بالتتابع على صفائف مصدر k . و ... نحصل على نفس النتيجة عند 10.26 ميغابايت في الاختبار السابع عشر. ومن ثم فقد حان الوقت لتذكر أن foldl (الذي يكون التناظرية هو foldM ) وفقًا لترتيب التخفيض المقبول سوف يوسع أولاً سلسلة كاملة من التعبيرات المتداخلة ثم يبدأ في حسابه النشط. كما تعلمون ، لمكافحة هذه الحقيقة ، تنفذ وحدة Data.List وظيفة foldl ، والتي تستخدم الدالة seq :: a -> b -> b ، والتي تقوم أولاً بإلقاء الوسيطة الأولى على النموذج العادي للرأس الضعيف ، أي أنها تقللها إلى الجزء الخارجي - قيمة الدالة أو المُنشئ ، ثم تُرجع الثانية ( https://www.ibm.com/developerworks/ru/library/l-haskell4/index.html ). ليس لدينا خيار سوى تنفيذ وظيفة foldM بشكل مستقل .


 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 

ونتيجة لذلك ، تقلصت الذاكرة المستخدمة في الاختبار السابع عشر إلى النصف تقريبًا وبلغت 5.64 ميجابايت! على الرغم من النجاح في اجتياز الاختبارين السابع عشر والثامن عشر ، إلا أن هذا التطبيق لم يجتاز الاختبار التاسع عشر لنفس سبب تجاوز الحد الأقصى للذاكرة - 10.25 ميجابايت.


حسنًا ، تابع - لم نجرب Data.Array.Unboxed بعد. هذا النوع من المصفوفات جدير بالملاحظة أنه ، على عكس المعيار ، يمكنه تخزين القيم بأنفسهم ، بدلاً من الإشارة إليها ( https://wiki.haskell.org/Arrays#Unboxed_arrays ). لهذا السبب ، تشغل هذه المصفوفات مساحة ذاكرة أقل وأكثر كفاءة. من أجل استخدامها ، نحتاج فقط إلى تغيير أنواع الاستيراد والوظائف ، لأن Data.Array و Data.Array.Unboxed يطبقان واجهة واحدة من صفيف IArray غير القابلة للتغيير.


نرسل حلاً - انخفض استهلاك الذاكرة 4.5 مرة إلى 2.26 ميجابايت ، لكنه لم يتجاوز الحد الزمني - كان وقت التنفيذ 1.09 ثانية. ما يمكن أن يكون هذا متصلا؟ انطلاقًا من حقيقة أن وقت تنفيذ الاختبارات المتبقية لا يزال كما هو ، أعتقد أن السبب لا يتمثل في أن المصفوفة غير المُعلَّب بها أصبحت أبطأ من المحاصر ، ولكن على وجه الخصوص نظام الاختبار. يبدو أن المهمة قد توقفت بمجرد انتهاك أحد القيود. ومع ذلك ، في حالات نادرة جدًا ، لا يزال هذا التطبيق يجتاز الاختبار التاسع عشر بنتيجة 0.98 ثانية ، لكنه فشل في اختبار رقم 20 أيضًا بسبب تجاوز الحد الزمني.


بعد ذلك حاولت استخدام التماثلية غير الآمنة لوظيفة التجميع ، والتي من الناحية النظرية يجب أن تكون أسرع ، أساليب التخزين المؤقت المختلفة ( hSetBuffering :: Handle -> BufferMode -> IO () دالة) ، صفائف IOArray القابلة للتغيير ، ولكن لم تسفر أي من هذه الطرق عن أي نتائج .


أنا لا أميل إلى الاعتقاد بأن حدود هاسكل وضعت بإحكام شديد ، وآمل أنه لا يزال هناك حل سوف يجتاز جميع الاختبارات. في مستودع المشروع ، قمت بنشر عدة إصدارات مختلفة من الكود لحل هذه المشكلة (مع Array و IOArray) ، وربما ستكون هذه هي نقطة الانطلاق للحل الذي يجتاز جميع الاختبارات.


استنتاج


على الرغم من حقيقة أن خمسة فقط من أصل ست مهام استسلم لي ، أكملت مهمتي الرئيسية - ممارسة البرمجة الوظيفية. لم يكن الدور الأقل أهمية هو القيود الشديدة المفروضة على الموارد التي يستهلكها البرنامج ، مما أجبرنا على البحث عن طرق أكثر وأكثر لحل المشكلات. آمل أن يكون وصفهم مفيدًا لأولئك الذين بدأوا للتو رحلتهم في البرمجة الوظيفية


هل كان النهج الوظيفي مناسبًا لحل مثل هذه المشكلات؟ بصراحة ، لدي انطباع مزدوج. من ناحية ، تبين أن حلول معظم المشكلات كانت موجزة للغاية ، وقد لعبت الأدوات المعبرة لهاسكل نفسه ، بالإضافة إلى مكتبته القياسية الغنية ، دورًا مهمًا في هذا. من ناحية أخرى ، لا يسع المرء إلا أن يعترف بأن إدارة الموارد المستهلكة في معظم الحالات يمكن أن تكون مشكلة معينة ، والتي لن تسمح بحل المشكلة في القيود المحددة.

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


All Articles