CSE 130 [Winter 2014]

Iteration & Recursion (Redux)

Ravi Chugh, UC San Diego, Feb 27

Combining Iteration AND Recursion

Combining Iteration AND Recursion

Elegant way to systematically:

  1. Generate Combinations
  1. Search Combinations
  1. Generate & Search Combinations

Combining Iteration AND Recursion

Elegant way to systematically:

  1. Generate Combinations

  2. Search Combinations

  3. Generate & Search Combinations

Nested for-loops

val res1 = for (xs <- Array("cat", "dog", "mouse");
                x  <- xs) 
           yield x 

What is the type of res1?

Nested for-loops

val res2 = for (xs <- List("cat", "dog", "mouse");
                x  <- xs) 
           yield x 

What is the type of res2?

CLICKER: Nested for-loops

val res3 = for (kv <- Map("Aa" -> 1, "Bb" -> 2);
                c  <- kv._1)
           yield (c, kv._2)

What is the type of res3?

A. List[Char, Int]

B. List[(Char, Int)]

C. Map[Char, Int]

D. Map[(Char, Int)]

CLICKER: Nested for-loops

val res3 = for (kv <- Map("Aa" -> 1, "Bb" -> 2);
                c  <- kv._1)
           yield (c, kv._2)

What is the type of res3?

Type Of Output Collection

CLICKER: Nested for-loops

val res = for ( i <- List(1, 2)
              ; j <- List("a", "b")) yield (i, j)

What is the value of res ?

A. Nothing (Type Error)

B. List(1, 2, "a", "b")

C. List(1, "a", 2, "b")

D. List((1, "a"), (2, "b"))

E. List((1,"a"), (1,"b"), (2,"a"), (2,"b"))

val res = for ( i <- List(1, 2)
              ; j <- List("a", "b")) yield (i, j)

CLICKER: Combining Iteration & Recursion

def bitStrings(n: Int) : List[String] =
  if (n <= 0) { List() }
  else {
    for (c <- List("0", "1");
         s <- bitStrings(n-1))
    yield(c + s)
  }

What does bitStrings(2) return?

A. Nothing (Type Error)
B. List()
C. List("0", "1")
D. List("00", "01", "10", "11")

def bitStrings(n: Int) : List[String] =
  if (n <= 0) { List() } else {
    for (c <- List("0", "1");
         s <- bitStrings(n-1))
    yield(c + s)
  }

What does bitStrings(2) evaluate to? Let's work backwards.

def bitStrings(n: Int) : List[String] =
  if (n <= 0) { List() } else {
    for (c <- List("0", "1");
         s <- bitStrings(n-1))
    yield(c + s)
  }

What does bitStrings(2) evaluate to? Let's work backwards.

Recap: Nested Loops

Nested loops are like cartesian products:

scala> for ( i <- List(1, 2)
           ; j <- List("a", "b")) 
       yield (i, j)
res0: List[(Int, String)] =
        List((1,a), (1,b), (2,a), (2,b))

Recap: Nested Loops

Result is empty if either the first sequence is empty

scala> for ( i <- List()
           ; j <- List("a", "b")) 
       yield (i, j)
res2: List[(Nothing, String)] = List()

or the second sequence is empty

scala> for ( i <- List(1,2)
           ; j <- List()) 
       yield (i, j)
res1: List[(Int, Nothing)] = List()

def bitStrings(n: Int) : List[String] =
  if (n <= 0) { List() } else {
    for (c <- List("0", "1");
         s <- bitStrings(n-1))
    yield(c + s)
  }

How can we fix this function so that:

Base Case

Only bit-string of length 0 is the empty string ""

def bitStrings(n: Int) : List[String] =
  if (n <= 0) { List("") } else {
    for (c <- List("0", "1");
         s <- bitStrings(n-1))
    yield(c + s)
  }

CLICKER: What is value of foo("go") ?

def foo(w: String) : List[String] =
  if (w == "")  { List(w) } else
    for (c <- List("", w.substring(0, 1));
         s <- foo(w.substring(1)))      
    yield(c + s)

Hint: "sunday".substring(0, 1) ==> "s" and
"sun".substring(1) ==> "unday"

A. Nothing (Infinite Loop)

B. List()

C. List("", "o", "g", "go")

D. List("")

E. List("o", "g", "go")

def subStrings(w: String) : List[String] =
  if (w == "")  { List(w) } else
    for (c <- List("", w.substring(0, 1));
         s <- subStrings(w.substring(1)))      
    yield(c + s)

Again, let's work backwards.

subStrings("") ==> List("")

def subStrings(w: String) : List[String] =
  if (w == "")  { List(w) } else
    for (c <- List("", w.substring(0, 1));
         s <- subStrings(w.substring(1)))      
    yield(c + s)

Let's work backwards. subStrings("o") ==> ?

def subStrings(w: String) : List[String] =
  if (w == "")  { List(w) } else
    for (c <- List("", w.substring(0, 1));
         s <- subStrings(w.substring(1)))      
    yield(c + s)

Let's work backwards. subStrings("o") ==> List("", "o")

def subStrings(w: String) : List[String] =
  if (w == "")  { List(w) } else
    for (c <- List("", w.substring(0, 1));
         s <- subStrings(w.substring(1)))      
    yield(c + s)

Let's work backwards. subStrings("go") ==> ?

def subStrings(w: String) : List[String] =
  if (w == "")  { List(w) } else
    for (c <- List("", w.substring(0, 1));
         s <- subStrings(w.substring(1)))      
    yield(c + s)

Let's work backwards. subStrings("go") ==> List("", "o", ???)

def subStrings(w: String) : List[String] =
  if (w == "")  { List(w) } else
    for (c <- List("", w.substring(0, 1));
         s <- subStrings(w.substring(1)))      
    yield(c + s)

Let's work backwards.

subStrings("go") ==> List("", "o", "g", "go")

Combining Iteration & Recursion

Mega Hint for HW5

substrings is almost identical to transformCapitalize

subStrings

Replace each character "c" by either "c" or ""

transformCapitalize

Replace each character "c" by either "c" or "C"

Combining Iteration AND Recursion

Elegant way to systematically:

  1. Generate Combinations

  2. Search Combinations

  3. Generate & Search Combinations

CLICKER: Systematic Search/Query

val res = for (i <- 1 to 10;
               j <- 1 to 10;
               k <- 1 to 10 
               if i*i + j*j == k*k)
          yield (i, j, k)

What is the value of res ?

A. Vector()

B. Vector((3,4,5), (6,8,10))

C. Vector((6,8,10), (3,4,5))

D. Vector((3,4,5), (4,3,5), (6,8,10), (8,6,10))

E. Vector((8,6,10), (6,8,10), (4,3,5), (3,4,5))

Systematic Search/Query

val res = for (i <- 1 to 10;
               j <- 1 to 10;
               k <- 1 to 10 
               if i*i + j*j == k*k)
          yield (i, j, k)

Is equivalent to:

val res =
  (for (i <- 1 to 10; j <- 1 to 10; k <- 1 to 10)
       yield (i, j, k)
  ).filter({ case (i,j,k) => i*i + j*j == k*k })

Systematic Search/Query

val res =
  (for (i <- 1 to 10; j <- 1 to 10; k <- 1 to 10)
       yield (i, j, k)
  ).filter({ case (i,j,k) => i*i + j*j == k*k })

Which is equivalent to:

Vector( ( 1,1,1), ( 1,1,2), ..., ( 1, 1,10),
        ( 1,2,1), ( 1,2,2), ..., ( 1,10,10),
        ( 2,1,1), ( 2,1,2), ..., ( 2, 1,10),
        ( 2,2,1), ( 2,2,2), ..., ( 2,10,10),
        ...,
        (10,1,1), (10,1,2), ..., (10, 1,10),
        (10,2,1), (10,2,2), ..., (10,10,10)
      ).filter({ case (i,j,k) => i*i + j*j == k*k })

Systematic Search/Query

filter Preserves Sequence Order

Hence, the expression

Vector( ( 1,1,1), ( 1,1,2), ..., ( 1, 1,10),
        ( 1,2,1), ( 1,2,2), ..., ( 1,10,10),
        ( 2,1,1), ( 2,1,2), ..., ( 2, 1,10),
        ( 2,2,1), ( 2,2,2), ..., ( 2,10,10),
        ...,
        (10,1,1), (10,1,2), ..., (10, 1,10),
        (10,2,1), (10,2,2), ..., (10,10,10)
      ).filter({ case (i,j,k) => i*i + j*j == k*k })

evaluates to:

D. Vector((3,4,5), (4,3,5), (6,8,10), (8,6,10))

Can Query More Interesting Objects

Brief Digression: Case Classes

Case Classes

case class Name(x1: T1, x2: T2, ..., xn: Tn)

Creates a class Name with

  1. Immutable fields x1 of type T1, ..., xn of type Tn
  1. Constructor method for object creation
  1. Extractor method for pattern matching (later...)

Case Classes

Here is a case class (just a fancy record for now):

case class Novel(title: String, author: String)

You can create instances without writing new:

scala> val n1 =
  Novel ("A Feast For Crows", "George R. R. Martin")
n1: Novel =
  Novel(A Feast For Crows,George R. R. Martin)

Access fields as expected:

scala> n1.author
n1: String = George R. R. Martin

Case Classes

Another case class:

case class Album(title: String, artist: String)

Again, no need for new:

scala> val a = Album("We are glitter", "Goldfrapp")
a: Album = Album(We are glitter,Goldfrapp)

Back to Querying More Interesting Objects

Database Style Queries

Suppose we have a "database" of novels:

val novels = 
  List( Novel ( "Skinny Legs and All"
              , "Tom Robbins")
      , Novel ( "Breakfast of Champions"
              , "Kurt Vonnegut")
      , Novel ( "An Object of Beauty"
              , "Steve Martin")
      , Novel ( "Sophie's Choice"
              , "William Styron")
      , Novel ( "The Other"
              , "Tom Tryon")
      )

Database Style Queries

... and a "database" of albums:

val albums =
  List( Album ( "Rare Bird Alert"
              , "Steve Martin")
      , Album ( "Paper Airplane"
              , "Alison Krauss and Union Station")
      , Album ( "Earl Scruggs and Friends"
              , "Earl Scruggs")
      )

Database Style Queries

... and a "database" of movies:

case class Movie(title: String, actors: List[String])

val movies = 
  List( Movie ("Airplane", 
               List ( "Leslie Nielsen", "Robert Stack"))
      , Movie ("Young Frankenstein", 
               List ("Gene Wilder", "Madeline Kahn"))
      , Movie ("The Pink Panther", 
               List ("Steve Martin", "Jean Reno"))
      )

Query: Is there tripleThreat ?

A person who has

  1. written a book,
  2. acted in a movie, and
  3. performed in an album ?

Query: Is there a tripleThreat ?

A person who has

  1. written a book,
  2. acted in a movie, and
  3. performed in an album ?
for (n <- novels;
     a <- albums;
     m <- movies;
     if (n.author == a.artist &&
         m.actors.contains (n.author)))
yield (n.author)

Query: Is there a tripleThreat ?

for (n <- novels;
     a <- albums;
     m <- movies;
     if (n.author == a.artist &&
         m.actors.contains (n.author)))
yield (n.author)

Evaluates (not very efficiently) to:

res: List[String] = List(Steve Martin)

From Loops-to-SQL

Databases are optimized to evaluate queries like this.

You can define a special collection type such that the expression

for (n <- novels;
     a <- albums;
     m <- movies;
     if (n.author == a.artist &&
         m.actors.contains (n.author)))
yield (n.author)

is automatically converted into a SQL (database) query!

From Loops-to-SQL

If you're curious (i.e. not on homeworks or final):

Combining Iteration AND Recursion

Elegant way to systematically:

  1. Generate Combinations

  2. Search Combinations

  3. Generate & Search Combinations

Iteration + Recursion + Filter
= Fun Ensues

Generate and Search: The N-Queens Puzzle

Given a chess board of size n rows and n columns

Find a placement of n queens so none can kill each other!


Generate and Search: The N-Queens Puzzle

Given a chess board of size n rows and n columns

Find a placement of n queens so none can kill each other!


First: What is a Solution?

Generate and Search: The N-Queens Puzzle

The first question: what is a solution ?

We represent each queen by its position XY, a pair of (row, col).

type XY = (Int, Int)

Generate and Search: The N-Queens Puzzle

The first question: what is a solution ?

We represent each queen by its position XY, a pair of (row, col).

type XY = (Int, Int)

One queen q1 can kill another q2 if

def canKill(q1: XY, q2: XY) = 
  q1._1 == q2._1 ||  // same row
  q1._2 == q2._2 ||  // same column
                     // same diagonal
  (q1._1 - q2._1).abs == (q1._2 - q2._2).abs

CLICKER: When is a board safe?

Which evaluates to true exactly when none of queens can kill q ?

A. for (qo <- queens) yield !canKill(qo, q)

B. for (qo <- queens) yield canKill(qo, q)

C. queens.exists(!canKill(_,q))

D. queens.forall(!canKill(_,q))

E. queens.map(!canKill(_,q)).foldLeft(true)(_ && _)

When is a board safe?

canKill(q1, q2) is true if a queen at q1 can kill another q2

queens : List[XY] is a list of occupied positions

q : XY is a new queen position.

Board is Safe

We represent each queen by its position XY, a pair of (row, col):

type XY = (Int, Int)

One queen q1 can kill another q2 if

def canKill(q1: XY, q2: XY) =
  eqRow(q1, q2) || eqCol(q1, q2) || eqDia(q1, q2)

A queen at q is safe w.r.t. other queens if:

def isSafe(q: XY, others: List[XY]) = 
  others forall (!canKill(_, q))

def placeQueens(columns: Int, row: Int) : List[List[XY]]

To place queens on row r ... 0

Strategy: Recursive Search

def placeQueens(columns: Int, row: Int) : List[List[XY]] = 
  if (row == 0) 
    List(List())
  else 
    for { queens <- placeQueens(columns, row - 1)
          column <- 1 to columns 
          queen   = (row, column)
          if isSafe(queen, queens) } 
    yield (queen :: queens)

Let's write a simple function to print a solution...

(See scala-iterators.scala for much nicer printing function!)

def printSolution(size: Int, sol: List[XY]) =
  for (i <- 1 to size) {
    for (j <- 1 to size) {
      if (sol contains (i,j)) print("X") else print("-")
    }
    println("")
  }

... and a function to print the first solution (if any):

def SolveQueens(n: Int) =
  for (sol <- placeQueens(n, n) take 1)
    printSolution(n, sol)

Generate and Search: The N-Queens Puzzle

Let's run it!

scala> SolveQueens(4)
-X--
---X
X---
--X-

Generate and Search: The N-Queens Puzzle

Let's run it!

scala> SolveQueens(8)
X-------
----X---
-------X
-----X--
--X-----
------X-
-X------
---X----

Generate and Search: The N-Queens Puzzle

Let's run it!

scala> SolveQueens(2)

scala> SolveQueens(3)

scala> 

There are no solutions for N = 2, N = 3

Iteration + Recursion + Filter
= Fun Continues

Generate and Search: A Sudoku Solver

But first...

Digression: Zero-Arg Methods... (Again)

scala> def f() = println("hello")
f: ()Unit

scala> f()
hello

Can be called without parentheses:

scala> f
hello

Digression: Zero-Arg Methods... (Again)

Can also be defined such that parens must be omitted:

scala> def g = println("hello")
g: Unit

scala> g
hello

scala> g
hello

scala> g()
... error: Unit does not take parameters ... g()

Digression: Zero-Arg Methods... (Again)

Can also be defined such that parens must be omitted:

scala> def g = println("hello")
g: Unit

Note that g is called each time, it's not the same as:

scala> val h = println("hello")
hello
h: Unit = ()

scala> h

scala> h

Digression: Zero-Arg Methods... (Again)

  1. If method defined with empty parameter list,
    Then may be called without parens.
    Recommended: use parens iff function has no side-effects.
  1. If method defined with no parameter list,
    Then must be called without parens.

Iterable vs. Iterator

Iterable

Iterable vs. Iterator

Iterator

  1. abstract def hasNext: Boolean
  1. abstract def next(): A

CLICKER: Nested for-loops

val res3 = for (xs <- Iterator("cat", "dog");
                x  <- xs) 
           yield x 

What is the type of res3?

A. String

B. List[String]

C. Iterator[String]

D. Array[Char]

E. Iterator[Char]

CLICKER: Nested for-loops

val res3 = for (xs <- Iterator("cat", "dog");
                x  <- xs) 
           yield x 

What is the type of res3?

RECALL: Type Of Output Collection

"Choose Your Own Adventure"

If class time is short, jump ahead.

But take a look at slides and Sudoku code at home!

Generate and Search: A Sudoku Solver

Given a Sudoku Puzzle

5, 3,  ,  , 7,  ,  ,  ,  
6,  ,  , 1, 0, 5,  ,  ,  
 , 0, 8,  ,  ,  ,  , 6,  
8,  ,  ,  , 6,  ,  ,  , 3
4,  ,  , 8,  , 3,  ,  , 1
7,  ,  ,  , 2,  ,  ,  , 6
 , 6,  ,  ,  ,  , 2, 8,  
 ,  ,  , 4, 1, 0,  ,  , 5
 ,  ,  ,  , 8,  ,  , 7, 0

Generate and Search: A Sudoku Solver

Find a Solution

5, 3, 4, 6, 7, 8, 0, 1, 2
6, 7, 2, 1, 0, 5, 3, 4, 8
1, 0, 8, 3, 4, 2, 5, 6, 7
8, 5, 0, 7, 6, 1, 4, 2, 3
4, 2, 6, 8, 5, 3, 7, 0, 1
7, 1, 3, 0, 2, 4, 8, 5, 6
0, 6, 1, 5, 3, 7, 2, 8, 4
2, 8, 7, 4, 1, 0, 6, 3, 5
3, 4, 5, 2, 8, 6, 1, 7, 0

Generate and Search: Sudoku Solver

The first question: what is a sudoku board ?

Generate and Search: Sudoku Solver

The first question: what is a sudoku board ?

Representing Squares

We represent each square by its position XY, a pair of (row, col):

type XY = (Int, Int)

Generate and Search: Sudoku Solver

The first question: what is a sudoku board ?

Representing Squares

We represent each square by its position XY, a pair of (row, col):

type XY = (Int, Int)

Representing Boards

We represent the board by a (partial) map from XY to Int:

type Board = Map[XY, Int]

Representing Sudoku Boards

5, 3,  ,  , 7,  ,  ,  ,  
6,  ,  , 1, 0, 5,  ,  ,  
 , 0, 8,  ,  ,  ,  , 6,  
8,  ,  ,  , 6,  ,  ,  , 3
4,  ,  , 8,  , 3,  ,  , 1
7,  ,  ,  , 2,  ,  ,  , 6
 , 6,  ,  ,  ,  , 2, 8,  
 ,  ,  , 4, 1, 0,  ,  , 5
 ,  ,  ,  , 8,  ,  , 7, 0

is represented by

Map ((0, 0) -> 5, (0, 1) -> 3, (0, 4) -> 7,
     (1, 0) -> 6, (1, 3) -> 1, (1, 4) -> 0, (1, 5) -> 5, 
     ...
    )

Generate and Search: Sudoku Solver

Given a partially filled Sudoku Board

Find a way to fill in all squares

Strategy: Recursive Search

Generate and Search: Sudoku Solver

def solve(b: Board) : Iterator[Board] = {
  val blanks = candidates(b)
  if (blanks.isEmpty) Iterator(b)  
  else { 
    val (xy, choices) = blanks minBy (_._2.length)
    for (choice <- choices.toIterator; 
         bFull  <- solve(b + (xy -> choice)))
    yield bFull
  }
}

Generate and Search: Sudoku Solver

Complete implementation can be found in scala-iterators.scala.

Contains examples of various higher-order functions, syntactic sugar, etc.

Scala is a big language, so learn by example!

Generate and Search: Sudoku Solver

Details: Computing candidates for a board

def candidates (b: Board) =
  for { x <- 0 until 9 ;
        y <- 0 until 9 ;
        if (!b.isDefinedAt((x,y))) ;
        xy   = (x, y) ;
        vals = (0 until 9) diff (alreadyTaken(x,y,b)) }
  yield (xy, vals)

Generate and Search: Sudoku Solver

Details: Computing alreadyTaken values for a square

def alreadyTaken(x: Int, y: Int, b: Board) =
  for (p <- pointsOfCol(x) ++ 
            pointsOfRow(y) ++ 
            pointsOfGrid(x, y)
       if b.isDefinedAt(p))
  yield b(p)

Generate and Search: Sudoku Solver

Details: Points in Same Grid

def pointsOfGrid(x: Int, y: Int) = {
  val (x0, y0) = ((x/3) * 3,  (y/3) * 3) 
  for (px <- x0 until x0 + 3 ;
       py <- y0 until y0 + 3 )
  yield (px, py)
}

Generate and Search: Sudoku Solver

Details: Points in Same Row/Column

def pointsOfCol(x: Int) =
  for (y <- 0 until 9) yield (x, y)

def pointsOfRow(y: Int) = 
  for (x <- 0 until 9) yield (x, y)

Let's run the solver!

Today: Iteration AND Recursion

Elegant way to systematically:

  1. Generate Combinations

  2. Search Combinations

  3. Generate & Search Combinations

How ?

Public Service Announcement:
Why choose Iterator vs. List?

PSA: Why choose Iterator vs. List?

We chose Iterator and not List for password cracking problem.

Why?

Not to make you write...

words.toList.filter(...).toIterator     // yuck

PSA: Why choose Iterator vs. List?

We chose Iterator and not List for password cracking problem.

Not to make you write...

words.toList.filter(...).toIterator     // yuck

... which is the same as

words.filter(...)                       // sweet!

PSA: Why choose Iterator vs. List?

We chose Iterator and not List for password cracking problem.

Iterator

List

PSA: Why choose Iterator vs. List?

We chose Iterator and not List for password cracking problem.

Iterator

Iterator

def allVowels(megaDictionary: Iterator[String])
  : Iterator[String] = 
  for { w <- megaDictionary if hasAllVowels(w)  }
    yield w

List

def allVowels(megaDictionary: List[String])
  : List[String] = 
  for (w <- megaDictionary if hasAllVowels(w))
    yield w

Why not always use Iterator?

PSA: Bottom-line

For HW5: