تمشي مهمة مثيرة للاهتمام على الشبكة ، والتي طُلبت في مقابلة على تويتر.تخيل أنك تنظر إلى جدران بارتفاعات مختلفة في الملف الشخصي. إنها تمطر ، في مكان ما يبقى الماء ، في مكان ما يتدفق على حواف الجدار بسبب الاختلاف في الارتفاع. يكمن التحدي في تحديد كمية المياه المتبقية بين الجدران.
لتمثيل تسلسل الجدران ، يمكننا استخدام القائمة:type Height = Int
walls :: [Height]
walls = [2,5,1,2,3,4,7,7,6]
لمعرفة حجم المياه في أعلى كل جدار ، نحتاج إلى معرفة ثلاثة أشياء:- ارتفاع الجدار الحالي
- ارتفاع أعلى الجدار الأيسر
- ارتفاع أعلى الجدار الأيمن
نكتشف أي من الجدران أدناه يمينًا أو يسارًا ، ونطرح ارتفاع الجدار الحالي من الأعلى ونحصل على حجم الماء. دعونا نلقي نظرة فاحصة على مثال:
تخيل أننا بحاجة إلى حساب حجم الماء للجدار ب. ارتفاع أعلى الجدار الأيسر - أ ، هو 3 . ارتفاع أعلى الجدار الأيمن - الصورة ، هو 2 . نظرًا للجدار الأيسر الأعلى ، سينتشر الماء إلى اليمين ، لذلك نحسب من ارتفاع الجدار الأيمن. ولذلك، فإن حجم المياه للجدار ب هو = الطول ج - ارتفاع ب = 2-1 = 1 .لن يعمل حل هذه المشكلة في مسار واحد من القائمة ، ولكنه سيظهر في قسمين - كل شيء هو حساب الجدران العلوية اليسرى واليمنى. ( UPD :roman_kashitsynأوضحت كيف يمكن ذلك لواحد فقط دون الحصول على معلومات عن حجم المياه المتراكمة على كل جدار)نبدأ بحساب أعلى الجدار الأيسر لكل جدار باستخدام تأثير الحالة:type Volume = Int
themax :: Height -> State Height Height
themax h = modify (max h) *> get
highest_left :: [Height] -> [Height]
highest_left walls = evalState (traverse themax walls) 0
الآن نحن بحاجة إلى حساب أعلى الجدار الأيمن لكل من الجدران ، ولكن في هذه الحالة نحتاج إلى بدء العد ليس على اليسار ، ولكن على اليمين. كيف تكون في هذه الحالة؟ هل تريد قلب القائمة؟ كما كنت قد خمنت من عنوان هذه المقالة ، هناك طريقة أكثر إثارة للاهتمام.المحركات التطبيقية العكسية
يعيش نوع مثير للاهتمام في حزمة المحولات (بالمعنى الحرفي والمجازي للكلمة). يسمح لك بتشغيل التأثيرات التطبيقية بالترتيب العكسي:newtype Backwards f a = Backwards { forwards :: f a }
كيف تعمل؟ دعونا نلقي نظرة فاحصة على مثيل الطبقة التطبيقية لـ Backwards :
instance Applicative t => Applicative (Backwards t) where
pure = Backwards . pure
Backwards f <*> Backwards x = Backwards ((&) <$> x <*> f)
& هو نفس تطبيق دالة على الوسيطة $ ، ولكن بترتيب مختلف من الوسيطات: أولاً الوسيطة ، ثم الوظيفة:f $ x = x & f
نظرًا للكسل ، فإننا نحسب أولاً المكون الثاني من سلسلة الحسابات التطبيقية ، وبعد ذلك فقط المكون الأول - ومن ثم اسم هذا النوع.في نفس المحولات يوجد نوع آخر مثير للاهتمام - عكس :newtype Reverse f a = Reverse { getReverse :: f a }
يطلق تأثيرات في Traversable في الاتجاه المعاكس باستخدام Backward .والآن نعود إلى مشكلتنا الأصلية مع هذا النوع بالذات.نحن نتقن التمشي على سطح القمر
"Moonwalk" (من ممشى القمر الإنجليزي) ، أو "الانزلاق للخلف" ، هو أسلوب رقص عندما يتحرك راقص إلى الوراء ، بينما يحاكي حركة ساقيه كما لو كان يسير إلى الأمام. (ويكيبيديا).
لإثارة مثل هذه الحيلة ، نأخذ نفس الوظيفة مع تأثير الحالة (كما لو كنا نمضي قدمًا ، من اليسار إلى اليمين) ، الذي استخدمناه لحساب أعلى الجدار الأيسر:
themax :: Height -> State Height Height
themax h = modify (max h) *> get
highest_right :: [Height] -> [Height]
highest_right walls = getReverse $ evalState (traverse themax $ Reverse walls)
الآن لدينا كل شيء لحساب الحجم الكلي للمياه المتراكمة. من أعلى الجدران اليسرى واليمنى ، نختار أدنى واطرح ارتفاع الجدار منه - سيكون هذا حجم المياه المتراكمة:walls = [2,5,1,2,3,4,7,7,6]
let hls = highest_left walls = [2,5,5,5,5,5,7,7,7]
let hrs = highest_right walls = [7,7,7,7,7,7,7,7,6]
sum $ zipWith3 (\l x r -> min l r - x) hls walls hrs
لذا ، ساعدتنا الدوافع التطبيقية على التجريد من تأثيرات محددة وهياكل البيانات. لديهم بعض استخدامات أكثر إثارة للاهتمام - تجميع الاستفسارات ، والفرز .يمكن العثور على شفرة المصدر لهذا الحل هنا . يحتوي هذا اللغز على بعض الأساليب الأكثر إثارة للاهتمام والتي يمكن عرضها هنا .