반복하지 않고이 알고리즘을 더 게으르게 만드는 방법은 무엇입니까?


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없는 버전과 거의 정확히 동일한 알고리즘이며 functor 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시작 functor에 관계없이 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

다형성 재귀의 좋은 예입니다.

성능 관점에서이 접근 방식의 또 다른 좋은 점은 -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

비 게으른 구현에서 시작하여 먼저 인수 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초기에 자신을 호출 idGT경우 :

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
(계속) 다형성 precise은 두 가지 유형으로 사용되며, 더 자세한 변형에 사용되는 두 가지 특수 기능에 직접 대응됩니다. 좋은 상호 작용. 또한이 CPS를 호출 wrap하지 않고 연속으로 사용되지 않으며 "내부"에 구축되지 않으며 재귀에 의해 외부에 쌓입니다. 그것은 아마도 경우 연속으로 사용 당신은 그 외부의 제거 얻을 수 idBTW 우리는 행동 (두 과정 사이의 전환, 기능성 인수의 오래된 패턴이 무엇을해야하는지의 지표로 사용되는 것을 다시 한 번 여기에서 볼 수 있습니다 ... S 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으로는 누산기 형태로 도 사용할 수 있지만 (즉, fromJustluqui의 답변에서 암시 적 대체 ) 중복되고 도달 할 수없는 지점이 될 것입니다.

알고리즘에 실제로 두 개의 "단계"가 있고, 하나를 누른 후에 하나는 다른 두 가지 아이디어가 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 때까지 다음. 그것은 실제로 두 가지 다른 "논리"입니다.

그래서 우리는 우선 왼쪽으로 가서, 그래서 만들 명시 :

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.