كم تسرب الماء؟ نقوم بحل المشكلة عن طريق المشي على سطح القمر على هاسكل

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


لتمثيل تسلسل الجدران ، يمكننا استخدام القائمة:

type Height = Int

walls :: [Height]
walls = [2,5,1,2,3,4,7,7,6]

لمعرفة حجم المياه في أعلى كل جدار ، نحتاج إلى معرفة ثلاثة أشياء:

  1. ارتفاع الجدار الحالي
  2. ارتفاع أعلى الجدار الأيسر
  3. ارتفاع أعلى الجدار الأيمن

نكتشف أي من الجدران أدناه يمينًا أو يسارًا ، ونطرح ارتفاع الجدار الحالي من الأعلى ونحصل على حجم الماء. دعونا نلقي نظرة فاحصة على مثال:



تخيل أننا بحاجة إلى حساب حجم الماء للجدار ب. ارتفاع أعلى الجدار الأيسر - أ ، هو 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 :

-- |          liftA2  <**>
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



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

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

All Articles