Every Possible Fraction

December 9, 2014

When I saw this web page a few days ago, I knew it would make a great exercise, simple but fascinating. Starting with x = 1, every fraction in lowest terms is generated by x ′ = 1 / (ny + 1), where n = ⌊ x ⌋ and y = x − n. The underlying math is known as the Stern-Brocot tree, and has been known since the ancient Greeks, who used terms of the Stern-Brocot tree to design the gear ratios in the Antikythera mechanism.

Your task is to write a program that generates the fractions in lowest terms. When you are finished, you are welcome to read or run a suggested solution, or to post your own solution or discuss the exercise in the comments below.

Pages: 1 2

13 Responses to “Every Possible Fraction”

  1. Francesco said

    Haskell:

    f (a:b:c) = a : (fst a + fst b, snd a + snd b) : f (b:c)
    f r       = r
    
    main = print $ until ((> 9) . length) f [(0,1), (1,0)]
    
  2. Paul said

    In Python.

    from fractions import Fraction
    from itertools import islice
    
    def rats():
        x = Fraction(1)
        while 1:
            yield x
            n = int(x)
            y = x - n
            x = 1 / (n - y + 1)
    
    r = rats()
    for lines in xrange(10):
        for f in islice(r, 10):
            print "{:8s}".format(f),
        print
    
  3. Brett Warren said

    Python 3.4, generator implementation

    def stern_brocot_gen():
    	from math import floor
    	from fractions import Fraction
    	x = 1
    	while True:
    		n = floor(x)
    		y = x - n
    		x = Fraction(1/(n - y + 1))
    		yield x
    		
    if __name__ == "__main__":
    	gen = stern_brocot_gen()
    	for n in range(10):
    		print(gen.__next__())
    
  4. programmingpraxis said

    I used a generator because I have an implementation of generators in the Standard Prelude and because it’s good to remind my readers that generators can be useful, but generators aren’t actually needed for this exercise, because only a single value is carried forward from one iteration to the next and because each iterative step is simple to compute. Here is an alternative solution:

    (define rat
      (let ((x 0))
        (lambda args
          (when (pair? args)
            (set! x (car args)))
          (let* ((n (floor x))
                 (y (- x n)))
            (set! x (/ (- n y -1))) x))))

    Calling (rat) repeatedly returns the same sequence of fractions as the original. Additionally, calling (rat r), where r is some rational number, resets the sequence to start from r, so for instance (rat 11/32) returns 32/21, and (rat 0) restarts the sequence from the beginning.

  5. Graham said

    For more Haskell fun, check out this functional pearl.

    [ EDIT: I linked that paper in the last paragraph of the solution page. As Graham says, it is well worth reading. Phil ]

  6. Graham said

    Oops! Sorry about that.

  7. matthew said

    Aha! Thanks for the link, I had just got the point of realizing that the sequence given didn’t actually generate the Stern-Brocot tree and that explains it.

    Here’s a Python generator for the sequence that does the rational arithmetic itself – and we don’t need to do an explicit reduction to lowest terms.

    def rats():
        p,q = 1,1
        while True:
            yield p,q
            p,q = q, p-2*(p%q)+q
    
  8. Lucas A. Brown said

    I’m rather partial to the Calkin-Wilf tree. It contains also contains every positive rational number in lowest terms exactly once; it just has them in a different order.

    def calkinwilf(x):
        if x == 0: return (0,1)
        n, y, l = ilog(x, 2), 0, []
        while x > 0:
            x, y = divmod(x, 2)
            l.append(y)
        i, j = 1, 1
        for t in xrange(n, 0, -1):
            if l[t-1] == 0: j += i
            if l[t-1] == 1: i += j
        return (i,j)
    
    def wilfcalkin(i, j):
        # Inverse function of calkinwilf()
        if i == 0: return 0
        x, t = 0, -1
        while i * j != 1:
            t += 1
            if i > j:
                i -= j
                x += 2**t
            else: j -= i
        return x + 2**(t+1)
    
    from itertools import count
    for n in count(): print n, calkinwilf(n)
    
  9. matthew said

    @Lucas – the rational enumeration we are after _is_ the Calkin-Wilf tree order.

    This paper looks interesting & suggests we can do a similar direct enumeration of the elements in the Stern-Brocot tree as well:

    http://www.cs.nott.ac.uk/~rcb/MPC/RecountingRationalsTwice.pdf

  10. matthew said

    Here’s the algorithm from the Backhouse and Ferreira paper – it’s quite nifty: take Euclid’s GCD algorithm, for coprime p and q, this does (p,q) => (p-q,q) or (p,q) => (p,q-p) until p = q = 1; reverse the process ie. (p,q) maps to (p+q, q) or (p,p+q), these two operations generate all co-prime pairs (p,q); but this is just using the two matrices L = [1 1 0 1] and R = [1 0 1 1] to generate all invertible 2×2 integral matrices, so we can form a tree of all strings of { L, R } and map to the rationals by applying each matrix or its transpose to (1,1): whether we use the matrix or its transpose determines if we get the Stern-Brocot tree or the Calkin-Wilf tree. They also give a method for iteratively generating these matrices, which can then be used to generate the tree elements in order – for the Calkin-Wilf tree, this simplifies to the method described above:

    def mats():
        a,b,c,d = 1,0,0,1
        while True:
            yield a,b,c,d
            if a == 1 and b == 0 and d == 1:
                a,b,c,d = 1,c+1,0,1
            else:
                j = (c+d-1)//(a+b)
                k = 2*j+1
                a,b,c,d = k*a-c,k*b-d,a,b
    
    m = mats()
    for i in range(15):
        a,b,c,d = m.next()
        print "[%d %d %d %d] %d/%d %d/%d"%(a,b,c,d,c+d,a+b,a+c,b+d)
    

    and the output:

    [1 0 0 1] 1/1 1/1
    [1 1 0 1] 1/2 1/2
    [1 0 1 1] 2/1 2/1
    [1 2 0 1] 1/3 1/3
    [1 1 1 2] 3/2 2/3
    [2 1 1 1] 2/3 3/2
    [1 0 2 1] 3/1 3/1
    [1 3 0 1] 1/4 1/4
    [1 2 1 3] 4/3 2/5
    [2 3 1 2] 3/5 3/5
    [1 1 2 3] 5/2 3/4
    [3 2 1 1] 2/5 4/3
    [2 1 3 2] 5/3 5/3
    [3 1 2 1] 3/4 5/2
    [1 0 3 1] 4/1 4/1
    

    There is an extended version of the paper I mentioned above at:
    http://eprints.nottingham.ac.uk/1856/1/scp-rationals.pdf with some interesting historical details amongst other stuff.

    Roland Backhouse was one of my lecturers years ago, nice guy.

  11. Andras said

    Scala

    package programmingpraxis
    
    object Fraction {
      class F(val num: Int, val den:Int) {
    	  def n(): F = {
    	  	new F((num / den)*den, den)
    	  }
    	  
    	  def y(): F= {
    	  	new F(num - n().num, den)
    	  }
    	  
    	  def next():F = {
    	  	new F(den, n().num - y().num +den)
    	  }
    	  
    	  def stream(): Stream[F]={
    	  	this #:: next().stream
    	  }
    	  
    	  override def toString() = num+"/"+den
      }
                                                      
      new F(1,1).stream().take(20).toList             //> res0: List[programmingpraxis.Fraction.F] = List(1/1, 1/2, 2/1, 1/3, 3/2, 2/3
                                                      //| , 3/1, 1/4, 4/3, 3/5, 5/2, 2/5, 5/3, 3/4, 4/1, 1/5, 5/4, 4/7, 7/3, 3/8)
    }
    
  12. Andras said

    a bit shorter Scala

    package programmingpraxis
    
    object Fraction {
      def stream(in:Tuple2[Int, Int]): Stream[Tuple2[Int, Int]]={
      	in #:: stream((in._2, (2 * (in._1 / in._2) + 1 ) * in._2 - in._1))
      }                                               //> stream: (in: (Int, Int))Stream[(Int, Int)]
    
      stream((1,1)).take(20).toList                   //> res0: List[(Int, Int)] = List((1,1), (1,2), (2,1), (1,3), (3,2), (2,3), (3,1
                                                      //| ), (1,4), (4,3), (3,5), (5,2), (2,5), (5,3), (3,4), (4,1), (1,5), (5,4), (4,
                                                      //| 7), (7,3), (3,8))
    }
    
  13. Jamie Hope said

    Using unfold from SRFI-1:

    (import (only (srfi :1) unfold))
    
    (define (next-fraction f)
      (let* ((n (floor f))
             (y (- f n)))
        (/ (- n y -1))))
    
    (define (fractions numterms)
      (unfold (lambda (x) (> (car x) numterms))
              cdr
              (lambda (x) (cons (+ 1 (car x))
                                (next-fraction (cdr x))))
              (cons 1 1)))
    
    (define (display-as-grid list numcols)
      (let loop ((i 1) (list list))
        (if (null? list)
            (values)
            (begin (display (car list))
                   (if (zero? (modulo i numcols))
                       (newline)
                       (display #\tab))
                   (loop (+ i 1) (cdr list))))))
    
    (display-as-grid (fractions 120) 10)
    

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: