EOP Exercise Round-Up: Chapter 2

This entry is part of a series, Elements of Programming»

A big thanks to Mark Ruzon and Piotr Jachowcz who submitted homework [and a thanks to Dave for painstakingly formatting the math without LaTeX -- Sean]. Chapter 2 gave us a detailed look at the structures obtained from just a single unary operation.

With a few exceptions—Mark Ruzon concentrated on the exercises and Piotr Jachowicz focused on proving lemmas. With that, let’s get started:

Lemmas

We’ll start with an exception: Mark proved this one.

Lemma 2.1
euclidean_norm(x,y,z) = euclidean_norm(euclidean_norm(x,y),z)

euclidean_norm(x,y,z) = sqrt(x²+y²+z²) = sqrt(sqrt(x²+y²)²+z²) = sqrt(euclidean_norm(x,y)²+z²) = euclidean_norm(euclidean_norm(x,y),z).

To prove the rest, Piotr introduced “lemma 2.pj”:

Lemma 2.pj

f a( f b(x) ) = f a+b(x)

We’ll prove it by recursion with respect to a.

  1. f 0( f b(x) ) = f b(x)   (by definition)

  2. Let us assume that f a( f b(x) ) = f a+b(x). Then we have
    f a+1( f b(x))
    = f( f a( f b(x) ) )
    = f( f a+b(x) )
    = f a+b+1(x).

Recursion concludes the proof.

Lemma 2.2
An orbit does not contain both cyclic and a terminal element.

Let us assume that it contains both cyclic and terminal element. Let

xc = f c(x)

and

xt = f t(x)

be the cyclic and terminal elements, respectively.

  • The case t < c is impossible because xt is terminal, which implies t is the maximim index for which we can calculate f t(x). Therefore t >= c.

  • If xc is a cyclic element, then there exists N > 0 such that

    f c+N  =  f c(x)  =  xc.

  • Let k = tc + 1. Then

    c + kN  =  c + (t-c)N + N  >  c + (t-c)  =  t,

    and

    f c+kN(x)  =  f c(x)  =  xc.

which shows that xt is not terminating. QED

Lemma 2.3
An orbit contains at most one terminal element.

Let xt₁ = f t₁(x) and xt₂ = f t₂(x) be two terminating elements.

  • If t₁ > t₂ then xt₁ is not terminating because

    xt₂
    = f t₂(x)
    = ft₁ + (t₂t₁)(x)
    = f t₂t₁( f t₁(x) )    (by Lemma 2.pj).

    which shows that xt₁ is not terminating.

  • Similarly, it is false that t₁ < t₂. Therefore t₁ = t₂ that proves that there is only one terminating element. QED

Lemma 2.5
The distance fron any point in the orbit to a point in a cycle of that orbit is always defined.

Let xa = f a(x) belong to the orbit and xb = f b(x) belong to the cycle.

  1. If a <= b then f b-a( f a(x) )  =  f b(x)  =  xb.

  2. If b < a and cycle size is c, then

    b + c(ab + 1)  >=  b + (ab + 1)  >  a,

    and

    xb  =  f b(x)  =  f b + c(ab + 1)(x).

    and

    b + c(ab + 1) > a

so by Lemma 2.pj,

f b + c(ab. + 1) – a( f a(x) )  = f b(x)  = xb

which proves xb is reachable from xa. QED

Lemma 2.6
If x and y are distinct points in a cycle of size c, c = distance(x, y, f) + distance(y, x, f)

Let distance(x, y, f) = n. Then,

  • f n(x) = y
  • f c-n( f n(x) )  =  f c(x)  =  x.

From the other side, f c-n( f n(x) ) = f c-n(y).

It sufficies to prove that there is no m < cn such that f m(y) = x.

Let us assume that such m exists. Then

x  =  f m(y)  =  f m( f n(x) )  =  f m+n(x).

But cycle size is c, so c <= m + n. We have m < cnc <= m + n that implies m < cnm >= cn, which is impossible. That proves that distance(y, x, f) = cn. QED

Lemma 2. 7 If x and y are points in a cycle of size c, then 0 <= dist(x, y, f) < c

  1. Because y belongs to a cycle, then due to Lemma 2.5, distance(x, y, f) is defined
  2. If x = y then distance(x, y, f) = 0 which satisfies thesis
  3. If x != y, then due to Lemma 2.6, distance(x, y, f) = cdistance(y, x, f). Because distance(y, x, f) > 0, then distance(x, y, f) < c. QED

Exercises

Exercise 2.1
Implement a definition-space predicate for addition on 32-bit signed integers.
bool is_defined(std::plus<int32>, int32 x, int32 y)
  {
     return x >= 0 ? std::numeric_limits<int32>::max() - x >= y :
                     std::numeric_limits<int32>::min() - x <= y;
  }

This function can be templatized for any integral type. Only the max() comparision is necessary for unsigned types. Because of the strange behavior of numeric_limits<T>::min when T is a floating type, a different implementation would be needed for that case.

Exercise 2.2
Design an algorithm that determines, given a transformation and its definition-space predicate, whether the orbits of two elements intersect.
template <typename F, typename P>
     requires(Transformation(F) && UnaryPredicate(P) && Domain(F)==Domain(P))
  bool orbits_intersect(const Domain(F)& x0, const Domain(F)& x1, F f, P p)
  {
     // Precondition: p(x) iff f(x) is defined
     Domain(F) y0 = collision_point(x0, f, p);
     Domain(F) y1 = collision_point(x1, f, p);
     if (y0 == y1)
       return true;
     if (!p(y0) || !p(y1)) // At least one orbit terminates
       return false;
     Domain(F) z = f(y0);
     while (z != y0 && z != y1)
       z = f(z);
     return z == y1; // if z == y0 && z != y1, the cycles are disjoint
  }
Exercise 2.3
For convergent_point to work, it must be called with elements whose distances to the convergent point are equal. Implement an algorithm convergent_point_guarded for use when that is not known to be the case, but there is an element in common to the orbits of both.
template <typename F>
     requires(Transformation(F))
  pair <Domain(F),Domain(F)> convergent_point_guarded(Domain(F) x0,
     Domain(F) x1, Domain(F) g, F f)
  {
     // Precondition: there exist m,n s.t. f^m(x0) == g && f^n(x1) == g
     bool found_x0 = false;
     bool found_x1 = false;
     while (x0 != x1 && !(found_x0 && found_x1)) {
       x0 = f(x0);
       x1 = f(x1);
       if (x0 == g)
         found_x0 = true;
       if (x1 == g)
         found_x1 = true;
     }
     // Postcondition: x0 == x1 or the loop executed max(m,n) times
     return pair<Domain(F),Domain(F)>(x0, x1);
  }
Exercise 2.4
Derive formulas for the count of different operations (f, p, equality) for the algorithms in this chapter.

But Mark wrote:

Compute AVERAGE count of operations in Ch. 2 algorithms (if I’m off anywhere the errors will cascade)

  • abs: <: 1, Unary -: 0.5
  • euclidean_norm (binary): *: 2, +: 1, sqrt: 1
  • euclidean_norm (ternary): *: 3, +: 2, sqrt: 1
  • power_unary: !=: n+1, -: n, f: n
  • distance: !=: n+1, f: n, +: n
  • collision_point:
    • terminating orbit: !=: n, f: 3n-0.5, !p: 2n-0.5
    • non-terminating orbit: !=: n+1, f: 3n+1, !p: 2n+1
  • terminating:
    • terminating orbit: !=: n, f: 3n-0.5 !p: 2n-0.5, p: 1
    • non-terminating orbit: !=: n+1, f: 3n+1, !p: 2n+2
  • collision_point_nonterminating_orbit: !=: n+1, f: 3n+1
  • circular_nonterminating_orbit: !=: n+1, ==: 1, f: 3n+2
  • circular:
    • terminating orbit: !=: n, f: 3n-0.5, !p: 2n-0.5, p: 1
    • non-terminating orbit: !=: n+1, ==: 1, f: 3n+0.5, !p: 2n-0.5, p: 1

For the rest, n is the convergent distance and h and c are as in the text.

  • convergent_point: !=: n+1, f: 2n
  • connection_point_nonterminating_orbit: !=: n+h+2, f: 3n+2h+2
  • connection_point:
    • terminating orbit: !=: n, f: 3n-0.5, !p: 2n+0.5
    • non-terminating orbit: !=: n+h+2 f: 3n+2h+0.5, !p: 2n+0.5
  • orbit_structure_nonterminating_orbit: !=: n+2h+c f: 3n+3h+c+1 +: h+c-2
  • orbit_structure:
    • terminating orbit: !=: n+h f: 3n+h-0.5, !p: 2n+0.5
    • non-terminating orbit: !=: n+h+c, f: 3n+h+c-0.5, !p: 2n+1.5
Exercise 2.5
Use orbit_structure_nonterminating_orbit to determine the average handle size and cycle size of the pseudorandom number generators on your platform for various seeds.
  • Microsoft/Vista/VS2008: Handle: 109, Cycle: 115 (1000 seeds)
  • Linux/gcc 4.1.2: Handle: 32033, Cycle: 21573 (20 seeds)
  • Linear Congruential Method (TAOCP): Handle: 552,493,925 Cycle: 421,412,441 (30 seeds)
Exercise 2.6
Rewrite all the algorithms in this chapter in terms of actions.
// Exercise 2.6: Rewrite all transformation algorithms in terms of actions
   
  template <typename A, // A models Action
            typename N> // N models Integer
  typename void power_unary(typename A::domain_type& x, N n, A a)
  {
    // Precondition: n >=0 and for all 0<i<=n, a^i(x) is defined
    while (n != N(0)) {
      n = n - N(1);
      a(x);
    }
  }
   
  template <typename A> // A models Action
  typename A::distance_type distance(typename A::domain_type x, typename A::domain_type y, A a)
  {
    // Precondition: y is reachable from x under a
    typedef typename A::distance_type N;
    N n(0);
    while (x != y) {
      a(x);
      n = n + N(1);
    }
    return n;
  }
   
  template <typename A, // A models Action
            typename P> // P models UnaryPredicate with P::domain_type == A::domain_type
  typename A::domain_type collision_point(const typename A::domain_type& x, A a, P p)
  {
    // Precondition: p(x) iff a(x) is defined
    if (!p(x)) return;
    typename A::domain_type slow = x;
    typename A::domain_type fast = x;
    a(fast);
   
    while (fast != slow) {
      a(slow);
      if (!p(fast)) return fast;
      a(fast);
      if (!p(fast)) return fast;
      a(fast);
    }
    return fast;
  }
   
  template <typename A, // A models Action
            typename P> // P models UnaryPredicate with P::domain_type == A::domain_type
  bool terminating(const typename A::domain_type& x, A a, P p)
  {
    // Precondition: p(x) iff a(x) is defined
    return !p(collision_point(x, a, p));
  }
   
  template <typename A> // A models Action
  typename A::domain_type collision_point_nonterminating_orbit(const typename A::domain_type& x, A a)
  {
    typename A::domain_type slow = x;
    typename A::domain_type fast = x;
    a(fast);
   
    while (fast != slow) {
      a(slow);
      a(fast);
      a(fast);
    }
    return fast;
  }
   
  template <typename A> // A models Action
  bool circular_nonterminating_orbit(const typename A::domain_type& x, A a)
  {
    typename A::domain_type y = collision_point_nonterminating_orbit(x, a);
    a(y);
    return x == y;
  }
   
  template <typename A, // A models Action
            typename P> // P models UnaryPredicate with P::domain_type == A::domain_type
  bool circular(const typename A::domain_type& x, A a, P p)
  {
    // Precondition: p(x) iff a(x) is defined
    typename A::domain_type y = collision_point(x, a, p);
    if (!p(y))
      return false;
    a(y);
    return x == y; 
  }
   
  template <typename A> // A models Action
  typename A::domain_type convergent_point(typename A::domain_type x0, typename A::domain_type x1, A a)
  {
    while (x0 != x1) {
      a(x0);
      a(x1);
    }
    return x0;
  }
   
  template <typename A> // A models Action
  typename A::domain_type connection_point_nonterminating_orbit(const typename A::domain_type& x, A a)
  {
    typename A::domain_type y = collision_point_nonterminating_orbit(x, a);
    a(y);
    return convergent_point(x, y, a);
  }
   
  template <typename A, // A models Action
            typename P> // P models UnaryPredicate with P::domain_type == A::domain_type
  typename A::domain_type connection_point(const typename A::domain_type& x, A a, P p)
  {
    // precondition: p(x) iff a(x) is defined
    typename A::domain_type y = collision_point(x, a, p);
    if (!p(y))
      return y;
    a(y);
    return convergent_point(x, y, a);
  }
   
  template <typename A> // A models Action
  triple<typename A::distance_type, typename A::distance_type, typename A::domain_type>
  orbit_structure_nonterminating_orbit(const typename A::domain_type& x, A a)
  {
    typedef typename A::distance_type N;
    typedef typename A::domain_type D;
    D y = connection_point_nonterminating_orbit(x, a);
    D z = y;
    a(z);
    return triple<N, N, D>(distance(x, y, a), distance(z, y, a), y);
  }
   
  template <typename A, // A models Action
            typename P> // P models UnaryPredicate with P::domain_type == A::domain_type
  triple<typename A::distance_type, typename A::distance_type, typename A::domain_type>
  orbit_structure_nonterminating_orbit(const typename A::domain_type& x, A a)
  {
    // precondition: p(x) iff a(x) is defined
    typedef typename A::distance_type N;
    typedef typename A::domain_type D;
    D y = connection_point(x, a, p);
    N m = distance(x, y, f);
    N n(0);
    if (p(y)) {
      D z = y;
      a(z);
      n = distance(z, y, a);
    }
    return triple<N, N, D>(m, n, y);
  }
Posted Wednesday, March 31st, 2010 under EoP Study Group, Exercises.

8 Responses to “EOP Exercise Round-Up: Chapter 2”

  1. Phuc Nguyen says:

    I don’t understand 2.3 here. Isn’t it a little pointless? You have convergent_point(x0, x1, g, f), where g is the convergent point. In the end, what you return is (g, g), right? So what’s the point of doing this?

      Quote
    • Paul McJones says:

      Good point. Here’s the solution we provide in the code accompanying Elements of Programming (http://www.elementsofprogramming.com/code/eop.h):

      // Exercise 2.3:
      
      template
          requires(Transformation(F))
      Domain(F) convergent_point_guarded(Domain(F) x0,
                                         Domain(F) x1,
                                         Domain(F) y, F f)
      {
          // Precondition: $\func{reachable}(x0, y, f) \wedge \func{reachable}(x1, y, f)$
          typedef DistanceType(F) N;
          N d0 = distance(x0, y, f);
          N d1 = distance(x1, y, f);
          if      (d0 < d1) x1 = power_unary(x1, d1 - d0, f);
          else if (d1 < d0) x0 = power_unary(x0, d0 - d1, f);
          return convergent_point(x0, x1, f);
      }
      
        Quote
  2. Regarding exercise 2.5, and the test done with Microsoft/Vista/VS2008…

    I’ve noticed that using the random function simulator of the form: int random(int x) { srand(x); return rand(); }

    results with very short orbit sizes, as is evident from the solution posted above, so I decided to use the following functor that does away with continuous reseeding:

    struct Slucajan { Slucajan() { srand(unsigned(time(NULL)));} Slucajan(unsigned x) { srand(x);} unsigned operator ()(unsigned x) { return rand(); } };

    Compiling this on VS2008 produced an average orbit size of ~64000 for 1000 seeds.

      Quote
    • Sean Parent says:

      Continually reseeding doesn’t give you an accurate analysis of the quality of the random number generator – it simply provides a source of cycles for the exercise. To properly analyze the random number generate you need to be looking for cycles in the internal state of the generator – and that requires sources to your random number generator. The problem with your solution is that it isn’t guaranteed to give you strict cycles – you might get a sequence like “1, 3, 5, 3, 27, 3, 48″. The function is no longer regular – and so the “orbit size” you are measuring is something else entirely.

        Quote
  3. Jason McCarrell says:

    Just a general comment. Someone should really add a favicon to this website, so I can take the label off it in my bookmark bar. That is all.

      Quote
  4. Mark Ruzon says:

    Hmm. I proved all the lemmas, too. I must have missed that page when transcribing. But why am I the only person doing the exercises?

      Quote
  5. …thanks to Dave for painstakingly formatting the math without LaTeX — Sean

    And since this was such a pain, I found and installed this plugin, which enables LaTeX math mode support (see the plugin’s FAQ for information on how to use it.

    I would appreciate it if you’d try to use it when submitting answers to the homework or chapter write-ups. It will save a huge number of headaches.

    Thanks!

      Quote

Leave a Comment (post replies using links below individual comments)

Spam Protection by WP-SpamFree

Subscribe without commenting