Wednesday, August 5, 2009

A Scala version of Unle Bob's lazy PI sequence in Clojure

I follow Uncle Bob on twitter and I'm also currently learning Scala. When I saw his tweet about a lazily evaluated infinite PI sequence in Clojure I couldn't resist giving it a shot in Scala. Here's my result (it works!). Feedback is absolutely welcome.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//Gives us the :: syntax for streams.  From http://www.codecommit.com/blog/scala/infinite-lists-for-the-finitely-patient.
class RichStream[A](str: =>Stream[A]) {
def ::(hd: A) = Stream.cons(hd, str)
}
implicit def streamToRichStream[A](str: =>Stream[A]) = new RichStream(str)

object M { //Gives us a shorthand for declaring BigDecimals that Scala lacks
def apply(i:Int) = BigDecimal(i)
}

//Hack to work around poor bigdecimal support
val mc = new java.math.MathContext(300,java.math.RoundingMode.HALF_UP)
def divide(a:BigDecimal, b:BigDecimal) = {
new BigDecimal(a.bigDecimal.divide(b.bigDecimal, mc))
}

def piSummands(n: BigDecimal): Stream[BigDecimal] = {
divide(M(1), n) :: piSummands(n + M(2)).map(_ * -1)
}

def scaleStream(stream:Stream[BigDecimal], factor:Int) = {
stream map {_ * factor}
}

def addStreams(s1:Stream[BigDecimal], s2:Stream[BigDecimal]) = {
s1 zip(s2) map {z => z._1 + z._2}
}

def partialSums(s:Stream[BigDecimal]):Stream[BigDecimal] = {
s.head :: addStreams(s.tail, partialSums(s))
}

def piStream = scaleStream(partialSums(piSummands(1)), 4)

def square(s:BigDecimal) = s * s

def eulerTransform(s:Stream[BigDecimal]):Stream[BigDecimal] = {
val s0 = s(0)
val s1 = s(1)
val s2 = s(2)
val head = s2 - divide((square(s2 - s1)), ((s0 + (M(-2) * s1) + s2)))
head :: eulerTransform(s.tail)
}

type Transform = (Stream[BigDecimal]) => Stream[BigDecimal]

def makeTableau(transform: Transform, s:Stream[BigDecimal]):Stream[Stream[BigDecimal]] = {
s :: makeTableau(transform, transform(s))
}

def acceleratedSequence(transform:Transform, s:Stream[BigDecimal]) = {
makeTableau(transform, s) map {_.head}
}

val set = acceleratedSequence(eulerTransform, piStream)
println(set.take(150).last)

No comments: