# 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?

• Array[Char]

# Nested for-loops

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

What is the type of res2?

• List[Char]

# 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

• Collection type same as first collection being iterated over.
• And type of contents are same as the yielded value.
• Hence, correct answer: C. Map[Char, Int]
• ... Evaluates to Map('A'->1, 'a'->1, 'B'->2, 'b'->2)

# 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)
• 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"))

# 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.

• bitStrings(0) ==> List()
• bitStrings(1) ==> ?
• c <- "0"
• s <- ... Nothing! (because bitStrings(0) ==> List())
• c <- "1"
• s <- ... Nothing! (because bitStrings(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.

# 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:

• 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)

### 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("")

• 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 <- "" (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("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 <- "" (because subStrings("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 <- "" (because subStrings("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")

# 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"

• Same holds for transformDigits
• Learn to spot the patterns...

# 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))

# 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)

# 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",
, 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

# 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!

# 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?

• Function canKill(q1, q2) returns true if queen at q1 can kill q2.
• Let's say queens : List[XY] is a list of occupied positions
• Let's say q : 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)(_ && _)

# 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

• When for each qo in queens, qo cannot kill q
• 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

### 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)
• Notice the base case!
• Note: can define vals (queen) inside for-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)

# 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

# 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.
• Goal: "Uniform Access Principle"
Make field access and side-effect-free method call look the same.

# 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:
1. abstract def hasNext: Boolean
1. abstract def next(): A
• Notice the parentheses! hasNext is side-effect-free; next is not.
• These are used to implement foreach.

# 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

• Collection type same as first collection being iterated over.
• And type of contents are same as the yielded 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!

# 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]
• How is a blank (unfilled) square represented?
• Simple: the position XY is just not in the map!

# 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

• 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

# 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
}
}
• Question: Why do we compute all solutions?
• Question: Why the .toIterator call?

# 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)
• 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.

# 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)
• Loop over squares in same row, col, grid
• Yield values at the square (if defined)

# 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)

# Today: Iteration AND Recursion

Elegant way to systematically:

1. Generate Combinations

2. Search Combinations

3. Generate & Search Combinations

### How ?

• Recursively generate & solve sub-problems, and
• Loop over candidates & yield valid solutions

# 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!
• The yucky version is not just longer ...
• ... it can be massively inefficient!

# PSA: Why choose 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!

# PSA: Why choose 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

# Why not always use Iterator?

• Cannot reuse after reaching the end
• Cannot rewind Iterator
• Cannot randomly access (efficiently)

# PSA: Bottom-line

For HW5:

• Stick to using for and yield and HOFs.
• Do not convert from Iterator to List.
• Otherwise, you will have all sorts of (unsolvable) memory issues.