【问题标题】:How to implement Floyd's Hare and Tortoise algorithm in Agda?如何在 Agda 中实现 Floyd 的兔兔算法?
【发布时间】:2016-05-29 14:07:29
【问题描述】:

我想把下面的 Haskell 代码翻译成 Agda:

import Control.Arrow (first)
import Control.Monad (join)

safeTail :: [a] -> [a]
safeTail []     = []
safeTail (_:xs) = xs

floyd :: [a] -> [a] -> ([a], [a])
floyd xs     []     = ([], xs)
floyd (x:xs) (_:ys) = first (x:) $ floyd xs (safeTail ys)

split :: [a] -> ([a], [a])
split = join floyd

这使我们可以有效地将列表一分为二:

split [1,2,3,4,5]   = ([1,2,3], [4,5])
split [1,2,3,4,5,6] = ([1,2,3], [4,5,6])

所以,我尝试将此代码转换为 Agda:

floyd : {A : Set} → List A → List A → List A × List A
floyd xs        []        = ([] , xs)
floyd (x :: xs) (_ :: ys) = first (x ::_) (floyd xs (safeTail ys))

唯一的问题是 Agda 抱怨我错过了floyd [] (y :: ys) 的案例。但是,这种情况绝不应该出现。我如何向 Agda 证明这种情况永远不会发生?


这是该算法如何工作的直观示例:

+-------------+-------------+
|   Tortoise  |     Hare    |
+-------------+-------------+
| ^ 1 2 3 4 5 | ^ 1 2 3 4 5 |
| 1 ^ 2 3 4 5 | 1 2 ^ 3 4 5 |
| 1 2 ^ 3 4 5 | 1 2 3 4 ^ 5 |
| 1 2 3 ^ 4 5 | 1 2 3 4 5 ^ |
+-------------+-------------+

这是另一个例子:

+---------------+---------------+
|    Tortoise   |      Hare     |
+---------------+---------------+
| ^ 1 2 3 4 5 6 | ^ 1 2 3 4 5 6 |
| 1 ^ 2 3 4 5 6 | 1 2 ^ 3 4 5 6 |
| 1 2 ^ 3 4 5 6 | 1 2 3 4 ^ 5 6 |
| 1 2 3 ^ 4 5 6 | 1 2 3 4 5 6 ^ |
+---------------+---------------+

当兔子(floyd 的第二个参数)到达列表的末尾时,乌龟(floyd 的第一个参数)到达列表的中间。因此,通过使用两个指针(第二个的移动速度是第一个的两倍),我们可以有效地将一个列表一分为二。

【问题讨论】:

  • Why 累加器?
  • 这种floyd acc [] (y ∷ ys) 的情况可能会出现,只需直接调用该函数即可。你可以给它一个虚拟的实现。
  • @Aadit M Shah,效率不高——没有必要严格,这意味着您不能尽快对列表的元素进行垃圾收集,这会导致性能非常差。没有人使用尾递归定义像 map_++_ 和其他函数,尤其是在依赖类型设置中。
  • 你可以给它类型floyd : {A : Set} → List A → (xs : List A) → (ys : List A) → length xs ≥ length ys → List A × List A
  • 好的,这是我第一次听说这个算法,它看起来很简洁,快速的谷歌搜索给了我它的变体/替代方案,实现了与你不同的目标。请您解释一下这个特定的弗洛伊德应该实现的目标。有了这样的规范,我们就可以讨论它的依赖类型了;)

标签: haskell functional-programming agda theorem-proving floyd-cycle-finding


【解决方案1】:

Twan van Laarhoven 在 cmets 中建议的内容相同,但使用 Vecs。他的版本可能更好。

open import Function
open import Data.Nat.Base
open import Data.Product renaming (map to pmap)
open import Data.List.Base
open import Data.Vec hiding (split)

≤-step : ∀ {m n} -> m ≤ n -> m ≤ suc n
≤-step  z≤n     = z≤n
≤-step (s≤s le) = s≤s (≤-step le)

≤-refl : ∀ {n} -> n ≤ n
≤-refl {0}     = z≤n
≤-refl {suc n} = s≤s ≤-refl

floyd : ∀ {A : Set} {n m} -> m ≤ n -> Vec A n -> Vec A m -> List A × List A
floyd  z≤n            xs       []          = [] , toList xs
floyd (s≤s  z≤n)     (x ∷ xs) (y ∷ [])     = x ∷ [] , toList xs
floyd (s≤s (s≤s le)) (x ∷ xs) (y ∷ z ∷ ys) = pmap (x ∷_) id (floyd (≤-step le) xs ys)

split : ∀ {A : Set} -> List A -> List A × List A
split xs = floyd ≤-refl (fromList xs) (fromList xs)

open import Relation.Binary.PropositionalEquality

test₁ : split (1 ∷ 2 ∷ 3 ∷ 4 ∷ 5 ∷ []) ≡ (1 ∷ 2 ∷ 3 ∷ [] , 4 ∷ 5 ∷ [])
test₁ = refl

test₂ : split (1 ∷ 2 ∷ 3 ∷ 4 ∷ 5 ∷ 6 ∷ []) ≡ (1 ∷ 2 ∷ 3 ∷ [] , 4 ∷ 5 ∷ 6 ∷ [])
test₂ = refl

另外,≤-refl≤-step 之类的函数在标准库中的某个位置,但我懒得去搜索。


这是我like要做的一件傻事:

open import Function
open import Data.Nat.Base
open import Data.Product renaming (map to pmap)
open import Data.List.Base
open import Data.Vec hiding (split)

floyd : ∀ {A : Set}
      -> (k : ℕ -> ℕ)
      -> (∀ {n} -> Vec A (k (suc n)) -> Vec A (suc (k n)))
      -> ∀ n
      -> Vec A (k n)
      -> List A × List A
floyd k u  0            xs = [] , toList xs
floyd k u  1            xs with u xs
... | x ∷ xs' = x ∷ [] , toList xs'
floyd k u (suc (suc n)) xs with u xs
... | x ∷ xs' = pmap (x ∷_) id (floyd (k ∘ suc) u n xs')

split : ∀ {A : Set} -> List A -> List A × List A
split xs = floyd id id (length xs) (fromList xs)

open import Relation.Binary.PropositionalEquality

test₁ : split (1 ∷ 2 ∷ 3 ∷ 4 ∷ 5 ∷ []) ≡ (1 ∷ 2 ∷ 3 ∷ [] , 4 ∷ 5 ∷ [])
test₁ = refl

test₂ : split (1 ∷ 2 ∷ 3 ∷ 4 ∷ 5 ∷ 6 ∷ []) ≡ (1 ∷ 2 ∷ 3 ∷ [] , 4 ∷ 5 ∷ 6 ∷ [])
test₂ = refl

这部分基于以下评论中的 Benjamin Hodgson 建议。

【讨论】:

  • 您通常可以通过要求一个Vec 的长度是另一个长度的“某物加”另一个来省去 的证明:forall {m} -> Vec A n -> Vec A (n + m) -> ...
  • 我认为您可能有错字,因为您从未在 floyd 中真正使用过 ys ... cf ideone.com/aKcgNZ 之前由 @user3237465 作为评论发布
  • @Musa Al-hassy,我们需要ys的唯一用途是比xs快两倍。
  • @BenjaminHodgson 但是你必须证明你可以将n 拆分为ceil (n / 2) + floor (n / 2) 才能启动算法。这就是弗洛伊德最初似乎在做的事情。
  • ≤-refl≤-stepData.Nat.Properties
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-12-11
  • 1970-01-01
  • 2014-10-13
  • 1970-01-01
  • 2012-12-17
  • 2013-02-24
  • 2011-11-19
相关资源
最近更新 更多