BIDMach is based on the Scala language, a very powerful language with a flexible syntax and produces code that runs on the Java virtual machine (JVM). Some quick features of Scala:
For this tutorial, we'll work through some basic language constructs in Scala. A lot will be left out, but our focus is on the kind of programming you might do for data analysis.
First, lets try some functional code in Scala.
In [ ]:
def fib(n:Int):Int = if (n <= 2) 1 else fib(n-1) + fib(n-2)
As you can see the syntax is similar to many languages. One significant difference is that variable types are written as qualifiers after the variable name, e.g. fib(n:Int):Int
.
Instead of declaring variables with their type, you preface each declaration with val
or var
. A val
is a value (not a variable) which is constant in its current scope. A var
is a variable that can be changed.
Try running this function and assigned the return value to v:
In [ ]:
val v = fib(40)
Assigning a new value to this symbol will result in an error:
In [ ]:
v = fib(20)
Variable declarations allow such changes:
In [ ]:
var u = fib(20)
u = fib(30)
Notice that we didnt specify a type for u or v. But in fact the compiler can figure out the type of both. The %type magic function in IScala lets us check
In [ ]:
%type v
In [ ]:
%type u
Since the tree of fib calls is a binary tree with leaves with value 1, the total number of calls is exactly 2 * fib(n) - 1, which allows us to evaluate the speed of the function: tic starts a seconds timer, while toc returns its value. (The BIDMat.MatFunctions class contains the timers)
In [ ]:
import BIDMat.MatFunctions._
tic
val n=fib(44)
val t=toc
println("time = %f secs, speed = %f million function calls per second" format (t, (2*n-1)/t/1e6))
TODO: Try writing and running a factorial function on your own:
In [ ]:
def fact(n:Int):Int
We used if/else
syntax in an intuitive way in the fib()
function. What was perhaps not completely clear was that this is a functional form of if statement. That it, it returns a value which is the result of either the first (then) statement or the second (else). If the return value is needed, it is illegal to skip the else clause.
Very often, the code in a then
or else
clause will be more than one statement, in which case we need to enclose them in Braces {}. The value returned by a set of Braces is the value of the last line in the enclosed statements. e.g.
In [ ]:
def test(a:Int):Int = {
if (a < 0) {
val b = a*a;
2 * a;
} else {
4 * a;
}
}
val x = (test(-4), test(3))
Notice that we made two function calls enclosed in a kind of list enclosed in parentheses. That's because cell evaluation, just like any code block in Scala, returns only the last statement value, but we wanted to see two values. If we check the type of this object we see:
In [ ]:
%type x
This is a tuple of two Int types. Its is more primitive than a list, and includes special compiler support for type checking. And e.g. it allows functions to return multiple values of different types while still checking them, which is very powerful.
Write a function sincos(x) that returns both the sin and cos of its argument. The sin and cos functions are in the math package.
In [ ]:
def sincos(x:Double):(Double, Double)
Scala includes a functional for loop, which you can use for C-style iteration:
In [ ]:
def fact(n:Int):Int = {
var p = 1;
for (i <- 2 to n) {
p *= i;
}
p
}
fact(5)
While loops have a familiar syntax:
In [ ]:
def fact2(n:Int):Int = {
var p = 1;
var i = n;
while (i > 0) {
p *= i
i -= 1
}
p
}
fact2(5)
Although for and while look very similar they are very different under the hood. While is implemented by the compiler with simple iteration and conditional jumps and is very fast. For creates a lexical context for each iteration, will be much slower if variables outside the context are used. For performance code, you want to use while loops.
In [ ]:
import BIDMat.MatFunctions._
val n = 100000000
var i = 0;
tic;
while (i < n) {
i += 1
}
val t = toc
println("t = %f secs, speed = %f million loops/sec" format (t, n/t/1e6))
In [ ]:
val n2 = 1000000;
var m = 0;
tic;
for (i <- 0 until n2) {
m += 1
}
val t2 = toc
println("t = %f secs, speed = %f million loops/sec" format (t2, n2/t2/1e6))
You can either define lists up front to use them, or construct them on the fly.
In [ ]:
val ll = List(1,2,3)
In [ ]:
%type ll
In [ ]:
val ll2 = 4 :: 5 :: 6 :: Nil // :: is a "cons" operator
In [ ]:
0 :: ll // You can also use :: to append to the front of a existing list in constant time
In [ ]:
ll :+ 4 // You can use :+ to append to the tail of a list, but this is much slower O(n)
Lists are immutable in Scala, so you cannot actually modify them. You can access an element with () but not modify it:
In [ ]:
ll(2)
In [ ]:
ll(2) = 4
In [ ]:
val ll3 = ll ++ ll2 // ++ is the operator to concatenate two lists
Lists, like other collections, support a variety of map and reduce functions. e.g. try
In [ ]:
def square(v:Int):Int = v*v
ll3.map(square)
Small, local utility functions are used a lot in Scala, and it has a simple syntax for creating anonymous functions. To define the square function on the fly, we could do:
In [ ]:
ll3.map((x:Int) => x * x)
And for a reducer, we just need to supply a function with two arguments:
In [ ]:
ll3.reduce((x:Int, y:Int) => x + y)
The vars x, y are repeated in the same order in the function parameter list and in its body, and that pattern occurs a lot in short utility functions. Scala includes special syntax for it. The underscore character "_" represents both the function parameter, and the value, in the order it appears. In other words
_op_ is equivalent to (x:Type, y:Type) => x op y
So we can write the sum reducer as:
In [ ]:
ll3.reduce(_+_) // the first "_" holds the first function parameter, while the second "_" holds the second.
The fold functions foldLeft and foldRight are like reduce, except they include an explicit start value. To copy a list we can do:
In [ ]:
ll3.foldRight(List[Int]())(_::_)
TODO: Write a similar expression below using foldRight that reverses the list. Hint: you can't use
_
arguments because you need to switch the order that the arguments appear when you concatenate them.
In [ ]:
There are special operators "to" and "until" which construct ranges of consecutive integers for use in functional "loops". "to" gives a sequence up to an including the last value, "until" discards the last value (which is what you want for C-style for loops).
In [ ]:
1 to 5
In [ ]:
(1 to 5).map(square)
In [ ]:
(1 to 5).reduce(_+_)
which gives us a functional way to compute factorial.
TODO: Write a functional factorial (with neither loops nor recursion) in the cell below:
In [ ]:
def factf(n:Int):Int
Arrays are the basic building block for numeric computation. In Scala they are mutable data structure of fixed size with constaint time indexed access.
In [ ]:
val arr = Array(1,2,3,4)
Arrays dont seem to print correctly in IScala. To see an arrays contents, you can convert it to a List or a Vector
In [ ]:
arr.toVector
You access elements of an array using (indx) instead of [indx] as in other languages.
In [ ]:
arr(2)
Since arrays are mutable, you can change their values:
In [ ]:
arr(2) = 7
In [ ]:
arr.toVector
Methods like map, reduce, fold are all applicable to arrays. You can convert from most other sequence-like structure to an array with the toArray method.
In [ ]:
val ar = (1 to 10).toArray
ar.toVector
In [ ]:
ar.reduce(_*_)
You can add the contents of two same-sized arrays by writing a while loop, but a more fucntional approach is to use the "zip" method which makes an array of pairs of values (2-tuples). With this array, you can use the sum function to add the arrays.
In [ ]:
val br = Array(4,4,5,6,3,3,4,4,2,2)
val ab = ar.zip(br)
val absum = ab.map(x => x._1 + x._2) // x is a 2-tuple, you need to extract its parts with _1 and _2 methods.
absum.toVector
Scala's functional syntax is very powerful and succinct, but generally not fast (the vector addition routine above is 100x slower than a while-loop-based version). BIDMach layers a different functional metaphor on top of Scala's arrays which is both fast and simple (similar to standard math syntax). We'll explore that shortly.
Like Python, Scala has a flexible Dictionary class. Actually there are several which use different internal representations. The HashMap will amost always be what we want.
In [ ]:
import scala.collection.mutable.HashMap
val a = HashMap[String,Int]()
In [ ]:
a("end") = 20
a("begin") = 40
a("up") = 100
a("down") = 1000
a
You can iterate through the map using a for loop to retrieve all the tuples:
In [ ]:
for (i <- a) println("key %s, value %d" format (i._1, i._2))
TODO: Write a function that computes and returns the (key,value) pair with the smallest value:
In [ ]: