【发布时间】: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