Slide 1

Slide 1 text

Coding dojo A simple Sudoku solver in Haskell Bucharest FP #027 1 / 21

Slide 2

Slide 2 text

Welcome Implement a Sudoku solver as described in (Bird, 2006, 2010, 2014) 2 / 21

Slide 3

Slide 3 text

Welcome Implement a Sudoku solver as described in (Bird, 2006, 2010, 2014) Twelve short functions: 5 easy ( ) 4 medium ( ) 3 challenging ( ) 2 / 21

Slide 4

Slide 4 text

Welcome Implement a Sudoku solver as described in (Bird, 2006, 2010, 2014) Twelve short functions: 5 easy ( ) 4 medium ( ) 3 challenging ( ) Programming techniques: Top-down programming / wishful thinking 2 / 21

Slide 5

Slide 5 text

Welcome Implement a Sudoku solver as described in (Bird, 2006, 2010, 2014) Twelve short functions: 5 easy ( ) 4 medium ( ) 3 challenging ( ) Programming techniques: Top-down programming / wishful thinking Wholemeal programming (prevents a disease called “indexitis”) 2 / 21

Slide 6

Slide 6 text

Welcome Implement a Sudoku solver as described in (Bird, 2006, 2010, 2014) Twelve short functions: 5 easy ( ) 4 medium ( ) 3 challenging ( ) Programming techniques: Top-down programming / wishful thinking Wholemeal programming (prevents a disease called “indexitis”) Higher-order functions, recursion, point-free style 2 / 21

Slide 7

Slide 7 text

How to play Sudoku N = 2 2 4 1 3 4 2 1 3 N = 3 2 5 1 9 8 2 3 6 3 6 7 1 6 5 4 1 9 2 7 9 3 8 2 8 4 7 1 9 7 6 Fill in the empty cells with digits 1 to N2 such that every row, column and N × N box contains the digits 1 to N2. 3 / 21

Slide 8

Slide 8 text

How to play Sudoku N = 2 2 4 1 3 4 2 1 3 3 1 4 2 3 1 2 4 N = 3 2 5 1 9 8 2 3 6 3 6 7 1 6 5 4 1 9 2 7 9 3 8 2 8 4 7 1 9 7 6 4 6 7 3 8 5 7 9 1 4 1 9 4 8 2 5 9 7 3 8 5 2 4 3 7 2 6 8 6 8 1 4 9 5 3 7 4 6 2 5 1 6 5 1 9 3 3 8 5 4 2 Fill in the empty cells with digits 1 to N2 such that every row, column and N × N box contains the digits 1 to N2. 3 / 21

Slide 9

Slide 9 text

Data types a a · · · a a a · · · a . . . . . . ... . . . a a · · · a Matrix a Row a Row a Row a type Matrix a = [Row a] type Row a = [a] 4 / 21

Slide 10

Slide 10 text

Data types a a · · · a a a · · · a . . . . . . ... . . . a a · · · a Matrix a Row a Row a Row a 0 3 0 1 1 0 3 2 3 0 1 0 0 1 0 3 Grid type Matrix a = [Row a] type Row a = [a] type Grid = Matrix Digit type Digit = Int 4 / 21

Slide 11

Slide 11 text

Data types a a · · · a a a · · · a . . . . . . ... . . . a a · · · a Matrix a Row a Row a Row a 0 3 0 1 1 0 3 2 3 0 1 0 0 1 0 3 Grid type Matrix a = [Row a] type Row a = [a] type Grid = Matrix Digit type Digit = Int We assume that digit zero indicates an empty cell: isEmpty :: Digit -> Bool isEmpty 0 = True isEmpty _ = False 4 / 21

Slide 12

Slide 12 text

Exercise 1: solve [ ] solve :: Grid -> [Grid] solve = undefined 5 / 21

Slide 13

Slide 13 text

Exercise 1: solve [ ] solve :: Grid -> [Grid] solve = undefined Given: -- Generates grids by replacing empty entries -- with all possible choices completions :: Grid -> [Grid] 5 / 21

Slide 14

Slide 14 text

Exercise 1: solve [ ] solve :: Grid -> [Grid] solve = undefined Given: -- Generates grids by replacing empty entries -- with all possible choices completions :: Grid -> [Grid] -- Tests whether a grid is a valid solution: -- has different entries in each row, column and box valid :: Grid -> Bool 5 / 21

Slide 15

Slide 15 text

Exercise 1: solve [ ] solve :: Grid -> [Grid] solve = undefined Given: -- Generates grids by replacing empty entries -- with all possible choices completions :: Grid -> [Grid] -- Tests whether a grid is a valid solution: -- has different entries in each row, column and box valid :: Grid -> Bool Example: 0 3 0 1 1 0 3 2 3 0 1 0 0 1 0 3 Grid 2 3 4 1 1 4 3 2 3 2 1 4 4 1 2 3 Grid 5 / 21

Slide 16

Slide 16 text

Exercise 2: completions [ ] completions :: Grid -> [Grid] completions = undefined 6 / 21

Slide 17

Slide 17 text

Exercise 2: completions [ ] completions :: Grid -> [Grid] completions = undefined Given: -- Replaces empty entries with all possible choices -- for that entry choices :: Grid -> Matrix [Digit] 6 / 21

Slide 18

Slide 18 text

Exercise 2: completions [ ] completions :: Grid -> [Grid] completions = undefined Given: -- Replaces empty entries with all possible choices -- for that entry choices :: Grid -> Matrix [Digit] -- Generates a list of all possible boards -- from a given matrix of choices expand :: Matrix [Digit] -> [Grid] 6 / 21

Slide 19

Slide 19 text

Exercise 2: completions [ ] completions :: Grid -> [Grid] completions = undefined Given: -- Replaces empty entries with all possible choices -- for that entry choices :: Grid -> Matrix [Digit] -- Generates a list of all possible boards -- from a given matrix of choices expand :: Matrix [Digit] -> [Grid] Example: 0 3 0 1 1 0 3 2 3 0 1 0 0 1 0 3 Grid 1 3 1 1 1 1 3 2 3 1 1 1 1 1 1 3 Grid 1 3 1 1 1 1 3 2 3 1 1 1 1 1 2 3 Grid · · · 4 3 4 1 1 4 3 2 3 4 1 4 4 1 4 3 Grid 6 / 21

Slide 20

Slide 20 text

Exercise 3: choices [ ] choices :: Grid -> Matrix [Digit] choices = undefined 7 / 21

Slide 21

Slide 21 text

Exercise 3: choices [ ] choices :: Grid -> Matrix [Digit] choices = undefined Example: 0 3 0 1 1 0 3 2 3 0 1 0 0 1 0 3 Matrix Digit 1 2 3 1 2 1 3 4 0 3 4 0 1 1 2 3 2 0 3 4 0 0 3 1 2 1 1 2 0 3 4 0 3 4 1 2 1 1 2 3 3 4 0 3 4 0 Matrix [Digit] 7 / 21

Slide 22

Slide 22 text

Exercise 3: choices [ ] choices :: Grid -> Matrix [Digit] choices = undefined Example: 0 3 0 1 1 0 3 2 3 0 1 0 0 1 0 3 Matrix Digit 1 2 3 1 2 1 3 4 0 3 4 0 1 1 2 3 2 0 3 4 0 0 3 1 2 1 1 2 0 3 4 0 3 4 1 2 1 1 2 3 3 4 0 3 4 0 Matrix [Digit] Hint: Define a helper function choice :: Digit -> [Digit] 7 / 21

Slide 23

Slide 23 text

Exercise 4: expand [ ] expand :: Matrix [Digit] -> [Grid] expand = undefined 8 / 21

Slide 24

Slide 24 text

Exercise 4: expand [ ] expand :: Matrix [Digit] -> [Grid] expand = undefined Given: -- Computes the cartesian product of a list of lists cp :: [[a]] -> [[a]] 8 / 21

Slide 25

Slide 25 text

Exercise 4: expand [ ] expand :: Matrix [Digit] -> [Grid] expand = undefined Given: -- Computes the cartesian product of a list of lists cp :: [[a]] -> [[a]] Example: 1 2 3 1 2 1 3 4 0 3 4 0 1 1 2 3 2 0 3 4 0 0 3 1 2 1 1 2 0 3 4 0 3 4 1 2 1 1 2 3 3 4 0 3 4 0 Matrix [Digit] 1 3 1 1 1 1 3 2 3 1 1 1 1 1 1 3 1 3 1 1 1 1 3 2 3 1 1 1 1 1 2 3 · · · 4 3 4 1 1 4 3 2 3 4 1 4 4 1 4 3 8 / 21

Slide 26

Slide 26 text

Exercise 4: expand [ ] expand :: Matrix [Digit] -> [Grid] expand = undefined Given: -- Computes the cartesian product of a list of lists cp :: [[a]] -> [[a]] Example: 1 2 3 1 2 1 3 4 0 3 4 0 1 1 2 3 2 0 3 4 0 0 3 1 2 1 1 2 0 3 4 0 3 4 1 2 1 1 2 3 3 4 0 3 4 0 Matrix [Digit] 1 3 1 1 1 1 3 2 3 1 1 1 1 1 1 3 1 3 1 1 1 1 3 2 3 1 1 1 1 1 2 3 · · · 4 3 4 1 1 4 3 2 3 4 1 4 4 1 4 3 Hints: First generate all combinations across each row Then combine those generated combinations 8 / 21

Slide 27

Slide 27 text

Exercise 5: cp [ ] cp :: [[a]] -> [[a]] cp = undefined 9 / 21

Slide 28

Slide 28 text

Exercise 5: cp [ ] cp :: [[a]] -> [[a]] cp = undefined Example: cp [[1, 2], [3, 4]] = [[1, 3], [1, 4], [2, 3], [2, 4]] 9 / 21

Slide 29

Slide 29 text

Exercise 5: cp [ ] cp :: [[a]] -> [[a]] cp = undefined Example: cp [[1, 2], [3, 4]] = [[1, 3], [1, 4], [2, 3], [2, 4]] Hint: Use recursion, with the following base case cp [] = [[]] 9 / 21

Slide 30

Slide 30 text

Exercise 5: cp [ ] cp :: [[a]] -> [[a]] cp = undefined Example: cp [[1, 2], [3, 4]] = [[1, 3], [1, 4], [2, 3], [2, 4]] Hint: Use recursion, with the following base case cp [] = [[]] xss 1 2 3 4 5 9 / 21

Slide 31

Slide 31 text

Exercise 5: cp [ ] cp :: [[a]] -> [[a]] cp = undefined Example: cp [[1, 2], [3, 4]] = [[1, 3], [1, 4], [2, 3], [2, 4]] Hint: Use recursion, with the following base case cp [] = [[]] xss' 1 2 xs 3 4 5 9 / 21

Slide 32

Slide 32 text

Exercise 5: cp [ ] cp :: [[a]] -> [[a]] cp = undefined Example: cp [[1, 2], [3, 4]] = [[1, 3], [1, 4], [2, 3], [2, 4]] Hint: Use recursion, with the following base case cp [] = [[]] xss' cp xss' 1 2 xs 3 4 5 3 4 3 5 9 / 21

Slide 33

Slide 33 text

Exercise 5: cp [ ] cp :: [[a]] -> [[a]] cp = undefined Example: cp [[1, 2], [3, 4]] = [[1, 3], [1, 4], [2, 3], [2, 4]] Hint: Use recursion, with the following base case cp [] = [[]] xss' cp xss' 1 2 xs 3 4 5 3 4 3 5 1 3 4 1 3 5 1 9 / 21

Slide 34

Slide 34 text

Exercise 5: cp [ ] cp :: [[a]] -> [[a]] cp = undefined Example: cp [[1, 2], [3, 4]] = [[1, 3], [1, 4], [2, 3], [2, 4]] Hint: Use recursion, with the following base case cp [] = [[]] xss' cp xss' 1 2 xs 3 4 5 3 4 3 5 1 3 4 1 3 5 1 2 3 4 2 3 5 2 9 / 21

Slide 35

Slide 35 text

Exercise 5: cp [ ] cp :: [[a]] -> [[a]] cp = undefined Example: cp [[1, 2], [3, 4]] = [[1, 3], [1, 4], [2, 3], [2, 4]] Hint: Use recursion, with the following base case cp [] = [[]] xss' cp xss' 1 2 xs 3 4 5 3 4 3 5 1 3 4 1 3 5 1 2 3 4 2 3 5 2 ++ 9 / 21

Slide 36

Slide 36 text

Intermezzo 0 3 0 1 1 0 3 2 3 0 1 0 0 1 0 3 1 2 3 1 2 1 3 4 0 3 4 0 1 1 2 3 2 0 3 4 0 0 3 1 2 1 1 2 0 3 4 0 3 4 1 2 1 1 2 3 3 4 0 3 4 0 1 3 1 1 1 1 3 2 3 1 1 1 1 1 1 3 · · · 4 3 4 1 1 4 3 2 3 4 1 4 4 1 4 3 Finished implementing the completions function 10 / 21

Slide 37

Slide 37 text

Intermezzo 0 3 0 1 1 0 3 2 3 0 1 0 0 1 0 3 1 2 3 1 2 1 3 4 0 3 4 0 1 1 2 3 2 0 3 4 0 0 3 1 2 1 1 2 0 3 4 0 3 4 1 2 1 1 2 3 3 4 0 3 4 0 1 3 1 1 1 1 3 2 3 1 1 1 1 1 1 3 · · · 4 3 4 1 1 4 3 2 3 4 1 4 4 1 4 3 False False Finished implementing the completions function Next, the valid function: test whether a grid is a valid solution 10 / 21

Slide 38

Slide 38 text

Exercise 6: valid [ ] valid :: Grid -> Bool valid = undefined 11 / 21

Slide 39

Slide 39 text

Exercise 6: valid [ ] valid :: Grid -> Bool valid = undefined Given: -- Checks that a list contains no duplicates nodups :: [a] -> Bool 11 / 21

Slide 40

Slide 40 text

Exercise 6: valid [ ] valid :: Grid -> Bool valid = undefined Given: -- Checks that a list contains no duplicates nodups :: [a] -> Bool -- Re-orders the values from a matrix's rows, columns -- or boxes to appear along the rows rows :: Matrix a -> Matrix a cols :: Matrix a -> Matrix a boxs :: Matrix a -> Matrix a 11 / 21

Slide 41

Slide 41 text

Exercise 6: valid [ ] valid :: Grid -> Bool valid = undefined Given: -- Checks that a list contains no duplicates nodups :: [a] -> Bool -- Re-orders the values from a matrix's rows, columns -- or boxes to appear along the rows rows :: Matrix a -> Matrix a cols :: Matrix a -> Matrix a boxs :: Matrix a -> Matrix a Examples: 1 3 1 1 1 1 3 2 3 1 1 1 1 1 1 3 False 2 3 4 1 1 4 3 2 3 2 1 4 4 1 2 3 True 11 / 21

Slide 42

Slide 42 text

Exercise 7: nodups [ ] nodups :: [a] -> Bool nodups = undefined 12 / 21

Slide 43

Slide 43 text

Exercise 7: nodups [ ] nodups :: [a] -> Bool nodups = undefined Examples: nodups [] = True nodups [1, 2, 3] = True nodups [1, 2, 1] = False 12 / 21

Slide 44

Slide 44 text

Exercise 7: nodups [ ] nodups :: [a] -> Bool nodups = undefined Examples: nodups [] = True nodups [1, 2, 3] = True nodups [1, 2, 1] = False Hints: Use recursion Use Hoogle to find a function of type a -> [a] -> Bool 12 / 21

Slide 45

Slide 45 text

Exercise 8: rows [ ] rows :: Matrix a -> Matrix a 13 / 21

Slide 46

Slide 46 text

Exercise 8: rows [ ] rows :: Matrix a -> Matrix a Example: 2 3 4 1 1 4 3 2 3 2 1 4 4 1 2 3 2 3 4 1 1 4 3 2 3 2 1 4 4 1 2 3 13 / 21

Slide 47

Slide 47 text

Exercise 9: cols [ ] cols :: Matrix a -> Matrix a 14 / 21

Slide 48

Slide 48 text

Exercise 9: cols [ ] cols :: Matrix a -> Matrix a Example: 2 3 4 1 1 4 3 2 3 2 1 4 4 1 2 3 2 1 3 4 3 4 2 1 4 3 1 2 1 2 4 3 14 / 21

Slide 49

Slide 49 text

Exercise 9: cols [ ] cols :: Matrix a -> Matrix a Example: 2 3 4 1 1 4 3 2 3 2 1 4 4 1 2 3 2 1 3 4 3 4 2 1 4 3 1 2 1 2 4 3 Hints: Use recursion Define a case for a one-row matrix; example: cols [[1,2,3,4]] = [[1],[2],[3],[4]] For the recursive case, use the zipWith function: zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] 14 / 21

Slide 50

Slide 50 text

Exercise 10: boxs [ ] boxs :: Matrix a -> Matrix a 15 / 21

Slide 51

Slide 51 text

Exercise 10: boxs [ ] boxs :: Matrix a -> Matrix a Given: -- Groups a list into lists of length two group :: [a] -> [[a]] 15 / 21

Slide 52

Slide 52 text

Exercise 10: boxs [ ] boxs :: Matrix a -> Matrix a Given: -- Groups a list into lists of length two group :: [a] -> [[a]] -- Flattens a nested list of elements ungroup :: [[a]] -> [a] 15 / 21

Slide 53

Slide 53 text

Exercise 10: boxs [ ] boxs :: Matrix a -> Matrix a Given: -- Groups a list into lists of length two group :: [a] -> [[a]] -- Flattens a nested list of elements ungroup :: [[a]] -> [a] Example: 2 3 4 1 1 4 3 2 3 2 1 4 4 1 2 3 2 3 1 4 4 1 3 2 3 2 4 1 1 4 2 3 15 / 21

Slide 54

Slide 54 text

Exercise 10: boxs [ ] boxs :: Matrix a -> Matrix a Given: -- Groups a list into lists of length two group :: [a] -> [[a]] -- Flattens a nested list of elements ungroup :: [[a]] -> [a] Example: 2 3 4 1 1 4 3 2 3 2 1 4 4 1 2 3 2 3 1 4 4 1 3 2 3 2 4 1 1 4 2 3 Hints: Use the previously defined cols function Chain five transformations (see next slide) 15 / 21

Slide 55

Slide 55 text

Exercise 10: boxs [ ] 2 3 4 1 1 4 3 2 3 2 1 4 4 1 2 3 16 / 21

Slide 56

Slide 56 text

Exercise 10: boxs [ ] 2 3 4 1 1 4 3 2 3 2 1 4 4 1 2 3 2 3 4 1 1 4 3 2 3 2 1 4 4 1 2 3 16 / 21

Slide 57

Slide 57 text

Exercise 10: boxs [ ] 2 3 4 1 1 4 3 2 3 2 1 4 4 1 2 3 2 3 4 1 1 4 3 2 3 2 1 4 4 1 2 3 2 3 4 1 1 4 3 2 3 2 1 4 4 1 2 3 16 / 21

Slide 58

Slide 58 text

Exercise 10: boxs [ ] 2 3 4 1 1 4 3 2 3 2 1 4 4 1 2 3 2 3 4 1 1 4 3 2 3 2 1 4 4 1 2 3 2 3 4 1 1 4 3 2 3 2 1 4 4 1 2 3 2 3 1 4 4 1 3 2 3 2 4 1 1 4 2 3 16 / 21

Slide 59

Slide 59 text

Exercise 10: boxs [ ] 2 3 4 1 1 4 3 2 3 2 1 4 4 1 2 3 2 3 4 1 1 4 3 2 3 2 1 4 4 1 2 3 2 3 4 1 1 4 3 2 3 2 1 4 4 1 2 3 2 3 1 4 4 1 3 2 3 2 4 1 1 4 2 3 2 3 1 4 4 1 3 2 3 2 4 1 1 4 2 3 16 / 21

Slide 60

Slide 60 text

Exercise 10: boxs [ ] 2 3 4 1 1 4 3 2 3 2 1 4 4 1 2 3 2 3 4 1 1 4 3 2 3 2 1 4 4 1 2 3 2 3 4 1 1 4 3 2 3 2 1 4 4 1 2 3 2 3 1 4 4 1 3 2 3 2 4 1 1 4 2 3 2 3 1 4 4 1 3 2 3 2 4 1 1 4 2 3 2 3 1 4 4 1 3 2 3 2 4 1 1 4 2 3 16 / 21

Slide 61

Slide 61 text

Exercise 11: group [ ] group :: [a] -> [[a]] 17 / 21

Slide 62

Slide 62 text

Exercise 11: group [ ] group :: [a] -> [[a]] Example: group [1,2,3,4] = [[1,2],[3,4]] 17 / 21

Slide 63

Slide 63 text

Exercise 12: ungroup [ ] ungroup :: [[a]] -> [a] 18 / 21

Slide 64

Slide 64 text

Exercise 12: ungroup [ ] ungroup :: [[a]] -> [a] Example: ungroup [[1,2],[3,4]] = [1,2,3,4] 18 / 21

Slide 65

Slide 65 text

Exercise 12: ungroup [ ] ungroup :: [[a]] -> [a] Example: ungroup [[1,2],[3,4]] = [1,2,3,4] Hints: Use Hoogle 18 / 21

Slide 66

Slide 66 text

That’s all folks Time to solve some Sudokus 19 / 21

Slide 67

Slide 67 text

That’s all folks Time to solve some Sudokus The current approach is inefficient, but correct 19 / 21

Slide 68

Slide 68 text

That’s all folks Time to solve some Sudokus The current approach is inefficient, but correct Equational reasoning to improve performance 19 / 21

Slide 69

Slide 69 text

That’s all folks Time to solve some Sudokus The current approach is inefficient, but correct Equational reasoning to improve performance Define a function prune that eliminates early invalid solutions filter valid . expand = filter valid . expand . prune 19 / 21

Slide 70

Slide 70 text

That’s all folks Time to solve some Sudokus The current approach is inefficient, but correct Equational reasoning to improve performance Define a function prune that eliminates early invalid solutions filter valid . expand = filter valid . expand . prune It is not hard to define a function to prune a row (exercise): pruneRow [[4],[1,2],[1],[1,3]] = [[4],[2],[1],[3]] 19 / 21

Slide 71

Slide 71 text

That’s all folks Time to solve some Sudokus The current approach is inefficient, but correct Equational reasoning to improve performance Define a function prune that eliminates early invalid solutions filter valid . expand = filter valid . expand . prune It is not hard to define a function to prune a row (exercise): pruneRow [[4],[1,2],[1],[1,3]] = [[4],[2],[1],[3]] Equational reasoning to define prune in terms of pruneRow 19 / 21

Slide 72

Slide 72 text

That’s all folks Time to solve some Sudokus The current approach is inefficient, but correct Equational reasoning to improve performance Define a function prune that eliminates early invalid solutions filter valid . expand = filter valid . expand . prune It is not hard to define a function to prune a row (exercise): pruneRow [[4],[1,2],[1],[1,3]] = [[4],[2],[1],[3]] Equational reasoning to define prune in terms of pruneRow The function pruneRow satisfies the equation filter nodups . cp = filter nodups . cp . pruneRow 19 / 21

Slide 73

Slide 73 text

That’s all folks Time to solve some Sudokus The current approach is inefficient, but correct Equational reasoning to improve performance Define a function prune that eliminates early invalid solutions filter valid . expand = filter valid . expand . prune It is not hard to define a function to prune a row (exercise): pruneRow [[4],[1,2],[1],[1,3]] = [[4],[2],[1],[3]] Equational reasoning to define prune in terms of pruneRow The function pruneRow satisfies the equation filter nodups . cp = filter nodups . cp . pruneRow Expand the expression filter valid . expand 19 / 21

Slide 74

Slide 74 text

That’s all folks Time to solve some Sudokus The current approach is inefficient, but correct Equational reasoning to improve performance Define a function prune that eliminates early invalid solutions filter valid . expand = filter valid . expand . prune It is not hard to define a function to prune a row (exercise): pruneRow [[4],[1,2],[1],[1,3]] = [[4],[2],[1],[3]] Equational reasoning to define prune in terms of pruneRow The function pruneRow satisfies the equation filter nodups . cp = filter nodups . cp . pruneRow Expand the expression filter valid . expand Use the above equation and compress back the formula 19 / 21

Slide 75

Slide 75 text

Further references Richard Bird’s papers and books (Bird, 2006, 2010, 2014) Conor McBride’s Sudoku solver using applicative and traversable: https://stackoverflow.com/a/10242673/474311 20 / 21

Slide 76

Slide 76 text

References Bird, R. (2010). Pearls of Functional Algorithm Design. Cambridge University Press. Bird, R. (2014). Thinking Functionally with Haskell. Cambridge University Press. Bird, R. S. (2006). A program to solve Sudoku. Journal of Functional Programming, 16(6):671–679. 21 / 21