मैं खुद को दोहराए बिना इस एल्गोरिदम को कैसे बना सकता हूं?


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

स्पष्ट आवरणों का उपयोग करने के बजाय, आप प्रकार प्रणाली का लाभ उठा सकते हैं। ध्यान दें कि उस संस्करण का 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, जिसे Identityफ़नकार में लिखा जा सकता है :

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

इन्हें एक संस्करण बहुरूपी में एकीकृत किया जा सकता है 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शाखा हमेशा एक मूल्य Identityलौटाएगी, तो हम इसे फ़नकार में चलाने के लिए बाध्य कर सकते हैं , भले ही शुरुआती फ़ंक्टर की परवाह किए बिना। यही है, हम Maybeफ़ंक्शनल में शुरू कर सकते हैं लेकिन ब्रांच Identityमें फ़ंक्टर में फिर से जा सकते हैं 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

और बहुरूपी पुनरावृत्ति का एक अच्छा उदाहरण है।

प्रदर्शन के दृष्टिकोण से इस दृष्टिकोण के बारे में एक और अच्छी बात यह है कि यह -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
यह एक बहुत अच्छा समाधान है
लूका

3

मेरे गैर-आलसी कार्यान्वयन से शुरू होकर, मैंने पहली बार एक तर्क के रूप में 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सभी कंपाइलर को जानते हैं। लेकिन यह एक कॉम्पैक्ट कोड के लिए बनाता है ... निश्चित रूप से, मेरा कोड अतिरिक्त सरलीकरण (एस के उन्मूलन id) के साथ यहां आपका और नयापन है । यह भी बहुत दिलचस्प है कि कैसे अधिक सामान्य प्रकार एक बाधा के रूप में कार्य करता है, इसमें शामिल मूल्यों के बीच एक संबंध है (हालांकि बहुत तंग नहीं है, first (1+)जैसा कि अनुमति दी जा रही है wrap)।
विल नेस

1
(contd।) आपके पॉलीमॉर्फिक preciseका उपयोग दो प्रकारों में किया जाता है, सीधे और अधिक वर्बल वेरिएंट में उपयोग किए जाने वाले दो विशेष कार्यों के लिए। वहां अच्छा इंटरप्ले हुआ। इसके अलावा, मैं इस सीपीएस को कॉल नहीं करूंगा wrap, इसे एक निरंतरता के रूप में उपयोग नहीं किया जाता है, इसे "अंदर पर" नहीं बनाया गया है, इसे स्टैक किया गया है - पुनरावृत्ति द्वारा - बाहर पर। हो सकता है कि अगर इसे निरंतरता के रूप में इस्तेमाल किया गया था, तो आप उन बाहरी लोगों से छुटकारा पा सकते थेid ... btw हम एक बार फिर यहां देख सकते हैं कि कार्यात्मक तर्क के पुराने पैटर्न को क्या करना है, कार्रवाई के दो पाठ्यक्रमों ( Justया id) के बीच स्विच करने के संकेतक के रूप में उपयोग किया जाता है ।
विल नेस

3

मुझे लगता है कि आपके द्वारा उत्तर दिया गया सीपीएस संस्करण सबसे अच्छा है लेकिन पूर्णता के लिए यहां कुछ और विचार हैं। (EDIT: बुहर का जवाब अब सबसे अधिक प्रदर्शन करने वाला है।)

पहला विचार " 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लुक्वी के उत्तर में निहित की जगह ), लेकिन यह एक निरर्थक, अनुपलब्ध शाखा होगी।

दूसरा विचार यह है कि एल्गोरिथ्म के वास्तव में दो "चरण" हैं, एक से पहले और एक के बाद आप एक को हिट करते हैं 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)

मैंने सीपीएस के रूप में मेरे जवाब के बारे में नहीं सोचा था जब तक कि आपने इसे इंगित नहीं किया। मैं एक कार्यकर्ता-आवरण परिवर्तन के करीब कुछ सोच रहा था। मुझे लगता है कि रेमंड चेन फिर से हमला करता है!
जोसेफ सिबल-रिनेटेट मोनिका

2

कैसा रहेगा

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

?


क्योंकि यह एक अधूरा पैटर्न मैच है। भले ही मेरा कार्य संपूर्ण है, मुझे यह पसंद नहीं है कि इसके अंश आंशिक हों।
जोसेफ सिबल-रिनेटेट मोनिका

तो आपने कहा "हम निश्चित रूप से जानते हैं" अभी भी कुछ संदेह के साथ। शायद वह स्वस्थ है।
19

हम निश्चित रूप से जानते हैं, यह देखते हुए कि मेरे प्रश्न में मेरा दूसरा कोड ब्लॉक हमेशा रिटर्न Justहै। मुझे पता है कि लिखित रूप में आपका समाधान वास्तव में कुल है, लेकिन यह इस बात में भंगुर है कि एक प्रतीत होता है कि सुरक्षित संशोधन इसके बाद इसे नीचे ला सकता है।
जोसेफ सिबल-बहाल मोनिका

यह भी कार्यक्रम को थोड़ा धीमा कर देगा, क्योंकि जीएचसी यह साबित नहीं कर सकता है कि यह हमेशा रहेगा Just, इसलिए यह सुनिश्चित करने के लिए एक परीक्षण जोड़ देगा कि यह Nothingहर बार जब इसके माध्यम से पुनरावृत्ति न हो ।
जोसेफ सिबल-रिनेटेट मोनिका

1

न केवल हम हमेशा जानते हैं Just, इसकी पहली खोज के बाद , हम हमेशा तब Nothing तक भी जानते हैं । यह वास्तव में दो अलग "लॉजिक्स" है।

तो, हम सब से पहले छोड़ दिया जाते हैं, ताकि बनाने कि स्पष्ट:

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

मूल्य हम एक बार में सबसे अधिक एक कदम पर दोहराते हैं ।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.