ฉันจะทำให้อัลกอริธึมนี้ดีกว่าโดยไม่ต้องทำซ้ำตัวเองได้อย่างไร


9

(ได้แรงบันดาลใจจากคำตอบของคำถามนี้ )

พิจารณารหัสนี้ (มันควรจะหาองค์ประกอบที่ใหญ่ที่สุดที่น้อยกว่าหรือเท่ากับอินพุตที่กำหนด):

data TreeMap v = Leaf | Node Integer v (TreeMap v) (TreeMap v) deriving (Show, Read, Eq, Ord)

closestLess :: Integer -> TreeMap v -> Maybe (Integer, v)
closestLess i = precise Nothing where
  precise :: Maybe (Integer, v) -> TreeMap v -> Maybe (Integer, v)
  precise closestSoFar Leaf = closestSoFar
  precise closestSoFar (Node k v l r) = case i `compare` k of
    LT -> precise closestSoFar l
    EQ -> Just (k, v)
    GT -> precise (Just (k, v)) r

มันไม่ขี้เกียจมาก เมื่อGTป้อนเคสแล้วเรารู้ว่าค่าตอบแทนคืนสุดท้ายจะเป็นJustอะไรที่มากกว่าNothingแต่Justก็ยังไม่สามารถใช้ได้จนกว่าจะหมด ฉันต้องการทำให้คนนี้Justใช้งานได้เร็วขึ้นเมื่อGTมีการป้อนเคส กรณีทดสอบของฉันสำหรับเรื่องนี้คือฉันต้องการData.Maybe.isJust $ closestLess 5 (Node 3 () Leaf undefined)ประเมินให้Trueมากกว่าการทำจุดต่ำสุด นี่เป็นวิธีหนึ่งที่ฉันสามารถทำได้:

data TreeMap v = Leaf | Node Integer v (TreeMap v) (TreeMap v) deriving (Show, Read, Eq, Ord)

closestLess :: Integer -> TreeMap v -> Maybe (Integer, v)
closestLess _ Leaf = Nothing
closestLess i (Node k v l r) = case i `compare` k of
  LT -> closestLess i l
  EQ -> Just (k, v)
  GT -> Just (precise (k, v) r)
  where
    precise :: (Integer, v) -> TreeMap v -> (Integer, v)
    precise closestSoFar Leaf = closestSoFar
    precise closestSoFar (Node k v l r) = case i `compare` k of
      LT -> precise closestSoFar l
      EQ -> (k, v)
      GT -> precise (k, v) r

แต่ตอนนี้ผมกำลังทำซ้ำตัวเอง: ตรรกะหลักอยู่ในขณะนี้ทั้งในและclosestLess preciseฉันจะเขียนสิ่งนี้เพื่อให้มันขี้เกียจ แต่ไม่ทำซ้ำตัวเองได้อย่างไร

คำตอบ:


4

แทนที่จะใช้ wrappers อย่างชัดเจนคุณสามารถใช้ประโยชน์จากระบบพิมพ์ได้ โปรดทราบว่ารุ่น preciseที่ใช้Maybeสำหรับข้อมูลโค้ดแรกของคุณ:

precise :: Maybe (Integer, v) -> TreeMap v -> Maybe (Integer, v)
precise closestSoFar Leaf = closestSoFar
precise closestSoFar (Node k v l r) = case i `compare` k of
  LT -> precise closestSoFar l
  EQ -> Just (k, v)
  GT -> precise (Just (k, v)) r

เกือบจะเป็นอัลกอริธึมแบบเดียวกับรุ่นที่preciseไม่ได้Maybeมาจากข้อมูลโค้ดที่สองของคุณซึ่งสามารถเขียนในIdentityfunctor เป็น:

precise :: Identity (Integer, v) -> TreeMap v -> Identity (Integer, v)
precise closestSoFar Leaf = closestSoFar
precise closestSoFar (Node k v l r) = case i `compare` k of
  LT -> precise closestSoFar l
  EQ -> Identity (k, v)
  GT -> precise (Identity (k, v)) r

สิ่งเหล่านี้สามารถรวมเป็น polymorphic รุ่นในApplicative:

precise :: (Applicative f) => f (Integer, v) -> TreeMap v -> f (Integer, v)
precise closestSoFar Leaf = closestSoFar
precise closestSoFar (Node k v l r) = case i `compare` k of
  LT -> precise closestSoFar l
  EQ -> pure (k, v)
  GT -> precise (pure (k, v)) r

แต่ถ้าเรารู้ว่าGTสาขาจะคืนค่าเสมอเราสามารถบังคับให้มันทำงานในIdentityfunctor โดยไม่คำนึงถึง functor เริ่มต้น นั่นคือเราสามารถเริ่มต้นในMaybefunctor แต่ถอนตัวเป็นIdentityfunctor ในGTสาขา:

closestLess :: Integer -> TreeMap v -> Maybe (Integer, v)
closestLess i = precise Nothing
  where
    precise :: (Applicative t) => t (Integer, v) -> TreeMap v -> t (Integer, v)
    precise closestSoFar Leaf = closestSoFar
    precise closestSoFar (Node k v l r) = case i `compare` k of
      LT -> precise closestSoFar l
      EQ -> pure (k, v)
      GT -> pure . runIdentity $ precise (Identity (k, v)) r

ใช้งานได้ดีกับกรณีทดสอบของคุณ:

> isJust $ closestLess 5 (Node 3 () Leaf undefined)
True

และเป็นตัวอย่างที่ดีของการเรียกซ้ำ polymorphic

อีกสิ่งที่ดีเกี่ยวกับวิธีการนี้จากมุมมองของประสิทธิภาพคือการ-ddump-simplแสดงให้เห็นว่าไม่มีการห่อหุ้มหรือพจนานุกรม ทุกอย่างถูกลบในระดับประเภทพร้อมกับฟังก์ชั่นพิเศษสำหรับฟังก์ชั่นสองตัว:

closestLess
  = \ @ v i eta ->
      letrec {
        $sprecise
        $sprecise
          = \ @ v1 closestSoFar ds ->
              case ds of {
                Leaf -> closestSoFar;
                Node k v2 l r ->
                  case compareInteger i k of {
                    LT -> $sprecise closestSoFar l;
                    EQ -> (k, v2) `cast` <Co:5>;
                    GT -> $sprecise ((k, v2) `cast` <Co:5>) r
                  }
              }; } in
      letrec {
        $sprecise1
        $sprecise1
          = \ @ v1 closestSoFar ds ->
              case ds of {
                Leaf -> closestSoFar;
                Node k v2 l r ->
                  case compareInteger i k of {
                    LT -> $sprecise1 closestSoFar l;
                    EQ -> Just (k, v2);
                    GT -> Just (($sprecise ((k, v2) `cast` <Co:5>) r) `cast` <Co:4>)
                  }
              }; } in
      $sprecise1 Nothing eta

2
นี่เป็นวิธีแก้ปัญหาที่ยอดเยี่ยม
luqui

3

เริ่มต้นจากการใช้งานที่ไม่ขี้เกียจของฉันฉัน refactored แรกที่preciseจะได้รับJustเป็นข้อโต้แย้งและสรุปประเภทของมันตาม:

data TreeMap v = Leaf | Node Integer v (TreeMap v) (TreeMap v) deriving (Show, Read, Eq, Ord)

closestLess :: Integer -> TreeMap v -> Maybe (Integer, v)
closestLess i = precise Just Nothing where
  precise :: ((Integer, v) -> t) -> t -> TreeMap v -> t
  precise _ closestSoFar Leaf = closestSoFar
  precise wrap closestSoFar (Node k v l r) = case i `compare` k of
    LT -> precise wrap closestSoFar l
    EQ -> wrap (k, v)
    GT -> precise wrap (wrap (k, v)) r

จากนั้นฉันเปลี่ยนเป็นสิ่งที่ต้องทำwrapก่อนและเรียกตัวเองด้วยidในGTกรณี:

data TreeMap v = Leaf | Node Integer v (TreeMap v) (TreeMap v) deriving (Show, Read, Eq, Ord)

closestLess :: Integer -> TreeMap v -> Maybe (Integer, v)
closestLess i = precise Just Nothing where
  precise :: ((Integer, v) -> t) -> t -> TreeMap v -> t
  precise _ closestSoFar Leaf = closestSoFar
  precise wrap closestSoFar (Node k v l r) = case i `compare` k of
    LT -> precise wrap closestSoFar l
    EQ -> wrap (k, v)
    GT -> wrap (precise id (k, v) r)

สิ่งนี้ยังคงทำงานเหมือนเดิมยกเว้นเพื่อประโยชน์ของความเกียจคร้านที่เพิ่มขึ้น


1
สิ่งเหล่านั้นidอยู่ตรงกลางระหว่างJustและสุดท้าย(k,v)ถูกกำจัดโดยคอมไพเลอร์หรือไม่? อาจจะไม่ได้ฟังก์ชั่นควรจะทึบและคุณสามารถใช้ (พิมพ์ได้ง่าย) first (1+)แทนidการคอมไพเลอร์ทุกคนรู้ แต่มันทำให้รหัสกะทัดรัด ... แน่นอนรหัสของฉันคือการเปิดเผยและสเปคของคุณที่นี่ด้วยการทำให้เข้าใจง่ายขึ้นเพิ่มเติม (การกำจัดของids) ยังน่าสนใจมากว่าประเภททั่วไปมากขึ้นทำหน้าที่เป็นข้อ จำกัด ความสัมพันธ์ระหว่างค่าที่เกี่ยวข้อง (ไม่แน่นพอแม้ว่าfirst (1+)จะได้รับอนุญาตเป็นwrap)
Will Ness

1
(ต่อ) polymorphic ของคุณpreciseจะถูกใช้ในสองประเภทซึ่งตรงกับฟังก์ชันพิเศษทั้งสองที่ใช้ในตัวแปร verbose มีปฏิสัมพันธ์ที่ดีที่นั่น นอกจากนี้ฉันจะไม่เรียก CPS wrapนี้ไม่ได้ใช้เป็นความต่อเนื่องมันไม่ได้สร้างขึ้น "ข้างใน" มันซ้อนกัน - โดยการสอบถามซ้ำ - ด้านนอก บางทีถ้ามันถูกใช้เป็นความต่อเนื่องคุณสามารถกำจัดสิ่งภายนอกidได้ ... btw เราสามารถดูที่นี่อีกครั้งว่ารูปแบบการทำงานแบบเก่าที่ใช้เป็นตัวบ่งชี้ว่าจะทำอย่างไรสลับไปมาระหว่างสองหลักสูตรของการกระทำ ( Justหรือid)
Will Ness

3

ฉันคิดว่าเวอร์ชัน CPS ที่คุณตอบด้วยตัวคุณเองนั้นดีที่สุด แต่สำหรับความสมบูรณ์ที่นี่เป็นแนวคิดเพิ่มเติมอีกเล็กน้อย (แก้ไข: คำตอบของ Buhr ตอนนี้กลายเป็นนักแสดงมากที่สุด)

แนวคิดแรกคือกำจัดตัวclosestSoFarสะสม "" และปล่อยให้GTเคสจัดการกับตรรกะทั้งหมดของการเลือกค่าที่ถูกที่สุดที่เล็กกว่าอาร์กิวเมนต์ ในแบบฟอร์มนี้GTกรณีสามารถส่งคืนได้โดยตรงJust:

closestLess1 :: Integer -> TreeMap v -> Maybe (Integer, v)
closestLess1 _ Leaf = Nothing
closestLess1 i (Node k v l r) =
  case i `compare` k of
    LT -> closestLess1 i l
    EQ -> Just (k, v)
    GT -> Just (fromMaybe (k, v) (closestLess1 i r))

วิธีนี้ง่ายกว่า แต่ใช้พื้นที่มากขึ้นในสแต็กเมื่อคุณมีหลายGTกรณี ในทางเทคนิคคุณสามารถใช้สิ่งนั้นfromMaybeในรูปแบบของตัวสะสม (เช่นแทนที่fromJustคำตอบโดยนัยในคำตอบของ luqui) แต่นั่นอาจเป็นสาขาที่ซ้ำซ้อนและไม่สามารถเข้าถึงได้

ความคิดอื่น ๆ ที่มี "เฟส" ของอัลกอริทึมสองตัวหนึ่งก่อนหน้าและหลังจากคุณกด a GTดังนั้นคุณกำหนดพารามิเตอร์โดยบูลีนเพื่อแสดงสองเฟสนี้และใช้ชนิดที่พึ่งพาเพื่อเข้ารหัสค่าคงที่ที่จะมี ส่งผลให้ในระยะที่สอง

data SBool (b :: Bool) where
  STrue :: SBool 'True
  SFalse :: SBool 'False

type family MaybeUnless (b :: Bool) a where
  MaybeUnless 'True a = a
  MaybeUnless 'False a = Maybe a

ret :: SBool b -> a -> MaybeUnless b a
ret SFalse = Just
ret STrue = id

closestLess2 :: Integer -> TreeMap v -> Maybe (Integer, v)
closestLess2 i = precise SFalse Nothing where
  precise :: SBool b -> MaybeUnless b (Integer, v) -> TreeMap v -> MaybeUnless b (Integer, v)
  precise _ closestSoFar Leaf = closestSoFar
  precise b closestSoFar (Node k v l r) = case i `compare` k of
    LT -> precise b closestSoFar l
    EQ -> ret b (k, v)
    GT -> ret b (precise STrue (k, v) r)

ฉันไม่คิดว่าคำตอบของฉันเป็น CPS จนกว่าคุณจะชี้ให้เห็น ฉันคิดถึงบางสิ่งที่ใกล้เคียงกับการแปลงร่างของคนทำงาน ฉันเดาเรย์มอนด์เฉินโต้อีกครั้ง!
Joseph Sible-Reinstate Monica

2

เกี่ยวกับ

GT -> let Just v = precise (Just (k,v) r) in Just v

?


เพราะนั่นเป็นรูปแบบที่ไม่สมบูรณ์ แม้ว่าฟังก์ชั่นของฉันทั้งหมดจะเป็นทั้งหมด แต่ฉันไม่ชอบบางส่วนของมันที่เป็นบางส่วน
Joseph Sible-Reinstate Monica

ดังนั้นคุณจึงพูดว่า "เรารู้แน่" ยังมีข้อสงสัยอยู่ บางทีนั่นอาจเป็นประโยชน์ต่อร่างกาย
luqui

เรารู้อย่างแน่นอนเนื่องจากการบล็อกโค้ดที่สองของฉันในคำถามของฉันมักจะส่งคืนผลJustรวมเสมอ ฉันรู้ว่าวิธีแก้ปัญหาของคุณตามที่เขียนไว้นั้นเป็นความจริงโดยรวม แต่การเปราะบางที่การเปลี่ยนแปลงที่ดูเหมือนปลอดภัยอาจส่งผลให้จุดต่ำสุดได้
Joseph Sible-Reinstate Monica

สิ่งนี้จะทำให้โปรแกรมช้าลงเล็กน้อยเนื่องจาก GHC ไม่สามารถพิสูจน์ได้ว่าจะเป็นJustเช่นนั้นเสมอดังนั้นจะเพิ่มการทดสอบเพื่อให้แน่ใจว่าไม่ใช่Nothingทุกครั้งที่มีการเรียกซ้ำ
Joseph Sible-Reinstate Monica

1

ไม่เพียง แต่เราจะรู้ดีเสมอJust, หลังจากการค้นพบครั้งแรกของเรายังเคยรู้Nothing จนแล้ว นั่นคือ "logics" สองแบบที่แตกต่างกัน

ดังนั้นเราไปทางซ้ายแรกของทั้งหมดเพื่อให้ที่ชัดเจน:

data TreeMap v = Leaf | Node Integer v (TreeMap v) (TreeMap v) 
                 deriving (Show, Read, Eq, Ord)

closestLess :: Integer 
            -> TreeMap v 
            -> Maybe (Integer, v)
closestLess i = goLeft 
  where
  goLeft :: TreeMap v -> Maybe (Integer, v)
  goLeft n@(Node k v l _) = case i `compare` k of
          LT -> goLeft l
          _  -> Just (precise (k, v) n)
  goLeft Leaf = Nothing

  -- no more maybe if we're here
  precise :: (Integer, v) -> TreeMap v -> (Integer, v)
  precise closestSoFar Leaf           = closestSoFar
  precise closestSoFar (Node k v l r) = case i `compare` k of
        LT -> precise closestSoFar l
        EQ -> (k, v)
        GT -> precise (k, v) r

ราคาคือเราทำซ้ำอย่างมากที่สุดหนึ่งขั้นในครั้งเดียว

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.