Skip to content

Commit d4fdc7b

Browse files
author
Simon C. Krüger
committed
Adds binary recursive stein algorithm to the GCD class
* Improves the LCM by passing the used GCD algorithm into the function. * Find an easy GCD function if possible.
1 parent a31bd41 commit d4fdc7b

File tree

5 files changed

+296
-92
lines changed

5 files changed

+296
-92
lines changed

GCD/GCD.playground/Contents.swift

Lines changed: 137 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,148 @@
11
//: Playground - noun: a place where people can play
22

3-
// Recursive version
4-
func gcd(_ a: Int, _ b: Int) -> Int {
5-
let r = a % b
6-
if r != 0 {
7-
return gcd(b, r)
8-
} else {
3+
/*
4+
Iterative approach based on the Euclidean algorithm.
5+
The Euclidean algorithm is based on the principle that the greatest
6+
common divisor of two numbers does not change if the larger number
7+
is replaced by its difference with the smaller number.
8+
- Parameter m: First natural number
9+
- Parameter n: Second natural number
10+
- Returns: The natural gcd if m and n
11+
*/
12+
func gcdIterativeEuklid(_ m: Int, _ n: Int) -> Int {
13+
var a: Int = 0
14+
var b: Int = max(m, n)
15+
var r: Int = min(m, n)
16+
17+
while r != 0 {
18+
a = b
19+
b = r
20+
r = a % b
21+
}
922
return b
10-
}
1123
}
1224

1325
/*
14-
// Iterative version
15-
func gcd(m: Int, _ n: Int) -> Int {
16-
var a = 0
17-
var b = max(m, n)
18-
var r = min(m, n)
19-
20-
while r != 0 {
21-
a = b
22-
b = r
23-
r = a % b
24-
}
25-
return b
26+
Recursive approach based on the Euclidean algorithm.
27+
28+
- Parameter m: First natural number
29+
- Parameter n: Second natural number
30+
- Returns: The natural gcd of m and n
31+
- Note: The recursive version makes only tail recursive calls.
32+
Most compilers for imperative languages do not optimize these.
33+
The swift compiler as well as the obj-c compiler is able to do
34+
optimizations for tail recursive calls, even though it still ends
35+
up to be the same in terms of complexity. That said, tail call
36+
elimination is not mutually exclusive to recursion.
37+
*/
38+
func gcdRecursiveEuklid(_ m: Int, _ n: Int) -> Int {
39+
let r: Int = m % n
40+
if r != 0 {
41+
return gcdRecursiveEuklid(n, r)
42+
} else {
43+
return n
44+
}
45+
}
46+
47+
/*
48+
The binary GCD algorithm, also known as Stein's algorithm,
49+
is an algorithm that computes the greatest common divisor of two
50+
nonnegative integers. Stein's algorithm uses simpler arithmetic
51+
operations than the conventional Euclidean algorithm; it replaces
52+
division with arithmetic shifts, comparisons, and subtraction.
53+
54+
- Parameter m: First natural number
55+
- Parameter n: Second natural number
56+
- Returns: The natural gcd of m and n
57+
- Complexity: worst case O(n^2), where n is the number of bits
58+
in the larger of the two numbers. Although each step reduces
59+
at least one of the operands by at least a factor of 2,
60+
the subtract and shift operations take linear time for very
61+
large integers
62+
*/
63+
func gcdBinaryRecursiveStein(_ m: Int, _ n: Int) -> Int {
64+
if let easySolution = findEasySolution(m, n) { return easySolution }
65+
66+
if (m & 1) == 0 {
67+
// m is even
68+
if (n & 1) == 1 {
69+
// and n is odd
70+
return gcdBinaryRecursiveStein(m >> 1, n)
71+
} else {
72+
// both m and n are even
73+
return gcdBinaryRecursiveStein(m >> 1, n >> 1) << 1
74+
}
75+
} else if (n & 1) == 0 {
76+
// m is odd, n is even
77+
return gcdBinaryRecursiveStein(m, n >> 1)
78+
} else if (m > n) {
79+
// reduce larger argument
80+
return gcdBinaryRecursiveStein((m - n) >> 1, n)
81+
} else {
82+
// reduce larger argument
83+
return gcdBinaryRecursiveStein((n - m) >> 1, m)
84+
}
85+
}
86+
87+
/*
88+
Finds an easy solution for the gcd.
89+
- Note: It might be relevant for different usecases to
90+
try finding an easy solution for the GCD calculation
91+
before even starting more difficult operations.
92+
*/
93+
func findEasySolution(_ m: Int, _ n: Int) -> Int? {
94+
if m == n {
95+
return m
96+
}
97+
if m == 0 {
98+
return n
99+
}
100+
if n == 0 {
101+
return m
102+
}
103+
return nil
26104
}
27-
*/
28105

29-
func lcm(_ m: Int, _ n: Int) -> Int {
30-
return m / gcd(m, n) * n // we divide before multiplying to avoid integer overflow
106+
107+
enum LCMError: Error {
108+
case divisionByZero
109+
case lcmEmptyList
110+
}
111+
112+
/*
113+
Calculates the lcm for two given numbers using a specified gcd algorithm.
114+
115+
- Parameter m: First natural number.
116+
- Parameter n: Second natural number.
117+
- Parameter using: The used gcd algorithm to calculate the lcm.
118+
- Throws: Can throw a `divisionByZero` error if one of the given
119+
attributes turns out to be zero or less.
120+
- Returns: The least common multiplier of the two attributes as
121+
an unsigned integer
122+
*/
123+
func lcm(_ m: Int, _ n: Int, using gcdAlgorithm: (Int, Int) -> (Int)) throws -> Int {
124+
guard (m & n) != 0 else { throw LCMError.divisionByZero }
125+
return m / gcdAlgorithm(m, n) * n
31126
}
32127

33-
gcd(52, 39) // 13
34-
gcd(228, 36) // 12
35-
gcd(51357, 3819) // 57
36-
gcd(841, 299) // 1
128+
gcdIterativeEuklid(52, 39) // 13
129+
gcdIterativeEuklid(228, 36) // 12
130+
gcdIterativeEuklid(51357, 3819) // 57
131+
gcdIterativeEuklid(841, 299) // 1
37132

38-
lcm(2, 3) // 6
39-
lcm(10, 8) // 40
133+
gcdRecursiveEuklid(52, 39) // 13
134+
gcdRecursiveEuklid(228, 36) // 12
135+
gcdRecursiveEuklid(51357, 3819) // 57
136+
gcdRecursiveEuklid(841, 299) // 1
137+
138+
gcdBinaryRecursiveStein(52, 39) // 13
139+
gcdBinaryRecursiveStein(228, 36) // 12
140+
gcdBinaryRecursiveStein(51357, 3819) // 57
141+
gcdBinaryRecursiveStein(841, 299) // 1
142+
143+
do {
144+
try lcm(2, 3, using: gcdIterativeEuklid) // 6
145+
try lcm(10, 8, using: gcdIterativeEuklid) // 40
146+
} catch {
147+
dump(error)
148+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>IDEDidComputeMac32BitWarning</key>
6+
<true/>
7+
</dict>
8+
</plist>

GCD/GCD.playground/timeline.xctimeline

Lines changed: 0 additions & 6 deletions
This file was deleted.

GCD/GCD.swift

Lines changed: 116 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,125 @@
1+
12
/*
2-
Euclid's algorithm for finding the greatest common divisor
3-
*/
4-
func gcd(_ m: Int, _ n: Int) -> Int {
5-
var a = 0
6-
var b = max(m, n)
7-
var r = min(m, n)
3+
Iterative approach based on the Euclidean algorithm.
4+
The Euclidean algorithm is based on the principle that the greatest
5+
common divisor of two numbers does not change if the larger number
6+
is replaced by its difference with the smaller number.
7+
- Parameter m: First natural number
8+
- Parameter n: Second natural number
9+
- Returns: The natural gcd if m and n
10+
*/
11+
func gcdIterativeEuklid(_ m: Int, _ n: Int) -> Int {
12+
var a: Int = 0
13+
var b: Int = max(m, n)
14+
var r: Int = min(m, n)
15+
16+
while r != 0 {
17+
a = b
18+
b = r
19+
r = a % b
20+
}
21+
return b
22+
}
823

9-
while r != 0 {
10-
a = b
11-
b = r
12-
r = a % b
13-
}
14-
return b
24+
/*
25+
Recursive approach based on the Euclidean algorithm.
26+
27+
- Parameter m: First natural number
28+
- Parameter n: Second natural number
29+
- Returns: The natural gcd of m and n
30+
- Note: The recursive version makes only tail recursive calls.
31+
Most compilers for imperative languages do not optimize these.
32+
The swift compiler as well as the obj-c compiler is able to do
33+
optimizations for tail recursive calls, even though it still ends
34+
up to be the same in terms of complexity. That said, tail call
35+
elimination is not mutually exclusive to recursion.
36+
*/
37+
func gcdRecursiveEuklid(_ m: Int, _ n: Int) -> Int {
38+
let r: Int = m % n
39+
if r != 0 {
40+
return gcdRecursiveEuklid(n, r)
41+
} else {
42+
return n
43+
}
1544
}
1645

1746
/*
18-
// Recursive version
19-
func gcd(_ a: Int, _ b: Int) -> Int {
20-
let r = a % b
21-
if r != 0 {
22-
return gcd(b, r)
23-
} else {
24-
return b
25-
}
47+
The binary GCD algorithm, also known as Stein's algorithm,
48+
is an algorithm that computes the greatest common divisor of two
49+
nonnegative integers. Stein's algorithm uses simpler arithmetic
50+
operations than the conventional Euclidean algorithm; it replaces
51+
division with arithmetic shifts, comparisons, and subtraction.
52+
53+
- Parameter m: First natural number
54+
- Parameter n: Second natural number
55+
- Returns: The natural gcd of m and n
56+
- Complexity: worst case O(n^2), where n is the number of bits
57+
in the larger of the two numbers. Although each step reduces
58+
at least one of the operands by at least a factor of 2,
59+
the subtract and shift operations take linear time for very
60+
large integers
61+
*/
62+
func gcdBinaryRecursiveStein(_ m: Int, _ n: Int) -> Int {
63+
if let easySolution = findEasySolution(m, n) { return easySolution }
64+
65+
if (m & 1) == 0 {
66+
// m is even
67+
if (n & 1) == 1 {
68+
// and n is odd
69+
return gcdBinaryRecursiveStein(m >> 1, n)
70+
} else {
71+
// both m and n are even
72+
return gcdBinaryRecursiveStein(m >> 1, n >> 1) << 1
73+
}
74+
} else if (n & 1) == 0 {
75+
// m is odd, n is even
76+
return gcdBinaryRecursiveStein(m, n >> 1)
77+
} else if (m > n) {
78+
// reduce larger argument
79+
return gcdBinaryRecursiveStein((m - n) >> 1, n)
80+
} else {
81+
// reduce larger argument
82+
return gcdBinaryRecursiveStein((n - m) >> 1, m)
83+
}
84+
}
85+
86+
/*
87+
Finds an easy solution for the gcd.
88+
- Note: It might be relevant for different usecases to
89+
try finding an easy solution for the GCD calculation
90+
before even starting more difficult operations.
91+
*/
92+
func findEasySolution(_ m: Int, _ n: Int) -> Int? {
93+
if m == n {
94+
return m
95+
}
96+
if m == 0 {
97+
return n
98+
}
99+
if n == 0 {
100+
return m
101+
}
102+
return nil
103+
}
104+
105+
106+
enum LCMError: Error {
107+
case divisionByZero
108+
case lcmEmptyList
26109
}
27-
*/
28110

29111
/*
30-
Returns the least common multiple of two numbers.
31-
*/
32-
func lcm(_ m: Int, _ n: Int) -> Int {
33-
return m / gcd(m, n) * n
112+
Calculates the lcm for two given numbers using a specified gcd algorithm.
113+
114+
- Parameter m: First natural number.
115+
- Parameter n: Second natural number.
116+
- Parameter using: The used gcd algorithm to calculate the lcm.
117+
- Throws: Can throw a `divisionByZero` error if one of the given
118+
attributes turns out to be zero or less.
119+
- Returns: The least common multiplier of the two attributes as
120+
an unsigned integer
121+
*/
122+
func lcm(_ m: Int, _ n: Int, using gcdAlgorithm: (Int, Int) -> (Int)) throws -> Int {
123+
guard (m & n) != 0 else { throw LCMError.divisionByZero }
124+
return m / gcdAlgorithm(m, n) * n
34125
}

0 commit comments

Comments
 (0)