egyptian_fraction.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. from sympy.core.containers import Tuple
  2. from sympy.core.numbers import (Integer, Rational)
  3. from sympy.core.singleton import S
  4. import sympy.polys
  5. from math import gcd
  6. def egyptian_fraction(r, algorithm="Greedy"):
  7. """
  8. Return the list of denominators of an Egyptian fraction
  9. expansion [1]_ of the said rational `r`.
  10. Parameters
  11. ==========
  12. r : Rational or (p, q)
  13. a positive rational number, ``p/q``.
  14. algorithm : { "Greedy", "Graham Jewett", "Takenouchi", "Golomb" }, optional
  15. Denotes the algorithm to be used (the default is "Greedy").
  16. Examples
  17. ========
  18. >>> from sympy import Rational
  19. >>> from sympy.ntheory.egyptian_fraction import egyptian_fraction
  20. >>> egyptian_fraction(Rational(3, 7))
  21. [3, 11, 231]
  22. >>> egyptian_fraction((3, 7), "Graham Jewett")
  23. [7, 8, 9, 56, 57, 72, 3192]
  24. >>> egyptian_fraction((3, 7), "Takenouchi")
  25. [4, 7, 28]
  26. >>> egyptian_fraction((3, 7), "Golomb")
  27. [3, 15, 35]
  28. >>> egyptian_fraction((11, 5), "Golomb")
  29. [1, 2, 3, 4, 9, 234, 1118, 2580]
  30. See Also
  31. ========
  32. sympy.core.numbers.Rational
  33. Notes
  34. =====
  35. Currently the following algorithms are supported:
  36. 1) Greedy Algorithm
  37. Also called the Fibonacci-Sylvester algorithm [2]_.
  38. At each step, extract the largest unit fraction less
  39. than the target and replace the target with the remainder.
  40. It has some distinct properties:
  41. a) Given `p/q` in lowest terms, generates an expansion of maximum
  42. length `p`. Even as the numerators get large, the number of
  43. terms is seldom more than a handful.
  44. b) Uses minimal memory.
  45. c) The terms can blow up (standard examples of this are 5/121 and
  46. 31/311). The denominator is at most squared at each step
  47. (doubly-exponential growth) and typically exhibits
  48. singly-exponential growth.
  49. 2) Graham Jewett Algorithm
  50. The algorithm suggested by the result of Graham and Jewett.
  51. Note that this has a tendency to blow up: the length of the
  52. resulting expansion is always ``2**(x/gcd(x, y)) - 1``. See [3]_.
  53. 3) Takenouchi Algorithm
  54. The algorithm suggested by Takenouchi (1921).
  55. Differs from the Graham-Jewett algorithm only in the handling
  56. of duplicates. See [3]_.
  57. 4) Golomb's Algorithm
  58. A method given by Golumb (1962), using modular arithmetic and
  59. inverses. It yields the same results as a method using continued
  60. fractions proposed by Bleicher (1972). See [4]_.
  61. If the given rational is greater than or equal to 1, a greedy algorithm
  62. of summing the harmonic sequence 1/1 + 1/2 + 1/3 + ... is used, taking
  63. all the unit fractions of this sequence until adding one more would be
  64. greater than the given number. This list of denominators is prefixed
  65. to the result from the requested algorithm used on the remainder. For
  66. example, if r is 8/3, using the Greedy algorithm, we get [1, 2, 3, 4,
  67. 5, 6, 7, 14, 420], where the beginning of the sequence, [1, 2, 3, 4, 5,
  68. 6, 7] is part of the harmonic sequence summing to 363/140, leaving a
  69. remainder of 31/420, which yields [14, 420] by the Greedy algorithm.
  70. The result of egyptian_fraction(Rational(8, 3), "Golomb") is [1, 2, 3,
  71. 4, 5, 6, 7, 14, 574, 2788, 6460, 11590, 33062, 113820], and so on.
  72. References
  73. ==========
  74. .. [1] https://en.wikipedia.org/wiki/Egyptian_fraction
  75. .. [2] https://en.wikipedia.org/wiki/Greedy_algorithm_for_Egyptian_fractions
  76. .. [3] https://www.ics.uci.edu/~eppstein/numth/egypt/conflict.html
  77. .. [4] https://web.archive.org/web/20180413004012/https://ami.ektf.hu/uploads/papers/finalpdf/AMI_42_from129to134.pdf
  78. """
  79. if not isinstance(r, Rational):
  80. if isinstance(r, (Tuple, tuple)) and len(r) == 2:
  81. r = Rational(*r)
  82. else:
  83. raise ValueError("Value must be a Rational or tuple of ints")
  84. if r <= 0:
  85. raise ValueError("Value must be positive")
  86. # common cases that all methods agree on
  87. x, y = r.as_numer_denom()
  88. if y == 1 and x == 2:
  89. return [Integer(i) for i in [1, 2, 3, 6]]
  90. if x == y + 1:
  91. return [S.One, y]
  92. prefix, rem = egypt_harmonic(r)
  93. if rem == 0:
  94. return prefix
  95. # work in Python ints
  96. x, y = rem.p, rem.q
  97. # assert x < y and gcd(x, y) = 1
  98. if algorithm == "Greedy":
  99. postfix = egypt_greedy(x, y)
  100. elif algorithm == "Graham Jewett":
  101. postfix = egypt_graham_jewett(x, y)
  102. elif algorithm == "Takenouchi":
  103. postfix = egypt_takenouchi(x, y)
  104. elif algorithm == "Golomb":
  105. postfix = egypt_golomb(x, y)
  106. else:
  107. raise ValueError("Entered invalid algorithm")
  108. return prefix + [Integer(i) for i in postfix]
  109. def egypt_greedy(x, y):
  110. # assumes gcd(x, y) == 1
  111. if x == 1:
  112. return [y]
  113. else:
  114. a = (-y) % x
  115. b = y*(y//x + 1)
  116. c = gcd(a, b)
  117. if c > 1:
  118. num, denom = a//c, b//c
  119. else:
  120. num, denom = a, b
  121. return [y//x + 1] + egypt_greedy(num, denom)
  122. def egypt_graham_jewett(x, y):
  123. # assumes gcd(x, y) == 1
  124. l = [y] * x
  125. # l is now a list of integers whose reciprocals sum to x/y.
  126. # we shall now proceed to manipulate the elements of l without
  127. # changing the reciprocated sum until all elements are unique.
  128. while len(l) != len(set(l)):
  129. l.sort() # so the list has duplicates. find a smallest pair
  130. for i in range(len(l) - 1):
  131. if l[i] == l[i + 1]:
  132. break
  133. # we have now identified a pair of identical
  134. # elements: l[i] and l[i + 1].
  135. # now comes the application of the result of graham and jewett:
  136. l[i + 1] = l[i] + 1
  137. # and we just iterate that until the list has no duplicates.
  138. l.append(l[i]*(l[i] + 1))
  139. return sorted(l)
  140. def egypt_takenouchi(x, y):
  141. # assumes gcd(x, y) == 1
  142. # special cases for 3/y
  143. if x == 3:
  144. if y % 2 == 0:
  145. return [y//2, y]
  146. i = (y - 1)//2
  147. j = i + 1
  148. k = j + i
  149. return [j, k, j*k]
  150. l = [y] * x
  151. while len(l) != len(set(l)):
  152. l.sort()
  153. for i in range(len(l) - 1):
  154. if l[i] == l[i + 1]:
  155. break
  156. k = l[i]
  157. if k % 2 == 0:
  158. l[i] = l[i] // 2
  159. del l[i + 1]
  160. else:
  161. l[i], l[i + 1] = (k + 1)//2, k*(k + 1)//2
  162. return sorted(l)
  163. def egypt_golomb(x, y):
  164. # assumes x < y and gcd(x, y) == 1
  165. if x == 1:
  166. return [y]
  167. xp = sympy.polys.ZZ.invert(int(x), int(y))
  168. rv = [xp*y]
  169. rv.extend(egypt_golomb((x*xp - 1)//y, xp))
  170. return sorted(rv)
  171. def egypt_harmonic(r):
  172. # assumes r is Rational
  173. rv = []
  174. d = S.One
  175. acc = S.Zero
  176. while acc + 1/d <= r:
  177. acc += 1/d
  178. rv.append(d)
  179. d += 1
  180. return (rv, r - acc)