Pascal's triangle (Scala)

From LiteratePrograms
Jump to: navigation, search
Other implementations: Scala | Scala, v2.8

This is a Scala program illustrating Pascal's triangle. Here's an example of what you'll see if you run this program:

$ scala PascalsTriangle 10
             1
            1 1
           1 2 1
          1 3 3 1
         1 4 6 4 1
       1 5 10 10 5 1
     1 6 15 20 15 6 1
    1 7 21 35 35 21 7 1
  1 8 28 56 70 56 28 8 1
1 9 36 84 126 126 84 36 9 1

Contents

[edit] Approach

The basic method used here to compute the triangle is to generate each new row as the sum of two "shifted" versions of the previous row, where the shifting is accomplished by appending a zero to the end of the row. So, for example, the row

1 2 1

will be "shifted" both left and right to produce

1 2 1 0
0 1 2 1

Summing these two shifted rows yields

1 3 3 1

which is the next row of the triangle.

Our implementation will use this approach to lazily compute the triangle as a Stream of rows:

<<pascal stream>>=
def pascalStream(row: List[Int]): Stream[List[Int]] =
  Stream.cons(row, pascalStream(addList(shiftLeft(row), shiftRight(row))))

[edit] Implementation

We use lists of integers to represent each row. Thus the two shifting operations are implemented as operations on a list. The shift right is easily accomplished by adding a 0 to the head of the list. The shift to the left is a little more involved, since it requires inserting a value at the end of the list. In this case, we've opted to concatenate the row with a new list containing just the value 0.

<<shift operations>>=
def shiftRight(row: List[Int]): List[Int] = 0::row
def shiftLeft(row: List[Int]): List[Int] = row:::List(0)  

Combining the two shifted versions of a row is also an operation on lists. A straightforward way to perform an element-wise sum on the two lists is to zip the two lists together -- which produces a single list of 2-tuples containing pairs of elements from each list -- and then map a sum across that single list.

<<element-wise addition>>=
def addList(l1: List[Int], l2: List[Int]): List[Int] = {
  (l1 zip l2) map { case (x, y) => x + y }
} 

A slightly more efficient way to achieve the same end would be to perform the summing operation at the same time as the two lists are combined, instead of making two passes through the lists. Here's a version of addList that performs summing in this way:

def addList(l1: List[Int], l2: List[Int]): List[Int] = (l1, l2) match { 
  case (_, Nil) => Nil
  case (Nil, _) => Nil
  case (h1::t1, h2::t2) => (h1 + h2)::addList(t1, t2)
}

[edit] Printing the triangle

Of course, generating lots of nifty Pascal's triangles isn't all that helpful if we can't see them. We could just directly print the elements of the triangle by converting rows to strings and printing them:

def printTriangle(triangle: Stream[List[Int]], nrows: Int) {
  val rows = triangle.take(nrows).map(_.mkString(" "))
  rows foreach { println _ }
}

But taking this approach produces an output that isn't much like the classical presentation of Pascal's triangle because everything is left-justified:

1
1 1
1 2 1
1 3 3 1
1 4 6 4 1 

We can make a more nicely formatted triangle by adding a little padding to the start of each row:

<<print triangle>>=
def printTriangle(triangle: Stream[List[Int]], nrows: Int) {
  val rows = triangle.take(nrows).map(_.mkString(" "))
    
  row padding
        
  rows foreach { row => pad(row); println(row) }
}

That way, the rows are center-justified, like this:

    1
   1 1
  1 2 1
 1 3 3 1
1 4 6 4 1

How much padding do we need to add? Well, we won't bother padding the widest row -- that way it'll be flush against the left side of the screen -- and then print enough spaces before each row that the distance from the left edge of the screen to the center of that row is (roughly) the same as the distance to the center of the widest row.

<<row padding>>=
val maxWidth = rows.last.length

def pad(row: String) {
  val padSize = (maxWidth - row.length) / 2
  for(i <- 1 to padSize) { print(" ") }
}

[edit] Completing the program

To round out our Pascal's triangle program, we'll wrap everything in a singleton object, and add a main method that reads a number of rows to print from the command-line arguments. In this case we want to print triangles starting from the smallest row, so we seed pascalStream with List(1).

<<PascalsTriangle.scala>>=
object PascalsTriangle {
    
  shift operations
  
  element-wise addition
  
  pascal stream
  
  print triangle
  
  def main(args: Array[String]) {
    val nrows = if (args.length > 0) args(0).toInt else 5
    val lazyTriangle = pascalStream(List(1))
    
    printTriangle(lazyTriangle, nrows)
  }
}
Download code
hijacker
hijacker
hijacker
hijacker