# Deque: a double-ended queue, as a front list and a reversed back list. # # Pushing and popping on either end is amortized O(1), in any mix of ends # (the refill below says why). Like List, the queries consume the deque: a # Data deque is shared with +. # # The laws at the bottom are checked every time this file is imported. import Base type Deque is Kind(a): Deq{front: List, back: List} def Deque.new(a, -A: Kind(a)) -> Deque: Deq{Nil{}, Nil{}} # the list's first element is the deque's front def Deque.from_list(a, -A: Kind(a), xs: List) -> Deque: Deq{xs, Nil{}} def Deque.push_front(a, -A: Kind(a), x: A, d: Deque) -> Deque: match d: case Deq{f, b}: Deq{x <> f, b} def Deque.push_back(a, -A: Kind(a), x: A, d: Deque) -> Deque: match d: case Deq{f, b}: Deq{f, x <> b} def Deque.count.put( a, -A: Kind(a), h: A, rn: List & Nat ) -> List & Nat: (rest, n) = rn (h <> rest, 1n+n) # a list and its length, in one pass that hands the list back def Deque.count(a, -A: Kind(a), xs: List) -> List & Nat: match xs: case Nil{}: (Nil{}, 0n) case h <> t: Deque.count.put(a, A, h, Deque.count(a, A, t)) def Deque.half(n: Nat) -> Nat: match n: case 0n: 0n case 1n+p: match p: case 0n: 0n case 1n+q: 1n+Deque.half(q) def Deque.split.put( a, -A: Kind(a), h: A, lr: List & List ) -> List & List: (l, r) = lr (h <> l, r) # the first n elements, and the rest def Deque.split(a, -A: Kind(a), n: Nat, xs: List) -> List & List: match n: case 0n: (Nil{}, xs) case 1n+p: match xs: case Nil{}: (Nil{}, Nil{}) case h <> t: Deque.split.put(a, A, h, Deque.split(a, A, p, t)) # When one end runs out, the other list is split in half and only the half # nearest the empty end is reversed over. Moving everything would let pops # that alternate ends reverse the whole deque each time; halving keeps the # lists balanced, so every operation stays amortized O(1). def Deque.pop_front.go( a, -A: Kind(a), xs: List, b: List ) -> Maybe<&1, A & Deque>: match xs: case Nil{}: None{} case h <> t: Some{(h, Deq{t, b})} def Deque.pop_front.halves( a, -A: Kind(a), sm: List & List ) -> Maybe<&1, A & Deque>: (stay, move) = sm Deque.pop_front.go(a, A, List.reverse(a, A, move), stay) def Deque.pop_front.refill( a, -A: Kind(a), bn: List & Nat ) -> Maybe<&1, A & Deque>: (b, n) = bn Deque.pop_front.halves(a, A, Deque.split(a, A, Deque.half(n), b)) def Deque.pop_front( a, -A: Kind(a), d: Deque ) -> Maybe<&1, A & Deque>: match d: case Deq{f, b}: match f: case h <> t: Some{(h, Deq{t, b})} case Nil{}: Deque.pop_front.refill(a, A, Deque.count(a, A, b)) def Deque.pop_back.go( a, -A: Kind(a), xs: List, f: List ) -> Maybe<&1, A & Deque>: match xs: case Nil{}: None{} case h <> t: Some{(h, Deq{f, t})} def Deque.pop_back.halves( a, -A: Kind(a), sm: List & List ) -> Maybe<&1, A & Deque>: (stay, move) = sm Deque.pop_back.go(a, A, List.reverse(a, A, move), stay) def Deque.pop_back.refill( a, -A: Kind(a), fn: List & Nat ) -> Maybe<&1, A & Deque>: (f, n) = fn Deque.pop_back.halves(a, A, Deque.split(a, A, Deque.half(n), f)) def Deque.pop_back( a, -A: Kind(a), d: Deque ) -> Maybe<&1, A & Deque>: match d: case Deq{f, b}: match b: case h <> t: Some{(h, Deq{f, t})} case Nil{}: Deque.pop_back.refill(a, A, Deque.count(a, A, f)) # the elements, front to back def Deque.to_list(a, -A: Kind(a), d: Deque) -> List: match d: case Deq{f, b}: List.append(a, A, f, List.reverse(a, A, b)) def Deque.length(a, -A: Kind(a), d: Deque) -> Nat: match d: case Deq{f, b}: Nat.add(List.length(a, A, f), List.length(a, A, b)) def Deque.is_empty(a, -A: Kind(a), d: Deque) -> Bool: match d: case Deq{f, b}: Bool.and(List.is_empty(a, A, f), List.is_empty(a, A, b)) # Laws # ---- # Each law reads a deque through to_list, the only view a user has of it. # LAW: a new deque reads as the empty list law Deque.to_list.new: for -A: Data {Deque.to_list(&2, A, Deque.new(&2, A)) == Nil{} : List<&2, A>} def Deque.to_list.new(A): {==} # LAW: push_front puts the element at the head of the list law Deque.to_list.push_front: for -A: Data for -x: A for d: Deque<&2, A> {Deque.to_list(&2, A, Deque.push_front(&2, A, x, d)) == x <> Deque.to_list(&2, A, d) : List<&2, A>} def Deque.to_list.push_front(A, x, d): match d: case Deq{f, b}: {==} # LAW: pop_front undoes push_front law Deque.pop_front.push_front: for -A: Data for -x: A for d: Deque<&2, A> {Deque.pop_front(&2, A, Deque.push_front(&2, A, x, d)) == Some{(x, d)} : Maybe<&1, A & Deque<&2, A>>} def Deque.pop_front.push_front(A, x, d): match d: case Deq{f, b}: {==} # LAW: pop_back undoes push_back law Deque.pop_back.push_back: for -A: Data for -x: A for d: Deque<&2, A> {Deque.pop_back(&2, A, Deque.push_back(&2, A, x, d)) == Some{(x, d)} : Maybe<&1, A & Deque<&2, A>>} def Deque.pop_back.push_back(A, x, d): match d: case Deq{f, b}: {==} # Lemmas over Base's List, by induction on the first list law Deque.lemma.append_nil: for -A: Data for xs: List<&2, A> {List.append(&2, A, xs, Nil{}) == xs : List<&2, A>} def Deque.lemma.append_nil(A, xs): match xs: case Nil{}: {==} case h <> t: %Deque.lemma.append_nil(A, t) : {h <> List.append(&2, A, t, Nil{}) == h <> _ : List<&2, A>} {==} law Deque.lemma.append_assoc: for -A: Data for xs: List<&2, A> for -ys: List<&2, A> for -zs: List<&2, A> {List.append(&2, A, List.append(&2, A, xs, ys), zs) == List.append(&2, A, xs, List.append(&2, A, ys, zs)) : List<&2, A>} def Deque.lemma.append_assoc(A, xs, ys, zs): match xs: case Nil{}: {==} case h <> t: %Deque.lemma.append_assoc(A, t, ys, zs) : {h <> List.append(&2, A, List.append(&2, A, t, ys), zs) == h <> _ : List<&2, A>} {==} # LAW: a list read into a deque reads back as itself law Deque.to_list.from_list: for -A: Data for xs: List<&2, A> {Deque.to_list(&2, A, Deque.from_list(&2, A, xs)) == xs : List<&2, A>} def Deque.to_list.from_list(A, xs): Deque.lemma.append_nil(A, xs) # reversing onto an accumulator is reversing, then appending it law Deque.lemma.reverse_go: for -A: Data for xs: List<&2, A> for -acc: List<&2, A> {List.append(&2, A, List.reverse.go(&2, A, xs, Nil{}), acc) == List.reverse.go(&2, A, xs, acc) : List<&2, A>} def Deque.lemma.reverse_go(A, xs, acc): match xs: case Nil{}: {==} case +h <> +t: %Deque.lemma.reverse_go(A, t, [h]) : {List.append(&2, A, _, acc) == List.reverse.go(&2, A, t, h <> acc) : List<&2, A>} %Deque.lemma.reverse_go(A, t, h <> acc) : {List.append(&2, A, List.append(&2, A, List.reverse.go(&2, A, t, Nil{}), [h]), acc) == _ : List<&2, A>} Deque.lemma.append_assoc(A, List.reverse.go(&2, A, t, Nil{}), [h], acc) # LAW: push_back puts the element at the end of the list law Deque.to_list.push_back: for -A: Data for -x: A for d: Deque<&2, A> {Deque.to_list(&2, A, Deque.push_back(&2, A, x, d)) == List.append(&2, A, Deque.to_list(&2, A, d), [x]) : List<&2, A>} def Deque.to_list.push_back(A, x, d): match d: case Deq{+f, +b}: %Deque.lemma.reverse_go(A, b, [x]) : {List.append(&2, A, f, _) == List.append(&2, A, List.append(&2, A, f, List.reverse(&2, A, b)), [x]) : List<&2, A>} %Deque.lemma.append_assoc(A, f, List.reverse(&2, A, b), [x]) : {_ == List.append(&2, A, List.append(&2, A, f, List.reverse(&2, A, b)), [x]) : List<&2, A>} {==}