Slide 1

Slide 1 text

@filippovitale May 2017 Ann Arbor FP Solving a Constraint Satisfaction Problem (CSP) with Monad Transformers .

Slide 2

Slide 2 text

SEND + MORE ------- MONEY @alexeyraga https://en.wikipedia.org/wiki/Verbal_arithmetic

Slide 3

Slide 3 text

SEND + MORE ------- MONEY @alexeyraga E == E == E

Slide 4

Slide 4 text

SEND + MORE ------- MONEY @alexeyraga S,M ≠ 0

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

SEND + MORE ------- MONEY M←1

Slide 7

Slide 7 text

SEND + 1ORE ------- 1ONEY M←1 O←0 S←8,9

Slide 8

Slide 8 text

M←1 Wait a minute... @alexeyraga

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

Immutability Higher-order function Monad Referential transparency

Slide 11

Slide 11 text

Mon*d

Slide 12

Slide 12 text

Brute Force Naïve implementation

Slide 13

Slide 13 text

Brute force 10!/(10 – 8)! ≈ 2 Million How big is the search space?

Slide 14

Slide 14 text

for (int s = 0; s < 10; ++s) for (int e = 0; e < 10; ++e) for (int n = 0; n < 10; ++n) for (int d = 0; d < 10; ++d) ... s != 0 e != s n != s && n != e d != s && d != e && d != n Imperative approach 8 nested loops

Slide 15

Slide 15 text

for (int s = 0; s < 10; ++s) for (int e = 0; e < 10; ++e) for (int n = 0; n < 10; ++n) for (int d = 0; d < 10; ++d) ... s != 0 e != s n != s && n != e d != s && d != e && d != n The solution boils down to: ➔ generating all those substitutions ➔ testing the constraints for each one

Slide 16

Slide 16 text

for { s <- 0 to 9 if s != 0 e <- 0 to 9 if e != s n <- 0 to 9 if n != s && n != e d <- 0 to 9 if d != s && d != e && d!=n ... for (int s = 0; s < 10; ++s) for (int e = 0; e < 10; ++e) for (int n = 0; n < 10; ++n) for (int d = 0; d < 10; ++d) ... s != 0 e != s n != s && n != e d != s && d != e && d != n The solution boils down to: ➔ generating all those substitutions ➔ testing the constraints for each one

Slide 17

Slide 17 text

Search candidate solutions Constraints satisfied?

Slide 18

Slide 18 text

val listOfDigits = (0 to 9).toList val solutions = for { s <- listOfDigits if s != 0 e <- listOfDigits if s != e n <- listOfDigits if s != n && e != n d <- listOfDigits if s != d && e != d && n != d m <- listOfDigits if m != 0 if s != m && e != m && n != m && d != m o <- listOfDigits if s != o && e != o && n != o && d != o && m != o r <- listOfDigits if s != r && e != r && n != r && d != r && m != r && o != r y <- listOfDigits if s != y && e != y && n != y && d != y && m != y && o != y && r != y } yield ??? Search candidate solutions Constraints satisfied?

Slide 19

Slide 19 text

val listOfDigits = (0 to 9).toList val solutions = for { s <- listOfDigits if s != 0 e <- listOfDigits if s != e n <- listOfDigits if s != n && e != n d <- listOfDigits if s != d && e != d && n != d m <- listOfDigits if m != 0 if s != m && e != m && n != m && d != m o <- listOfDigits if s != o && e != o && n != o && d != o && m != o r <- listOfDigits if s != r && e != r && n != r && d != r && m != r && o != r y <- listOfDigits if s != y && e != y && n != y && d != y && m != y && o != y && r != y send = List(s, e, n, d).reduce(_ * 10 + _) more = List(m, o, r, e).reduce(_ * 10 + _) money = List(m, o, n, e, y).reduce(_ * 10 + _) if send + more == money } yield (send, more, money) Search candidate solutions Constraints satisfied?

Slide 20

Slide 20 text

val listOfDigits = (0 to 9).toList val solutions = for { s <- listOfDigits if s != 0 e <- listOfDigits if s != e n <- listOfDigits if s != n && e != n d <- listOfDigits if s != d && e != d && n != d m <- listOfDigits if m != 0 if s != m && e != m && n != m && d != m o <- listOfDigits if s != o && e != o && n != o && d != o && m != o r <- listOfDigits if s != r && e != r && n != r && d != r && m != r && o != r y <- listOfDigits if s != y && e != y && n != y && d != y && m != y && o != y && r != y send = List(s, e, n, d).reduce(_ * 10 + _) more = List(m, o, r, e).reduce(_ * 10 + _) money = List(m, o, n, e, y).reduce(_ * 10 + _) if send + more == money } yield (send, more, money)

Slide 21

Slide 21 text

“Jimmy” and the Multiverse

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

0 1 2 3 4 5 6 7 8 9

Slide 24

Slide 24 text

1 2 3 4 5 6 7 8 9 0

Slide 25

Slide 25 text

? ? ? ? ? ? ? ? ? ? S = …

Slide 26

Slide 26 text

1 2 3 4 5 6 7 8 9 0 0 S = 0!

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

? ? ? ? ? ? ? ? ? ? S = …

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 5 1 2 3 4 0 6 7 8 9 0 6 2 3 4 5 1 7 8 9 0 1 7 3 4 5 6 2 8 9 0 1 3 2 4 5 6 7 8 9 0 1 8 3 4 5 6 7 8 9 0 1 4 3 2 5 6 7 8 9 0 1 9 3 4 5 6 7 8 2 S = 1

Slide 31

Slide 31 text

0 1 2 3 4 5 6 7 8 9 S = 1

Slide 32

Slide 32 text

0 1 2 3 4 5 6 7 8 9 S = 1 E = …

Slide 33

Slide 33 text

0 1 2 3 4 5 6 7 8 9 S = 1 E = … 0 2 3 4 5 6 7 8 9

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

Multi-verse Implementation using List

Slide 36

Slide 36 text

val l = for { a <- List(1, 2) b <- List(1, 2) } yield Map('a' -> a, 'b' -> b) val l = ???

Slide 37

Slide 37 text

val l = for { a <- List(1, 2) b <- List(1, 2) } yield Map('a' -> a, 'b' -> b) val l: List[A] = ???

Slide 38

Slide 38 text

val l = for { a <- List(1, 2) b <- List(1, 2) } yield Map('a' -> a, 'b' -> b) val l: List[Map[Char, Int]] = ???

Slide 39

Slide 39 text

val l = for { a <- List(1, 2) b <- List(1, 2) } yield Map('a' -> a, 'b' -> b) List(Map('a' -> 1, 'b' -> 1), Map('a' -> 1, 'b' -> 2), Map('a' -> 2, 'b' -> 1), Map('a' -> 2, 'b' -> 2))

Slide 40

Slide 40 text

val l = for { a <- List(1, 2) b <- List(1, 2) if a != b } yield Map('a' -> a, 'b' -> b) List(Map('a' -> 1, 'b' -> 2), Map('a' -> 2, 'b' -> 1))

Slide 41

Slide 41 text

Multi-verse Implementation using the State Monad

Slide 42

Slide 42 text

“The future is a function of the past.” – A. P. Robertson https://goo.gl/jt6bvz

Slide 43

Slide 43 text

// stateful computation type State[S, +A] = S => (S, A) https://bartoszmilewski.com/2016/11/30/monads-and-effects/#State

Slide 44

Slide 44 text

object State { def apply[S, A](f: S => (S, A)): State[S, A] }

Slide 45

Slide 45 text

// stateful computation f: S => (S, A) def select(xs: List[Int]): (List[Int], Int) = (xs.tail, xs.head)

Slide 46

Slide 46 text

// stateful computation f: S => (S, A) def select(xs: List[Int]): (List[Int], Int) = (xs.tail, xs.head)

Slide 47

Slide 47 text

// stateful computation f: S => (S, A) def select(xs: List[Int]): (List[Int], Int) = (xs.tail, xs.head)

Slide 48

Slide 48 text

import scalaz.State val s = for { a <- State(select) b <- State(select) } yield Map('a' -> a, 'b' -> b) val s = ???

Slide 49

Slide 49 text

import scalaz.State val s = for { a <- State(select) b <- State(select) } yield Map('a' -> a, 'b' -> b) val s: State[ S, A ] = ???

Slide 50

Slide 50 text

import scalaz.State val s = for { a <- State(select) b <- State(select) } yield Map('a' -> a, 'b' -> b) val s: State[ List[Int], Map[Char, Int] ] = ???

Slide 51

Slide 51 text

import scalaz.State val s = for { a <- State(select) b <- State(select) } yield Map('a' -> a, 'b' -> b) val s: State[ List[Int], Map[Char, Int] ] = ??? s.eval(List(1, 2)) ==== Map('a' -> 1, 'b' -> 2)

Slide 52

Slide 52 text

import scalaz.State val s = for { a <- State(select) b <- State(select) } yield Map('a' -> a, 'b' -> b) val s: State[ List[Int], Map[Char, Int] ] = ??? s.eval(List(8, 3)) ==== Map('a' -> 8, 'b' -> 3)

Slide 53

Slide 53 text

val s = for { a <- State(select) b <- State(select) c <- State(select) } yield Map('a' -> a, 'b' -> b, 'c' -> c) s.eval(1 |-> 3) ==== Map('a' -> 1, 'b' -> 2, 'c' -> 3)

Slide 54

Slide 54 text

val s = for { a <- State(select) b <- State(select) c <- State(select) d <- State(select) } yield Map('a' -> a, 'b' -> b, 'c' -> c, 'd' -> d) s.eval(1 |-> 4) ==== Map('a' -> 1, 'b' -> 2, 'c' -> 3, 'd' -> 4)

Slide 55

Slide 55 text

No content

Slide 56

Slide 56 text

Multi-verse Implementation using StateT[List, S, A]

Slide 57

Slide 57 text

No content

Slide 58

Slide 58 text

No content

Slide 59

Slide 59 text

def select[A](xs: List[A]): (List[A], A) State S => ( S , A) def select[A](xs: List[A]): List[(List[A], A)] StateT[F, S, A] S => F[( S , A)]

Slide 60

Slide 60 text

def select[A](xs: List[A]): List[(List[A], A)] = xs map { x => (xs.filterNot(_ == x), x) }

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

sl.eval(1 |-> 2) == ??? import scalaz.StateT import scalaz.std.list._ val sl = for { a <- StateT(select) b <- StateT(select) } yield Map('a' -> a, 'b' -> b)

Slide 63

Slide 63 text

import scalaz.StateT import scalaz.std.list._ val sl = for { a <- StateT(select) b <- StateT(select) } yield Map('a' -> a, 'b' -> b) sl.eval(1 |-> 2) == List(Map('a' -> 1, 'b' -> 2), Map('a' -> 2, 'b' -> 1)) https://gist.github.com/filippovitale/6cc45396ed917a2a8411

Slide 64

Slide 64 text

val sel = StateT(select[Int]) for { s <- sel e <- sel n <- sel d <- sel m <- sel o <- sel r <- sel y <- sel … }

Slide 65

Slide 65 text

val sel = StateT(select[Int]) for { s <- sel e <- sel n <- sel d <- sel m <- sel o <- sel r <- sel y <- sel … } For every `.flatMap` call, `sel` return a different number! 0 1 2 3 4 5 6 7 8 9

Slide 66

Slide 66 text

Solving the puzzle using StateT[List, S, A]

Slide 67

Slide 67 text

val sel = StateT(select[Int]) for { s <- sel if s != 0 e <- sel n <- sel d <- sel m <- sel if m != 0 o <- sel r <- sel y <- sel send = List(s, e, n, d).reduce(_ * 10 + _) more = List(m, o, r, e).reduce(_ * 10 + _) money = List(m, o, n, e, y).reduce(_ * 10 + _) if send + more == money } yield (s, e, n, d, m, o, r, y) MonadPlus MonadPlus https://speakerdeck.com/filippovitale/send-plus-more-equals-money-scalasyd-july-2015

Slide 68

Slide 68 text

Solving the puzzle using the Scala APIs

Slide 69

Slide 69 text

val maps = for { combination <- (0 to 9).combinations(8) // 45 combinazioni permutation <- combination.permutations // 40K permutazioni } yield ("sendmoremoney".distinct zip permutation).toMap // ~2M val solutions = for { m <- maps if m('s') != 0 && m('m') != 0 send = "send" map m reduce (_ * 10 + _) more = "more" map m reduce (_ * 10 + _) money = "money" map m reduce (_ * 10 + _) if send + more == money } yield (send, more, money) solutions.next() // (9567,1085,10652) Constraints satisfied? Search candidate solutions

Slide 70

Slide 70 text

These are all depth-first search implementations

Slide 71

Slide 71 text

No content

Slide 72

Slide 72 text

Solving the puzzle with a Genetic Algorithm http://dl.acm.org/citation.cfm?id=1725467

Slide 73

Slide 73 text

Genetic Algorithm type Digit = Int type Individual = Vector[Digit] // “sendmory” Vector length 8 ⇒ 45 different Species - Individual

Slide 74

Slide 74 text

Genetic Algorithm type Digit = Int type Individual = Vector[Digit] // “sendmory” type Population = List[Individual] - Individual - Population

Slide 75

Slide 75 text

Genetic Algorithm type Digit = Int type Individual = Vector[Digit] // “sendmory” type Population = List[Individual] def fitness(individual: Individual): Int // 0 - Individual - Population - Fitness function

Slide 76

Slide 76 text

Genetic Algorithm - Individual - Population - Fitness function - Mutation type Digit = Int type Individual = Vector[Digit] // “sendmory” type Population = List[Individual] def fitness(individual: Individual): Int def mutation(individual: Individual): Individual // asexual genetic algorithm

Slide 77

Slide 77 text

No content

Slide 78

Slide 78 text

Simple Implementation of the Genetic Algorithm to solve the puzzle

Slide 79

Slide 79 text

/** side-effect */ val r = util.Random val generationSize = 512 val generationRetainment = 32 // top 6% // 16 of the 28 possible mutations val individualProlificness: Int = generationSize / generationRetainment

Slide 80

Slide 80 text

def fitness(individual: Individual): Int = { math.abs(send + more - money) }

Slide 81

Slide 81 text

def fitness(individual: Individual): Int = { val cs = "sendmoremoney".distinct val m = cs.zip(individual).toMap math.abs(send + more - money) }

Slide 82

Slide 82 text

def fitness(individual: Individual): Int = { val cs = "sendmoremoney".distinct val m = cs.zip(individual).toMap val send = "send" map m reduce (_ * 10 + _) val more = "more" map m reduce (_ * 10 + _) val money = "money" map m reduce (_ * 10 + _) math.abs(send + more - money) }

Slide 83

Slide 83 text

val mutationIndices = List(0 |-> 7).combinations(2) def everyMutation(individual: Individual): Iterator[Individual] = r.shuffle(mutationIndices) .map { case Seq(i, j) => individual .updated(i, individual(j)) .updated(j, individual(i)) } // +-+-+-+-+-+-+-+-+ // |S|E|N|D|M|O|R|Y| // +-+-+-+-+-+-+-+-+ // |0|1|2|3|4|5|6|7| // +-+-+-+-+-+-+-+-+ // ^ ^ // | | // +-----+ // [i] [j]

Slide 84

Slide 84 text

val solution = ???

Slide 85

Slide 85 text

val solution = Iterator .iterate(generatePopulation(generationSize)) (createNextGeneration)

Slide 86

Slide 86 text

val solution = Iterator .iterate(generatePopulation(generationSize)) (createNextGeneration)

Slide 87

Slide 87 text

def createNextGeneration(currentGeneration: Population) : Population = ???

Slide 88

Slide 88 text

def createNextGeneration(currentGeneration: Population) : Population = currentGeneration .take(generationRetainment)

Slide 89

Slide 89 text

def createNextGeneration(currentGeneration: Population) : Population = currentGeneration .take(generationRetainment) .flatMap(everyMutation(_).take(individualProlificness))

Slide 90

Slide 90 text

def createNextGeneration(currentGeneration: Population) : Population = currentGeneration .take(generationRetainment) .flatMap(everyMutation(_).take(individualProlificness)) .sortBy(fitness)

Slide 91

Slide 91 text

def createNextGeneration(currentGeneration: Population) : Population = if (fitness(currentGeneration.head) == 0) { ??? } else { ??? }

Slide 92

Slide 92 text

def createNextGeneration(currentGeneration: Population) : Population = if (fitness(currentGeneration.head) == 0) { ??? } else { currentGeneration.take(generationRetainment) .flatMap(everyMutation(_).take(individualProlificness)) .sortBy(fitness) }

Slide 93

Slide 93 text

def createNextGeneration(currentGeneration: Population) : Population = if (fitness(currentGeneration.head) == 0) { currentGeneration.take(1) } else { currentGeneration.take(generationRetainment) .flatMap(everyMutation(_).take(individualProlificness)) .sortBy(fitness) }

Slide 94

Slide 94 text

val solution = Iterator .iterate(generatePopulation(generationSize)) (createNextGeneration) .dropWhile(_.size > 1)

Slide 95

Slide 95 text

val solution = Iterator .iterate(generatePopulation(generationSize)) (createNextGeneration) .dropWhile(_.size > 1) .map(_.head).next()

Slide 96

Slide 96 text

Benchmarking

Slide 97

Slide 97 text

No content

Slide 98

Slide 98 text

No content

Slide 99

Slide 99 text

@filippovitale May 2017 Ann Arbor FP $ tail -f questions