Wiki Agenda Contact English version

Tortoise and hare algorithm

Floyd's cycle detection algorithm, also known as ``tortoise and hare algorithm''.


Auteurs: Jean-Christophe Filliâtre

Catégories: Tricky termination

Outils: Why3

Références: The Art of Computer Programming / The COST FoVeOOS'11 Competition

see also the index (by topic, by tool, by reference, by year)


(* Floyd's cycle detection, also known as ``tortoise and hare'' algorithm.

   See The Art of Computer Programming, vol 2, exercise 6 page 7. *)

module TortoiseAndHareAlgorithm

  use int.Int
  use int.EuclideanDivision
  use int.Iter
  use seq.Seq
  use seq.Distinct
  use ref.Refint
  use pigeon.Pigeonhole

  val function f int : int

  (* f maps 0..m-1 to 0..m-1 *)
  constant m: int
  axiom m_positive: m > 0

  axiom f_range: forall x. 0 <= x < m -> 0 <= f x < m

  (* given some initial value x0 *)
  val constant x0: int
    ensures { 0 <= result < m }

  (* we can build the infinite sequence defined by x{i+1} = f(xi) *)
  function x (i: int): int = iter f i x0

  let rec lemma x_in_range (n: int)
    requires { 0 <= n } ensures { 0 <= x n < m }
    variant  { n }
  = if n > 0 then x_in_range (n - 1)

  (* First, we prove the existence of mu and lambda, with a ghost program.
     Starting with x0, we repeatedly apply f, storing the elements in
     a sequence, until we find a repetition. *)
  let ghost periodicity () : (mu:int, lambda:int)
    ensures { 0 <= mu < m /\ 1 <= lambda <= m /\ mu + lambda <= m /\
              x (mu + lambda) = x mu }
    ensures { forall i j. 0 <= i < j < mu + lambda -> x i <> x j }
  = let s = ref (singleton x0) in
    let cur = ref x0 in
    for k = 1 to m do
      invariant { 1 <= length !s = k <= m /\ !cur = !s[length !s - 1] }
      invariant { forall i. 0 <= i < length !s -> !s[i] = x i }
      invariant { distinct !s }
      cur := f !cur;
      (* look for a repetition *)
      for mu = 0 to length !s - 1 do
        invariant { forall i. 0 <= i < mu -> !s[i] <> !cur }
        if !cur = !s[mu] then return (mu, length !s - mu)
      done;
      s := snoc !s !cur;
      if k = m then pigeonhole (m+1) m (pure { fun i -> !s[i] })
    done;
    absurd

  (* Then we can state the condition for two elements to be equal. *)
  let lemma equality (mu lambda: int)
    requires { 0 <= mu < m /\ 1 <= lambda <= m /\ mu + lambda <= m /\
               x (mu + lambda) = x mu }
    requires { forall i j. 0 <= i < j < mu + lambda -> x i <> x j }
    ensures  { forall r n. r > n >= 0 ->
               x r = x n <-> n >= mu /\ exists k. k >= 1 /\ r-n = k*lambda }
  = let rec lemma plus_lambda (n: int) variant { n }
      requires { mu <= n }
      ensures  { x (n + lambda) = x n } =
      if n > mu then plus_lambda (n - 1) in
    let rec lemma plus_lambdas (n k: int) variant { k }
      requires { mu <= n } requires { 0 <= k }
      ensures  { x (n + k * lambda) = x n } =
      if k > 0 then begin
        plus_lambdas n (k - 1); plus_lambda (n + (k - 1) * lambda) end in
    let decomp (i: int) : (qi:int, mi:int)
      requires { i >= mu }
      ensures  { i = mu + qi * lambda + mi && qi >= 0 &&
                 0 <= mi < lambda && x i = x (mu + mi) } =
      let qi = div (i - mu) lambda in
      let mi = mod (i - mu) lambda in
      plus_lambdas (mu + mi) qi;
      qi, mi in
    let lemma equ (r n: int)
      requires { r > n >= 0 } requires { x r = x n }
      ensures  { n >= mu /\ exists k. k >= 1 /\ r-n = k*lambda } =
      if n < mu then (if r >= mu then let _ = decomp r in absurd)
      else begin
        let qr,mr = decomp r in let qn, mn = decomp n in
        assert { mr = mn };
        assert { r-n = (qr-qn) * lambda }
      end in
    ()

  (* Finally, we implement the tortoise and hare algorithm that computes
     the values of mu and lambda in time O(mu+lambda) and constant space *)
  let tortoise_and_hare () : (mu:int, lambda:int)
    ensures { 0 <= mu < m /\ 1 <= lambda <= m /\ mu + lambda <= m /\
              x (mu + lambda) = x mu }
    ensures { forall i j. 0 <= i < j < mu + lambda -> x i <> x j }
  = [@vc:do_not_keep_trace] (* traceability info breaks the proofs *)
    let mu, lambda = periodicity () in
    equality mu lambda;
    (* the first loop implements the tortoise and hare,
       and finds the smallest n >= 1 such that x n = x (2n), in O(mu+lambda) *)
    let tortoise = ref (f x0) in
    let hare = ref (f (f x0)) in
    let n = ref 1 in
    while !tortoise <> !hare do
      invariant {
        1 <= !n <= mu+lambda /\ !tortoise = x !n /\ !hare = x (2 * !n) /\
        forall i. 1 <= i < !n -> x i <> x (2*i) }
      variant { mu + lambda - !n }
      tortoise := f !tortoise;
      hare     := f (f !hare);
      incr n;
      if !n > mu + lambda then begin
        let m = lambda - mod mu lambda in
        let i = mu + m in
        assert { 2*i - i = (div mu lambda + 1) * lambda };
        absurd
      end
    done;
    let n = !n in
    assert { n >= mu };
    assert { exists k. k >= 1 /\ n = k * lambda >= 1 };
    assert { forall j. j >= mu -> x j = x (j + n) };
    let xn = !tortoise in
    (* then a second loop finds mu and lambda, in O(mu) *)
    let i   = ref 0  in
    let xi  = ref x0 in (* = x i     *)
    let xni = ref xn in (* = x (n+i) *)
    let lam = ref 0  in (* 0 or lambda *)
    while !xi <> !xni do
      invariant { 0 <= !i <= mu }
      invariant { !xi = x !i /\ !xni = x (n + !i) }
      invariant { forall j. 0 <= j < !i -> x j <> x (n + j) }
      invariant { if !lam = 0 then forall j. 0 < j < !i -> x (n + j) <> x n
                  else !lam = lambda }
      variant   { mu - !i }
      if !lam = 0 && !i > 0 && !xni = xn then lam := !i;
      xi  := f !xi;
      xni := f !xni;
      incr i;
    done;
    let m = !i in
    assert { m = mu };
    let l = if !lam = 0 then n else !lam in
    assert { l = lambda };
    m, l

end

download ZIP archive