<!-- vim: set tw=80 fo=cqt wm=0 colorcolumn=83: -->
<meta charset="utf-8">
<head>
<title>Resolution du Sudoku en Haskell</title>
<style>
pre{font-size:125%;}
code{font-size:125%;}
</style>
</head>
<body bgcolor=#d0ffd0>
<h1>Résolution du Sudoku en Haskell 
(<a href="./sudoku.lhs">sudoku.lhs</a>)
</h1>

<address>
Pierre-Edouard Portier
<br>pierre-edouard(dot)portier(at)insa-lyon.fr
<br>http://liris.cnrs.fr/pierre-edouard.portier
</address>

<h3> 
Résolution du <a href="http://en.wikipedia.org/wiki/Sudoku">Sudoku</a>.
<br> L'exemple provient du livre de 
<a href="http://en.wikipedia.org/wiki/Richard_Bird_%28computer_scientist%29">Richard Bird</a> :
<a href="http://www.cambridge.org/us/academic/subjects/computer-science/programming-languages-and-applied-logic/thinking-functionally-haskell">
Thinking Functionally with Haskell</a>.

<pre><font color=green>

> import System.Environment (getArgs)
> import Control.Monad.Par

</pre

<hr><font color=0>
On définit une grille comme étant une matrice de caractères.

<pre><font color=green>

> type Matrix a = [Row a]
> type Row a = [a]
> type Grid = Matrix Digit
> type Digit = Char
>
> digits :: [Char]
> digits = ['1' .. '9']
>
> blank :: Digit -> Bool
> blank = (== '0')

</pre>

<hr><font color=0>
Comme pour la résolution du Countdown, on commence par développer une 
spécification exécutable sous forme d'une solution combinatoirement coûteuse.
<br>Dans un second temps, on essaie de transformer cette spécification en un 
programme efficace.
<br>Ainsi, à partir d'une grille incomplète initiale, il s'agit de générer 
toutes les grilles possibles puis de filtrer les grilles qui sont solution.

<pre id="solve"><font color=green>

> solve :: Grid -> [Grid]
> solve = filter valid . completions
> 
> completions :: Grid -> [Grid]
> valid :: Grid -> Bool

</pre>

<hr><font color=0>
Pour <font color=red><code>completions</code><font color=0>, on propose 
d'utiliser une stratégie assez classique :<ul> 
<li> D'abord remplacer le contenu de chaque case inconnue par l'ensemble
des valeurs que la case pourrait prendre 
(résister pour le moment à tout effort d'optimisation...)
<li> Puis exploser cette matrice de choix en un choix de matrices
</ul>

<pre><font color=green>

> completions = expand . choices
>
> choices :: Grid -> Matrix [Digit]
> expand :: Matrix [Digit] -> [Grid]

</pre>

<hr><font color=0>
Une version naïve de la fonction <font color=red><code>choices</code>
<font color=0>est simple à proposer :

<pre><font color=green>

> choices = map (map choice)
> choice d = if blank d then digits else [d]

</pre>

<hr><font color=0>
La fonction expand est un cas particulier du produit cartésien généralisé sur 
les listes :
<pre><font color=red>

cp [[1,2,3],[2],[1,3]] ===
[[1,2,1],[1,2,3],[2,2,1],[2,2,3],
 [3,2,1],[3,2,3]]

cp [[2],[1,3]] ===
[[2,1],[2,3]]

</pre>

<pre><font color=green>

> cp :: [[a]] -> [[a]]
> cp [] = [[]]
> cp (xs:xss) = [x:ys|x<-xs, ys<-yss]
>               where yss = cp xss

</pre>

<font color=0>
Pourquoi <font color=red><code>cp [] = []</code><font color=0> ne serait pas 
correct ?

<hr><font color=0>
Comme définir <font color=red><code>expand</code><font color=0> en fonction 
de cp ?

<pre><font color=green>

> expand = cp . map cp

</pre>

<hr><font color=0>
Une matrice est valide si<ul>
<li>aucune ligne ne contient de doublons
<li>aucune colonne ne contient de doublons
<li>aucune boîte 3x3 ne contient de doublons
</ul>

<pre><font color=green>

> --valid :: Grid -> Bool
> valid g = all nodups (rows g) &&
>           all nodups (cols g) &&
>           all nodups (boxs g)
>
> nodups :: (Eq a) => [a] -> Bool
> nodups [] = True
> nodups (x:xs) = all (/=x) xs && nodups xs

</pre>

<font color=0>
Quelle est la complexité de nodups ?
<br>Comment pourrait-on réduire cette complexité ?
<br>Est-ce intéressant de le faire ?

<hr><font color=0>
Définir les fonctions <font color=red><code>rows</code><font color=0> et 
<font color=red><code>cols</code><font color=0>.
<br>Essayer de définir <font color=red><code>cols</code><font color=0> sans 
utiliser la fonction <font color=red><code>transpose</code><font color=0> 
(i.e. re-définir <font color=red><code>transpose</code><font color=0> est un 
bon exercice).

<pre><font color=green>

> rows :: Matrix a -> Matrix a
> rows = id
>
> cols :: Matrix a -> Matrix a
> cols [xs] = [[x] | x<-xs]
> cols (xs:xss) = zipWith (:) xs (cols xss)

</pre>

<hr><font color=0>
Pour la définition de <font color=red><code>boxs</code><font color=0>, vous 
pouvez commencer par définir <font color=red><code>group n</code><font color=0> 
qui découpe une liste en listes de taille n, et son inverse 
<font color=red><code>ungroup</code><font color=0>.

<pre><font color=green>

> group :: Int -> [a] -> [[a]]
> group _ [] = []
> group n xs = take n xs : group n (drop n xs)
>
> group3 = group 3
>
> ungroup :: [[a]] -> [a]
> ungroup = concat

</pre>

<hr><font color=0>
Définir <font color=red><code>boxs</code><font color=0> en fonction de group.

<pre><font color=red>
                             /       \
/       \                    |/     \|
|a b c d| group . map group  ||ab cd||
|e f g h|------------------->||ef gh||
|i j k l|                    |\     /|
|m n o p|                    |/     \|
\       /                    ||ij kl||
                             ||mn op||
                             |\     /|
                             \       /
                                 | map cols
                                 v
                             /       \
/       \ map ungroup .      |/     \|
|a b e f|     ungroup        ||ab ef||
|c d g h|<-------------------||cd gh||
|i j m n|                    |\     /|
|k l o p|                    |/     \|
\       /                    ||ij mn||
                             ||kl op||
                             |\     /|
                             \       /
</pre>

<pre><font color=green>

> boxs :: Matrix a -> Matrix a
> boxs = map ungroup . ungroup .
>        map cols .
>        group3 . map group3

</pre>

<hr><font color=0>
En utilisant, entre autres, les deux lois fonctorielles (LFONC) : 
<br><pre id="LFONC"><font color=blue>map id = id
map f . map g = map (f . g)
</pre>
<font color=0>montrer que :
<br><pre><font color=blue>boxs . boxs = id</pre>
<font color=0>On a de même :
<br><pre><font color=blue>rows . rows = id
cols . cols = id</pre>
<font color=0>Solution :
<br><pre><font color=blue>boxs . boxs
<font color=chocolate>= {par définition de boxs}<font color=blue>
map ungroup . ungroup . map cols . group . map group .
map ungroup . ungroup . map cols . group . map group
<font color=chocolate>= { map group . map ungroup = id
    car group . ungroup = id et par application de
    la première loi fonctorielle }<font color=blue>
map ungroup . ungroup . map cols . group .
ungroup . map cols . group . map group
<font color=chocolate>= { group . ungroup = id }<font color=blue>
map ungroup . ungroup . map cols .
map cols . group . map group
<font color=chocolate>= { map cols . map cols = map (cols . cols) =
    map id = id }<font color=blue>
map ungroup . ungroup .
group . map group
<font color=chocolate>= { ungroup . group = id }<font color=blue>
map ungroup .
map group
<font color=chocolate>= { ungroup . group = id et seconde loi fonctorielle }<font color=blue>
id</pre>

<hr><font color=0>
On se donne également les lois (LNAT) :
<br><pre id="LNAT"><font color=blue>map rows . expand = expand . rows
map cols . expand = expand . cols
map boxs . expand = expand . boxs
map (map f) . cp = cp . map (map f)</pre>
<font color=0>Ces lois appartiennent à la classe des lois 
dites de "naturalité". C'est-à-dire par exemple que 
<font color=red><code>cp</code><font color=0> est une fonction "naturelle" qui 
s'applique à un conteneur de type 
<font color=red><code>[[a]]</code><font color=0> et ne modifie ni la forme 
du conteneur ni les données contenues, mais réordonne, duplique, etc. 
(c'est pourquoi son type est polymorphique).
<br><font color=0>On pourra aussi utiliser (LFILCP) :
<br><pre id="LFILCP"><font color=blue>filter (all p) . cp = cp . map (filter p)</pre>
<font color=0>Que signifie cette dernière loi ?

<hr><font color=0>
La première proposition d'optimisation consiste pour chaque boîte, ligne ou 
colonne d'une matrice de choix à supprimer les choix qui apparaissent aussi 
comme singletons :

<pre><font color=green>

> prune :: Matrix [Digit] -> Matrix [Digit]

</pre>

<font color=0>Tel que :
<br><pre><font color=blue>filter valid . expand = filter valid . expand . prune</pre>
<font color=0>Il s'agit maintenant de définir 
<font color=red><code>prune</code><font color=0>. Comment feriez-vous ?
<br>Nous allons essayer de <i>calculer</i> cette fonction !

<hr><font color=0>
Assez clairement, <font color=red><code>prune</code><font color=0> travaille 
ligne par ligne (ou colonne par colonne, ou boîte par boîte) :

<pre><font color=green>

> pruneRow :: Row [Digit] -> Row [Digit]

</pre>

<font color=0>Définir <font color=red><code>pruneRow</code><font color=0>.

<pre><font color=green>

> pruneRow r = map (remove singles) r
>              where singles = [d | [d] <- r]
> 
> remove :: [Digit] -> [Digit] -> [Digit]
> remove ds [x] = [x]
> remove ds xs  = filter (`notElem` ds) xs

</pre>

<hr><font color=0>
<font color=red><code>pruneRow</code><font color=0> respecte la propriété 
suivante :

<pre id="LPRUNE"><font color=blue>filter nodups . cp = filter nodups . cp . pruneRow (LPRUNE)</pre>

<hr><font color=0>
Nous utiliserons également la propriété suivante valable pour toute fonction 
involutive (i.e. <code><font color=blue>f . f = id</code><font color=0>) :

<pre id="LFILINV"><font color=blue>filter (p . f) = map f . filter p . map f (LFILINV)</pre>

<font color=0>Prouvons le :

<pre><font color=blue>map f . filter p . map f
<font color=chocolate>= {filter p . map f = map f . filter (p . f)}<font color=blue>
map f . map f . filter (p . f)
<font color=chocolate>= {LFONC et involutivité de f}<font color=blue>
filter (p . f)</pre>

<font color=0>Prouvons la loi utilisée dans le premier indice de la preuve 
précédente. Nous avons besoin d'une définition de filter :

<pre><font color=green>

> -- filter p = concat . map (test p)
> -- test p x = if p x then [x] else []</pre>

<pre><font color=blue>filter p . map f
<font color=chocolate>= {déf. filter}<font color=blue>
concat . map (test p) . map f
<font color=chocolate>= {LFONC}<font color=blue>
concat . map (test p . f)
<font color=chocolate>= {test p . f = map f . test (p . f)}<font color=blue>
concat . map (map f . test (p . f))
<font color=chocolate>= {LFONC}<font color=blue>
concat . map (map f) . map (test (p . f))
<font color=chocolate>= {naturalité de concat}<font color=blue>
map f . concat . map (test (p . f))
<font color=chocolate>= {déf. filter}<font color=blue>
map f . filter (p . f)</pre>

<font color=0>On a aussi facilement (par involutivité de f et LFONC) :

<pre><font color=blue>filter (p . f) . map f = map f . filter p</pre>

<hr><font color=0>
Rappelons que la stratégie de résolution (voir la fonction 
<a href="#solve">
<font color=red><code>solve</code><font color=0></a>) est basée sur l'expression 
<font color=red><code>filter valid . expand</code><font color=0> que l'on 
réécrit comme ci-dessous en utilisant la définition de 
<font color=red><code>valid</code><font color=0> :

<pre><font color=blue>filter valid . expand =
filter (all nodups . boxs) .
filter (all nodups . cols) .
filter (all nodups . rows) . expand</pre>

<font color=0>
L'idée est de faire apparaître l'expression 
<font color=red><code>filter nodups . cp</code><font color=0> afin de pouvoir 
utiliser la propriété <a href="#LPRUNE">(LPRUNE)</a> de 
<font color=red><code>pruneRow</code><font color=0>.
<br>Le problème est clairement symétrique pour 
<font color=red><code>boxs</code><font color=0>, 
<font color=red><code>cols</code><font color=0> et 
<font color=red><code>rows</code><font color=0>. La propriété commune utile de 
ces fonctions étant leur involutivité. On peut donc se concentrer 
par exemple sur le cas de <font color=red><code>rows</code><font color=0>.
<br>On utilisera les lois <a href="#LFILINV">LFILINV</a>,
<a href="#LNAT">LNAT</a>,
<a href="#LFILCP">LFILCP</a>,
<a href="#LFONC">LFONC</a> et
<a href="#LPRUNE">LPRUNE</a>.

<pre><font color=blue>filter (all nodups . rows) . expand
<font color=chocolate>= {LFILINV}<font color=blue> 
map rows . filter (all nodups) . map rows . expand
<font color=chocolate>= {LNAT}<font color=blue> 
map rows . filter (all nodups) . expand . rows
<font color=chocolate>= {Déf. expand}<font color=blue> 
map rows . filter (all nodups) . cp . map cp . rows
<font color=chocolate>= {LFILCP}<font color=blue> 
map rows . cp . map (filter nodups) . map cp . rows
<font color=chocolate>= {LFONC}<font color=blue> 
map rows . cp . map (filter nodups . cp) . rows
<font color=chocolate>= {LPRUNE}<font color=blue> 
map rows . cp . map (filter nodups . cp . pruneRow) . rows</pre>

<font color=0>
Maintenant que nous sommes parvenus à introduire 
<font color=red><code>pruneRow</code><font color=0>, il suffit de parcourir le 
chemin inverse pour retrouver une forme qui utilise 
<font color=red><code>expand</code><font color=0> (peu d'intelligence ici...) :

<pre><font color=blue>map rows . cp . map (filter nodups . cp . pruneRow) . rows
<font color=chocolate>= {LFONC}<font color=blue> 
map rows . cp . map (filter nodups) .
map (cp . pruneRow) . rows
<font color=chocolate>= {LFILCP}<font color=blue> 
map rows . filter (all nodups) . cp .
map (cp . pruneRow) . rows
<font color=chocolate>= {LFONC}<font color=blue> 
map rows . filter (all nodups) . 
cp . map cp . map pruneRow . rows
<font color=chocolate>= {Déf. expand}<font color=blue> 
map rows . filter (all nodups) . 
expand . map pruneRow . rows
<font color=chocolate>= {LFILINV}<font color=blue> 
filter (all nodups . rows) . map rows . 
expand . map pruneRow . rows
<font color=chocolate>= {LNAT}<font color=blue> 
filter (all nodups . rows) . expand . 
rows . map pruneRow . rows
<font color=chocolate>= {introduction de pruneBy}<font color=blue> 
filter (all nodups . rows) . expand . pruneBy rows</pre>

<font color=0>
Avec <font color=red><code>pruneBy</code><font color=0> définit par :

<pre><font color=green>

> pruneBy f = f . map pruneRow . f
>
> prune = pruneBy rows . pruneBy cols . pruneBy boxs

</pre>

<font color=0>
En utilisant la propriété (vraie lorsque les prédicats sont des fonctions 
totales) :
<pre><font color=blue>filter p . filter q = filter q . filter p</pre>
<font color=0>
On prouve la propriété recherchée :

<pre><font color=blue>filter valid . expand = 
filter valid . expand . prune</pre>

<hr><font color=0>
On peut maintenant écrire une nouvelle version plus optimisée du solveur :

<pre><font color=green>

> solve' = filter valid . expand . prune . choices

</pre>

<hr><font color=0>
On peut encore améliorer le solveur en répétant l'application de 
<font color=red><code>prune</code><font color=0> tant que le problème continue 
à se simplifier. Pour cela, écrire la fonction :<br>
<font color=red><code>many :: (Eq a) => (a -> a) -> a -> a</code><font color=0>

<pre><font color=green>

> solve'' = filter valid . expand . many prune . choices
>
> many :: (Eq a) => (a -> a) -> a -> a
> many f x = if x == y then x else many f y
>            where y = f x

</pre>

<hr><font color=0>
Après l'application de 
<font color=red><code>many prune . choices</code><font color=0> :
<ul>
<li>soit la matrice de choix de contient que des singletons et le problème est 
résolu ;
<li>soit la matrice de choix contient une liste vide, et il n'y a alors pas de 
solutions (quel sera alors le résultat de l'application de 
<font color=red><code>expand</code><font color=0> ?) ;
<li>soit la matrice ne contient pas de liste vide, et contient également au 
moins une entrée composée de deux choix ou plus.
</ul>
Dans ce dernier cas, plutôt que de construire immédiatement tous les choix de 
matrices, nous pouvons exploser une seule case qui contient plus d'un choix 
afin d'obtenir une liste de matrices de choix sur lesquelles le pruning pourra 
être à nouveau appliqué. 

<pre><font color=green>

> expand1 :: Matrix [Digit] -> [Matrix [Digit]]

</pre>

<font color=0>
<font color=red><code>expand1</code><font color=0> doit respecter la propriété 
suivante :

<pre><font color=blue>expand = concat . map expand . expand1</code></pre>

<hr><font color=0>
Nous avons besoin d'une fonction pour déterminer si une liste est un singleton :

<pre><font color=green>

> single :: [a] -> Bool
> single [_] = True
> single  _  = False

</pre>

<hr><font color=0>
Quelle cellule pivot choisir lors de l'application de 
<font color=red><code>expand1</code><font color=0> ? Il faut une cellule qui 
ait plus d'un choix. Il semble pertinent de choisir une cellule avec le moins 
de choix possible.

<pre><font color=green>

> counts :: Matrix [Digit] -> [Int]
> counts = filter (/= 1) . map length . concat
>
> -- minimum :: Ord a => [a] -> a
> -- minimum [x] = x
> -- minimum (x:xs) = x `min` minimum xs
>
> -- expand1 :: Matrix [Digit] -> [Matrix [Digit]]
> expand1 rows =
>   [rows1 ++ [row1 ++ [c]:row2] ++ rows2 | c <- cs]
>   where
>   (rows1,row:rows2) = break (any smallest) rows
>   (row1,cs:row2)    = break smallest row
>   smallest cs       = length cs == n
>   n                 = minimum (counts rows)

</pre>

<font color=0>
<ul>
<li>(Q1) Que retourne <font color=red><code>expand1</code><font color=0> lorsque la 
matrice de choix contient un choix vide ?
<li>(Q2) Que retourne <font color=red><code>expand1</code><font color=0> lorsque la 
matrice de choix ne contient que des singletons ?
</ul>

<ul>
<li>(R1) Une liste vide.
<li>(R2) Une erreur (car <font color=red><code>minimum []</code><font color=0> 
retourne une erreur).
</ul>

<br>Il faut donc pouvoir tester si une matrice de choix ne contient que des 
singletons (on dira qu'elle est <i>complète</i>) :

<pre><font color=green>

> complete :: Matrix [Digit] -> Bool
> complete = all (all single)

</pre>

<hr><font color=0>
Par ailleurs, on dira qu'une matrice de choix est <i>sûre</i> si le choix 
de matrices qu'elle implique peut contenir une matrice valide :

<pre><font color=green>

> safe :: Matrix [Digit] -> Bool
> safe m = all ok (rows m) &&
>          all ok (cols m) &&
>          all ok (boxs m)
>
> ok row = nodups [x | [x] <- row]

</pre>

<font color=0>
Ainsi, une matrice de choix complète et sûre correspond à un choix d'exactement 
une matrice qui est solution du problème :

<pre><font color=green>

> extract :: Matrix [Digit] -> Grid
> extract = map (map head)

</pre>

<font color=0>
Pour une matrice complète et sûre, on a la propriété :

<pre><font color=blue>filter valid (expand m) = [extract m]</pre>

<font color=0>
Pour une matrice incomplète et sûre, on a la propriété :

<pre><font color=blue>filter valid . expand 
<font color=chocolate>= {Propriété de expand1}<font color=blue> 
filter valid . concat . map expand . expand1 
<font color=chocolate>= {filter p . concat = concat . map (filter p)}<font color=blue> 
concat . map (filter valid) . map expand . expand1
<font color=chocolate>= {LFONC}<font color=blue> 
concat . map (filter valid . expand) . expand1
<font color=chocolate>= {loi prouvée plus haut}<font color=blue> 
concat . map (filter valid . expand . prune) . expand1</pre>

<font color=0>
Ainsi, pour une matrice incomplète et sûre, en posant 
<font color=red><code>search = filter valid . expand . prune</code><font color=0>, 
on obtient :

<pre><font color=blue>search = concat . map search . expand1</pre>

<font color=0>
Ce qui nous permet de proposer une nouvelle version du solveur :

<pre><font color=green>

> solve''' = search . choices
>
> search m
>  | not (safe pm) = []
>  | complete pm   = [extract pm]
>  | otherwise     = concat $ map search $ expand1 pm
>  where pm = prune m

</pre>

<hr><font color=0>
Afin de tester notre solveur, nous allons utiliser un 
<a href="http://staffhome.ecm.uwa.edu.au/~00013890/sudoku17">jeu de données</a> 
de plus de 49000 grilles de sudoku chacune ayant exactement 17 indices (ce qui
<a href="http://staffhome.ecm.uwa.edu.au/~00013890/sudokumin.php">semble
être</a> le plus petit nombre d'indices nécessaire pour qu'une grille puisse
être résolue d'une manière unique).
<br>Voici pour exemple la première ligne du fichier 'sudoku17' 
(<a href="./dataset/sudoku17">copie locale</a>) :

<pre><font
color=red>000000010400000000020000000000050407008000300001090000300400200050100000000806000</pre>

<font color=0>
La fonction <font color=red><code>s2g</code><font color=0> convertit une telle
ligne en grille :

<pre><font color=green>

> s2g :: String -> Grid
> s2g = group 9

</pre>

<font color=0>
Pourquoi le programme suivant semble-t-il résoudre les >49000 grilles de sudoku
en moins de 1s (même avec <font color=red><code>solve'''</code><font color=0>) ?! 
<br>(compilé avec :<br>
<font color=red><code>$ ghc -o sudoku -O sudoku.lhs</code><font color=0><br> 
et exécuté avec :<br>
<font color=red><code>$ ./sudoku dataset/sudoku17 +RTS -s</code><font color=0>)

<pre><font color=green>

> --main :: IO ()
> --main = do
> -- [f] <- getArgs
> -- gs <- fmap lines $ readFile f
> -- print $ (length $ map (solve''' . s2g) gs) == (length gs)

</pre>

<font color=0>
A cause de l'évaluation paresseuse (<i>lazy evaluation</i>) !
<br>Ajouter par exemple un filtre forcera l'évaluation :

<pre><font color=green>

> --main :: IO ()
> --main = do
> -- [f] <- getArgs
> -- gs <- fmap lines $ readFile f
> -- print $ (length $ filter single $ map (solve''' . s2g) $ take 20 gs) == 20

</pre>

<pre><font color=red>
A2PF_2014/sudoku $ ./sudoku dataset/sudoku17 +RTS -s
True
  37,960,268,984 bytes allocated in the heap
   1,728,353,264 bytes copied during GC
         293,648 bytes maximum residency (484 sample(s))
          47,912 bytes maximum slop
               2 MB total memory in use (0 MB lost due to fragmentation)

                                    Tot time (elapsed)  Avg pause  Max pause
  Gen  0     72477 colls,     0 par    1.89s    1.88s     0.0000s    0.0001s
  Gen  1       484 colls,     0 par    0.08s    0.08s     0.0002s    0.0003s

  INIT    time    0.00s  (  0.00s elapsed)
  MUT     time   11.02s  ( 11.01s elapsed)
  GC      time    1.96s  (  1.96s elapsed)
  EXIT    time    0.00s  (  0.00s elapsed)
  Total   time   12.98s  ( 12.97s elapsed)

  %GC     time      15.1%  (15.1% elapsed)

  Alloc rate    3,445,356,437 bytes per MUT second

  Productivity  84.9% of total user, 84.9% of total elapsed
</pre>

<hr><font color=0>
J'ai 4 coeurs sur ma machine 
(<font color=red><code>cat /proc/cpuinfo | grep processor | wc -l</code><font
color=0>). 
<br>Comment en profiter ? Par exemple grâce à la monade 
<font color=red><code>Par</code><font color=0> !<br>
<font color=red><code>$ cabal install monad-par</code><font color=0><br>
<font color=red><code>$ cabal install threadscope</code><font color=0><br>
Voir le livre du créateur de cette monade <a
href="http://community.haskell.org/~simonmar/">Simon Marlow</a> : 
<a href="http://chimera.labs.oreilly.com/books/1230000000929/index.html">Parallel 
and Concurrent Programming in Haskell</a>

<pre><font color=blue>
data Par a
instance Monad Par

runPar :: Par a -> a

fork :: Par () -> Par ()

data IVar a

new :: Par (IVar a)
get :: IVar a -> Par a
put :: NFData a => IVar a -> a -> Par ()

main = do
 v1 <- new
 v2 <- new
 fork $ put v1 (f x)
 fork $ put v2 (g x)
 get v1
 get v2
 return (h v1 v2)
</pre>

<pre><font color=chocolate>
 _____         _____
|     |       |     |
| f x |       | g x |
|_____|       |_____|
      \       /
       \     /
      v1\   /v2
      ___v_v___
     |         |
     | h v1 v2 |
     |_________|

</pre>

<pre><font color=green>

> main :: IO ()
> main = do
>  [f] <- getArgs
>  gs <- fmap lines $ readFile f
>
>  let nbg = 20
>  let gs' = take nbg gs
>  let (as,bs) = splitAt (length gs' `div` 2) gs'
>
>  print $ (==nbg) $ length $ filter single $ runPar $ do 
>   i1 <- new
>   i2 <- new
>   fork $ put i1 (map (solve''' . s2g) as)
>   fork $ put i2 (map (solve''' . s2g) bs)
>   as' <- get i1
>   bs' <- get i2
>   return (as' ++ bs')

</pre>

<font color=0>
Pour bénéficier du parallélisme il faut compiler le programme ainsi :<br>
<font color=red><code>$ ghc -o sudoku -O -threaded sudoku.lhs</code><font color=0>
<br>Et l'exécuter ainsi :<br>
<font color=red><code>$ ./sudoku dataset/sudoku17 +RTS -s -N2</code><font color=0>

<pre><font color=red>Total   time   14.88s  (  7.57s elapsed)</pre>

<hr><font color=0>
Pour mieux se rendre compte de l'occupation des processeurs nous allons utiliser 
<font color=red><code>threadscope</code><font color=0> :

<pre><font color=red>$ ghc -o sudoku -O -threaded -eventlog sudoku.lhs
$ ./sudoku dataset/sudoku17 +RTS -N2 -l
$ threadscope sudoku.eventlog</pre>

<img src="threadscope.png"/>
