如何在线性时间内通过`Fin`枚举列表的元素?

时间:2015-10-26 12:33:46

标签: time-complexity agda dependent-type idris

我们可以列举这样的列表元素:

-- enumerate-ℕ = zip [0..]
enumerate-ℕ : ∀ {α} {A : Set α} -> List A -> List (ℕ × A)
enumerate-ℕ = go 0 where
  go : ∀ {α} {A : Set α} -> ℕ -> List A -> List (ℕ × A)
  go n  []      = []
  go n (x ∷ xs) = (n , x) ∷ go (ℕ.suc n) xs

E.g。 enumerate-ℕ (1 ∷ 3 ∷ 2 ∷ 5 ∷ [])等于(0 , 1) ∷ (1 , 3) ∷ (2 , 2) ∷ (3 , 5) ∷ []。假设Agda中存在共享,则函数是线性的。

但是,如果我们尝试按Fin而不是来枚举列表的元素,则该函数变为二次方:

enumerate-Fin : ∀ {α} {A : Set α} -> (xs : List A) -> List (Fin (length xs) × A)
enumerate-Fin  []      = []
enumerate-Fin (x ∷ xs) = (zero , x) ∷ map (pmap suc id) (enumerate-Fin xs)

是否可以按线性时间Fin枚举?

1 个答案:

答案 0 :(得分:5)

将此视为首次尝试:

go : ∀ {m α} {A : Set α} -> Fin m -> (xs : List A) -> List (Fin (m + length xs) × A)
go i  []      = []
go i (x ∷ xs) = (inject+ _ i , x) ∷ {!go (suc i) xs!}

i会在每次递归调用时增长,但是存在不匹配:

目标的类型是List (Fin (.m + suc (length xs)) × .A)

表达式的类型是洞是List (Fin (suc (.m + length xs)) × .A)

很容易证明两种类型相同,但它也很脏。这是一个常见的问题:一个参数增长而另一个参数降低,因此我们需要定义交换_+_来处理这两种情况,但是没有办法定义它。解决方案是使用CPS:

go : ∀ {α} {A : Set α} -> (k : ℕ -> ℕ) -> (xs : List A) -> List (Fin (k (length xs)) × A)
go k  []      = []
go k (x ∷ xs) = ({!!} , x) ∷ go (k ∘ suc) xs

(k ∘ suc) (length xs)k (length (x ∷ xs))相同,因此不匹配是固定的,但现在i是什么?洞的类型是Fin (k (suc (length xs))),它在当前环境中无人居住,所以让我们介绍一些居民:

go : ∀ {α} {A : Set α}
   -> (k : ℕ -> ℕ)
   -> (∀ {n} -> Fin (k (suc n)))
   -> (xs : List A)
   -> List (Fin (k (length xs)) × A)
go k i  []      = []
go k i (x ∷ xs) = (i , x) ∷ go (k ∘ suc) {!!} xs

新洞的类型是{n : ℕ} → Fin (k (suc (suc n)))。我们可以使用i填充它,但i必须在每次递归调用时增长。但是,suck不会通勤,因此suc iFin (suc (k (suc (_n_65 k i x xs))))。所以我们在suc下添加k s的另一个参数,最终的定义是

enumerate-Fin : ∀ {α} {A : Set α} -> (xs : List A) -> List (Fin (length xs) × A)
enumerate-Fin = go id suc zero where
  go : ∀ {α} {A : Set α}
     -> (k : ℕ -> ℕ)
     -> (∀ {n} -> Fin (k n) -> Fin (k (suc n)))
     -> (∀ {n} -> Fin (k (suc n)))
     -> (xs : List A)
     -> List (Fin (k (length xs)) × A)
  go k s i  []      = []
  go k s i (x ∷ xs) = (i , x) ∷ go (k ∘ suc) s (s i) xs

有效,因为s : {n : ℕ} → Fin (k n) → Fin (k (suc n))可以视为{n : ℕ} → Fin (k (suc n)) → Fin (k (suc (suc n)))

测试:C-c C-n enumerate-Fin (1 ∷ 3 ∷ 2 ∷ 5 ∷ [])给出了

(zero , 1) ∷
(suc zero , 3) ∷
(suc (suc zero) , 2) ∷ (suc (suc (suc zero)) , 5) ∷ []

现在请注意,enumerate-Fin k始终跟随Fin类型。因此,我们可以抽象Fin ∘ k并获得适用于Fin的函数的通用版本:

genumerate : ∀ {α β} {A : Set α}
           -> (B : ℕ -> Set β)
           -> (∀ {n} -> B n -> B (suc n))
           -> (∀ {n} -> B (suc n))
           -> (xs : List A)
           -> List (B (length xs) × A)
genumerate B s i  []      = []
genumerate B s i (x ∷ xs) = (i , x) ∷ genumerate (B ∘ suc) s (s i) xs

enumerate-ℕ : ∀ {α} {A : Set α} -> List A -> List (ℕ × A)
enumerate-ℕ = genumerate _ suc 0

enumerate-Fin : ∀ {α} {A : Set α} -> (xs : List A) -> List (Fin (length xs) × A)
enumerate-Fin = genumerate Fin suc zero