Elegant way to systematically:
- Generate Combinations
- Search Combinations
- Generate & Search Combinations
Elegant way to systematically:
Generate Combinations
Search Combinations
Generate & Search Combinations
for
-loopsval res1 = for (xs <- Array("cat", "dog", "mouse");
x <- xs)
yield x
What is the type of res1
?
- Array[Char]
for
-loopsval res2 = for (xs <- List("cat", "dog", "mouse");
x <- xs)
yield x
What is the type of res2
?
- List[Char]
for
-loopsval 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)]
for
-loopsval res3 = for (kv <- Map("Aa" -> 1, "Bb" -> 2);
c <- kv._1)
yield (c, kv._2)
What is the type of res3
?
- Collection type same as first collection being iterated over.
- And type of contents are same as the
yield
ed value.
- Hence, correct answer: C.
Map[Char, Int]
- ... Evaluates to
Map('A'->1, 'a'->1, 'B'->2, 'b'->2)
for
-loopsval 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)
i <- 1
j <- a
yield (1, "a")
j <- b
yield (1, "b")
i <- 2
j <- a
yield (2, "a")
j <- b
yield (2, "b")
- E:
List((1,"a"), (1,"b"), (2,"a"), (2,"b"))
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.
bitStrings(0)
==>List()
bitStrings(1)
==> ?c <- "0"
s <- ...
Nothing! (becausebitStrings(0)
==>List()
)c <- "1"
s <- ...
Nothing! (becausebitStrings(0)
==>List()
)
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.
bitStrings(0)
==>List()
bitStrings(1)
==>List()
- ... same for
bitStrings(2)
,bitStrings(3)
, etc.
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))
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:
bitStrs(1)
==> List("0","1")
bitStrs(2)
==> List("00","01","10","11")
bitStrs(3)
List("000","001","010","011",
"100","101","110","111")
- Fix the base case:
bitStrings(0)
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)
}
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("")
- Correct base case!
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")
==> ?
c <- ""
s <- ""
(fromsubStrings("")
==>List("")
)yield ("" + "")
c <- "o"
s <- ""
(fromsubStrings("")
==>List("")
)yield ("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")
c <- ""
s <- ""
(from subStrings("")
==> List("")
) yield ("" + "")
c <- "o"
s <- ""
(from subStrings("")
==> List("")
) yield ("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")
==> ?
c <- ""
s <- ""
(becausesubStrings("o")
==>List("", "o")
)yield ("" + "")
s <- "o"
yield ("" + "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", ???)
c <- "g"
s <- ""
(becausesubStrings("o")
==>List("", "o")
)yield ("g" + "")
s <- "o"
yield ("g" + "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")
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"
- Same holds for
transformDigits
- Learn to spot the patterns...
Elegant way to systematically:
Generate Combinations
Search Combinations
Generate & Search Combinations
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))
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 })
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 })
filter
Preserves Sequence OrderHence, 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))
case class Name(x1: T1, x2: T2, ..., xn: Tn)
Creates a class Name
with
- Immutable fields
x1
of typeT1
, ...,xn
of typeTn
- Constructor method for object creation
- Extractor method for pattern matching (later...)
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
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)
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")
)
... 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")
)
... 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"))
)
tripleThreat
?A person who has
- written a book,
- acted in a movie, and
- performed in an album ?
tripleThreat
?A person who has
for (n <- novels;
a <- albums;
m <- movies;
if (n.author == a.artist &&
m.actors.contains (n.author)))
yield (n.author)
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)
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!
If you're curious (i.e. not on homeworks or final):
Elegant way to systematically:
Generate Combinations
Search Combinations
Generate & Search Combinations
Given a chess board of size n
rows and n
columns
Find a placement of n
queens so none can kill each other!
Given a chess board of size n
rows and n
columns
Find a placement of n
queens so none can kill each other!
The first question: what is a solution ?
We represent each queen by its position XY
, a pair of (row, col)
.
type XY = (Int, Int)
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
canKill(q1, q2)
returns true if queen at q1
can kill q2
.queens : List[XY]
is a list of occupied positionsq : XY
is a new queen position.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)(_ && _)
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.
- When for each
qo
inqueens
,qo
cannot killq
queens.forall (qo => !(canKill(qo, q))
- D:
queens.forall (!(canKill(_, q))
- However, E: is also right. (Why?)
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
- (recursively) place queens on rows
r-1
...0
- for each solution to the subproblem
- for each possible placement for current row
r
- return new position if
isSafe
w.r.t. recursive solution
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)
- Notice the base case!
- Note: can define
val
s (queen
) insidefor
-loop:
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)
scala> SolveQueens(4)
-X--
---X
X---
--X-
scala> SolveQueens(8)
X-------
----X---
-------X
-----X--
--X-----
------X-
-X------
---X----
scala> SolveQueens(2)
scala> SolveQueens(3)
scala>
There are no solutions for N = 2, N = 3
scala> def f() = println("hello")
f: ()Unit
scala> f()
hello
Can be called without parentheses:
scala> f
hello
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()
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
- If method defined with empty parameter list,
Then may be called without parens.
Recommended: use parens iff function has no side-effects.
- If method defined with no parameter list,
Then must be called without parens.
- Goal: "Uniform Access Principle"
Make field access and side-effect-free method call look the same.
- See style guidelines and this discussion.
Iterable
vs. Iterator
Iterable
Iterable[A]
is a trait ...
- ... that describes objects that can be iterated over.
- This trait requires that
foreach
be implemented in all subclasses.
- We'll get to traits, classes, subclasses, ...
Iterable
vs. Iterator
Iterator
Iterator[A]
is a trait ...
- ... that requires objects to define (at least) the following:
abstract def hasNext: Boolean
abstract def next(): A
- Notice the parentheses!
hasNext
is side-effect-free;next
is not.
- These are used to implement
foreach
.
for
-loopsval 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]
for
-loopsval res3 = for (xs <- Iterator("cat", "dog");
x <- xs)
yield x
What is the type of res3
?
- Collection type same as first collection being iterated over.
- And type of contents are same as the
yield
ed value.
- Hence, correct answer: E.
Iterator[Char]
If class time is short, jump ahead.
But take a look at slides and Sudoku code at home!
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
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
The first question: what is a sudoku board ?
The first question: what is a sudoku board ?
We represent each square by its position XY
, a pair of (row, col)
:
type XY = (Int, Int)
The first question: what is a sudoku board ?
We represent each square by its position XY
, a pair of (row, col)
:
type XY = (Int, Int)
We represent the board by a (partial) map from XY
to Int
:
type Board = Map[XY, Int]
- How is a blank (unfilled) square represented?
- Simple: the position
XY
is just not in the map!
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,
...
)
Given a partially filled Sudoku Board
Find a way to fill in all squares
- If no blank squares on board then
- return board!
- Else pick blank with fewest possible choices
- for each choice
- recursively solve board with blank filled by choice
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
}
}
- Question: Why do we compute all solutions?
- Question: Why the
.toIterator
call?
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!
candidates
for a boarddef 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)
- Loop over all squares,
- Filter the ones that are not defined,
- Prune the already taken values for the square,
- Yield a tuple of the cell and remaining values.
alreadyTaken
values for a squaredef alreadyTaken(x: Int, y: Int, b: Board) =
for (p <- pointsOfCol(x) ++
pointsOfRow(y) ++
pointsOfGrid(x, y)
if b.isDefinedAt(p))
yield b(p)
- Loop over squares in same row, col, grid
- Yield values at the square (if defined)
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)
}
def pointsOfCol(x: Int) =
for (y <- 0 until 9) yield (x, y)
def pointsOfRow(y: Int) =
for (x <- 0 until 9) yield (x, y)
Elegant way to systematically:
Generate Combinations
Search Combinations
Generate & Search Combinations
- Recursively generate & solve sub-problems, and
- Loop over candidates & yield valid solutions
Iterator
vs. List
?Iterator
vs. List
?We chose Iterator
and not List
for password cracking problem.
Not to make you write...
words.toList.filter(...).toIterator // yuck
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!
- The yucky version is not just longer ...
- ... it can be massively inefficient!
Iterator
vs. List
?We chose Iterator
and not List
for password cracking problem.
Iterator
- "Lazily" computes one element at a time
- At each call to
.next
List
- Builds entire sequence up at once!
- Stores entire sequence in memory!
Iterator
vs. List
?We chose Iterator
and not List
for password cracking problem.
Iterator
"Lazily" computes one element at a time
At each call to .next
- So
Iterator
is good for operating on big streams of data
- ... like large dictionaries of words and transformations of words
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
Iterator
uses O(1) memory (at any given time) ...List
uses O(N) memory, where N =megaDictionary
size.- Note: Only difference is in the type
Iterator
?
- Cannot reuse after reaching the end
- Cannot rewind
Iterator
- Cannot randomly access (efficiently)
For HW5:
- Stick to using
for
andyield
and HOFs.
- Do not convert from
Iterator
toList
.
- Otherwise, you will have all sorts of (unsolvable) memory issues.