Bu algoritmayı kendimi tekrar etmeden nasıl daha tembel hale getirebilirim?


9

( Bu soruya verdiğim yanıttan ilham aldım .)

Bu kodu göz önünde bulundurun (belirli bir girdiden küçük veya ona eşit olan en büyük öğeyi bulması gerekir):

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

Bu çok tembel değil. GTVaka girildikten sonra, nihai dönüş değerinin bundan Justziyade bir şey olacağını biliyoruz Nothing, ancak Justyine de sonuna kadar mevcut değil. O nedenle bu lazier yapmak istiyorum Justen kısa sürede kullanılabilir GTdurumda girilir. Bunun için test durumum, dipten ziyade Data.Maybe.isJust $ closestLess 5 (Node 3 () Leaf undefined)değerlendirmek istediğimdir True. Bunu yapmayı düşünebileceğim bir yol var:

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

Ancak, şimdi kendimi tekrarlıyorum: çekirdek mantık şimdi hem içinde hem de closestLessiçinde precise. Tembel olmak için kendimi tekrar etmeden nasıl yazabilirim?

Yanıtlar:


4

Müstehcen sarmalayıcılar kullanmaktansa, tür sisteminden yararlanabilirsiniz. İlk kod snippet'iniz için precisekullanılan sürümün 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

functor'da şu şekilde yazılabilen ikinci kod snippet'iniz preciseolmadan sürümüyle hemen hemen aynı algoritmadır :MaybeIdentity

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

Bunlar aşağıdakilerdeki bir polimorfik versiyonda birleştirilebilir 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

Kendi başına, bu pek bir şey başaramaz, ancak GTdalın her zaman bir değer döndüreceğini Identitybilersek, başlangıç ​​işlevinden bağımsız olarak onu işleç içinde çalışmaya zorlayabiliriz . Yani, Maybefunctor'da başlayabiliriz, ancak daldaki Identityfunctor'a geri dönebiliriz 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

Bu, test durumunuzla iyi çalışır:

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

ve polimorfik özyinelemenin güzel bir örneğidir.

Performans açısından bu yaklaşımla ilgili bir başka güzel şey de -ddump-simpl, ambalaj veya sözlük olmadığını gösterir. Her biri iki işlev için özel işlevlerle tür düzeyinde silinmiştir:

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
Bu oldukça güzel bir çözüm
luqui

3

Tembel olmayan uygulamamdan başlayarak, ilk olarak bir argüman olarak precisekabul ettim Justve türünü buna göre genelleştirdim:

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

Sonra, yapacak değiştirdim wraperken ve kendini çağırır idiçinde GTdurum:

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)

Eklenen tembelliğin yararı hariç, bu hala eskisi gibi çalışıyor.


1
Tüm idbunlar ortada Justve sonda (k,v)derleyici tarafından ortadan kaldırıldı mı? Muhtemelen hayır, fonksiyonların opak olması gerekiyordu ve tüm derleyici first (1+)yerine (tip-uygulanabilir) kullanmış olabilirsiniz id. ama bu kompakt bir kod yapar ... tabii ki, benim kod burada çözülme ve şartname, ek basitleştirme ( ids ortadan kaldırılması ) ile. aynı zamanda çok daha genel tip bir kısıtlama olarak nasıl sunduğu ilginç, ilgili değerler arasında bir ilişki (ile değil sıkı yeterli olsa da, first (1+)olarak izin verilmesi wrap).
Ness Ness

1
(devam) polimorfik precise, daha ayrıntılı varyantta kullanılan iki özel fonksiyona doğrudan karşılık gelen iki tipte kullanılır. orada güzel bir etkileşim. Ayrıca, bu CPS demezdim, wrapbir süreklilik olarak kullanılmaz, "içeride" inşa edilmez, dışarıda - özyineleme ile - istiflenir. O Belki edildi devamı olarak kullanılan o yabancı temizleyecektir idbtw biz eylem (iki kursları arasında geçiş fonksiyonel argüman eski model ne yapacağını göstergesi olarak kullanılan bir kez daha burada görebilirsiniz ... s Justveya id).
Ness Ness

3

Kendinizle cevapladığınız CPS versiyonunun en iyisi olduğunu düşünüyorum, ancak burada tamlık için birkaç fikir daha var. (DÜZENLEME: Buhr'un cevabı artık en yüksek performans.)

İlk fikir, " closestSoFar" akümülatöründen kurtulmak ve bunun yerine, GTdurumun argümandan en küçük değeri seçmenin tüm mantığını işlemesine izin vermektir . Bu formda GTdava doğrudan aşağıdakileri döndürebilir 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))

Bu daha basit, ancak çok sayıda GTvakaya çarptığınızda yığın üzerinde biraz daha fazla yer kaplıyor . Teknik olarak bunu fromMaybeakümülatör formunda bile kullanabilirsiniz (yani, fromJustluqui'nin cevabındaki örtük olanı değiştirmek ), ancak bu gereksiz, ulaşılamaz bir dal olacaktır.

Algoritmanın gerçekten iki "aşaması" olduğu bir diğer fikir, a tuşuna basmadan önce ve bir tane sonra GT, bu nedenle bu iki fazı temsil etmek için bir boole ile parametreleştirirsiniz ve her zaman bir ikinci aşama ile sonuçlanır.

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)

Siz cevaplayana kadar cevabımı CPS olarak düşünmedim. İşçi sarıcı dönüşümüne daha yakın bir şey düşünüyordum. Sanýrým Raymond Chen yine vuruyor!
Joseph Sible-Reinstate Monica

2

Peki ya

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

?


Çünkü bu tamamlanmamış bir kalıp eşleşmesi. İşlevim bir bütün olsa bile, parçalarının kısmi olmasını sevmiyorum.
Joseph Sible-Reinstate Monica

"Şüphesiz biz biliyoruz" dediniz. Belki de sağlıklıdır.
luqui

Eminim, sorumun ikinci kod bloğumun her zaman geri döndüğü göz önüne alındığında Justtoplam. Yazılı olarak çözümünüzün aslında toplam olduğunu biliyorum, ancak görünüşte güvenli bir modifikasyonun dipte sonuçlanabilmesi kırılgandır.
Joseph Sible-Reinstate Monica

Bu da programı biraz yavaşlatacaktır, çünkü GHC her zaman olacağını kanıtlayamaz, bu Justyüzden Nothingher seferinde geri almadığından emin olmak için bir test ekler .
Joseph Sible-Reinstate Monica

1

Sadece biz hep biliyoruz Just, sonra ilk keşif, biz de hep biliyoruz Nothing kadar sonra. Bu aslında iki farklı "mantık".

Her şeyden önce sola gidiyoruz, bu yüzden bunu açıkça belirtin :

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

Fiyat en fazla bir adım en fazla bir kez tekrarlanır .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.