On Tue, 2007-03-20 at 15:09 +1000, Matthew Brecknell wrote:

> I'm not sure I see the problem, since any operation that touches all the
> elements of a n-by-n matrix will be at least O(n^2). For such an
> operation, a transposition should just add a constant factor.

What I was hoping for was a data structure such that I could directly
access the columns of the matrix, rather than having to apply a function
to get to them.  I didn't think it likely, but then again, a man can
dream... ;)

> 
> When you tried using Arrays, I presume you used an array indexed by a
> pair (i,j), and just reversed the order of the index pair to switch from
> row-wise to column-wise access? It's hard to see how that would slow you
> down. Perhaps the slowdown was caused by excessive array copying?
> Immutable arrays can be efficient for algorithms that write a whole
> array at once, but usually not for algorithms that modify one element at
> a time.
> 
> I think you'll need to show specific functions that are performing below
> expectation before the list will be able to help.
> 

I've attached the code I had written using Arrays.  Note that it wasn't
the final version of my algorithms, etc. as I continued work a bit
further using lists of lists, but the working behind them is just the
same (just some extra optimizations, etc. are missing).

Here's the way I was pulling out the rows and columns from the array:


type Matrix a = IArray MatCoord a
type MatCoord = (Int,Int)
type Row a = Array Int a

getRows :: Matrix a -> [Row a]
getRows m = [newRow sr [(c,v) | ((r,c),v) <- vals, r == r'] |
                r' <- values sr]
            where
            sr = fst . snd . bounds $ m
            vals = assocs m

unRows :: [Row a] -> Matrix a
unRows = liftM2 newMatrix length addRowValues
         where
         addRowValue r = map (\ (c,v) -> ((r,c),v)) . assocs
         addRowValues = concat . zipWith addRowValue [1..]


getCols :: Matrix a -> [Row a]
getCols m = [newRow sc [(r,v) | ((r,c),v) <- vals, c == c'] |
                c' <- values sc]
            where
            sc = snd . snd . bounds $ m
            vals = assocs m

unCols :: [Row a] -> Matrix a
unCols = liftM2 newMatrix length addColValues
         where
         addColValue c = map (\ (r,v) -> ((r,c),v)) . assocs
         addColValues = concat . zipWith addColValue [1..]

And here's an example where I used these functions.  What it's doing is
storing a list of all possible values for each cell in the matrix, then
removing double ups.

type Choices = [[Int]]

prune   :: Matrix Choices -> Matrix Choices
prune =  pruneBy cols . pruneBy rows
         where 
         pruneBy (f,f') = f' . map reduce . f

reduce     :: Row Choices -> Row Choices
reduce r =  array size (map reducer vals)
            where
            size = bounds r
            vals = assocs r
            reducer (c,vs) = (c, vs `minus` singles)
            singles = getSingles r

minus   :: Choices -> Choices -> Choices
xs `minus` ys   =  if single xs then xs else xs \\ ys

> For problems like latin squares and sudoku, also try thinking "outside
> the matrix". Do you really need to structure the problem in terms of
> rows and columns? What about a set of mutually-intersecting sets of
> cells?

I based my code upon the paper by Richard Bird and later implemented by
Graham Hutton.  I'm going to be re-writing it a bit, in terms of first
generating the "shapes" that the Partial Latin Squares can take, then
trying to fill in the numbers.  I have considered using a graph-theory
approach, but I have no idea where to even start to code it.
Fundamentally, I'm trying to generate all Partial Latin Squares that fit
a given criteria, so I need the rows/columns to ensure that I have no
more than one instance of each value in each row or column.

-- 
Ivan Lazar Miljenovic
Latin Squares Solver and Generator
==================================

Declaring this as a module
--------------------------

> module LatinSquares  where

Importing Modules
-----------------

> import Data.List
> import Data.Maybe()
> import Control.Monad.List
> import Data.Array.IArray

Defining Types
--------------

There are two main storage structures used: Sets and Matrices.
Whilst both of these utilise underlying Haskell lists, this distinction
is used to separate the different uses.  Sets are used to store all possible
or given values in no particular order, whilst matrices are used to store 
elements in some order in a 2-dimensional fashion.

A matrix is defined as a list of rows, where each row is a list
of values.  Note that, due to this definition, matrices are not 
inherently defined as having the same number of items in each 
row and column: this is up to the function that creates the matrix!

> type Matrix a = IArray MatCoord a
>
> type MatCoord = (Int,Int)
>
> type Row a = Array Int a

A set is merely a collection of items of the same type

> type Set a = [a]

A Latin Square is defined as a matrix of values, with Value as a wrapper
around Int.  Choices is a set of values used to indicate which values
can be stored in a particular position in the Latin Square. SolutionSet is used
to signify the results from the solve function, and should only be used
by those functions that require this output (otherwise, use "Set LatinSquare"
directly).

> newtype LatinSquare = LS (Matrix Value)
>     deriving (Eq, Ord)
>
> type SolutionSet = Set LatinSquare
>
> type Value = Int
>
> type Choices = Set Value

We require that LatinSquare is in the Eq and Show classes:

> instance Show LatinSquare where
>     show (LS m) = matToString printFunc m
>         where
>         printFunc v = if (isUnset v) then unsetString else (show v)
>         unsetString = "."

Basic Definitions
-----------------

A list of all the allowed values

> values :: Int -> [Int]
> values = enumFromTo 1
 
Indicates an undefined value

> unsetValue :: Value
> unsetValue = 0

Checks if the given value is set or unset

> isUnset :: Value -> Bool
> isUnset = (== unsetValue)
>
> isSet :: Value -> Bool
> isSet = (/= unsetValue)

Checks if there is only one element

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

Checks if there are any elements

> empty :: Set a -> Bool
> empty = null

Find the first element in a given Set that fulfills the criteria,
or else the last element if none of them fulfill the criteria
(note: the set cannot be non-empty).

> dropUntil          :: (a -> Bool) -> Set a -> a
> dropUntil _ []     = error "dropUntil requires a non-empty list!!!"
> dropUntil p xs     = if (not (empty filtered)) then (head filtered) else (last xs)
>                      where
>                      filtered = filter p $ xs


Matrix-related Functions
------------------------

To create a square, array-based matrix

> newMatrix   :: Int -> [(MatCoord,a)] -> Matrix a
> newMatrix n = array ((1,1),(n,n))

To create a row/column

> newRow   :: Int -> [(Int,a)] -> Row a
> newRow n = array (1,n)

To create a new matrix filled with the given value:

> blankMatrix     :: a -> Int -> Matrix a
> blankMatrix v n = newMatrix n [((r,c),v) | r <- values n, c <- values n]

To get the values only, without worrying about row or columns (though due
to the storage mechanism of arrays, it should come in terms of row by row).
This should, however, also get the values out of a Row.

> getVals :: (Ix i ) => Array i a -> Set a
> getVals = map snd . assocs

To unset each set value in turn.

> getSubSquares        :: LatinSquare -> Set LatinSquare
> getSubSquares (LS m) = [LS (m // [((r,c),unsetValue)]) 
>                         | ((r,c),v) <- vals, isSet v]
>                        where
>                        vals = assocs m

To get the rows from a Matrix, we need to pull out all coordinates from
the matrix that are in a given row.

> getRows :: Matrix a -> Set (Row a)
> getRows m = [newRow sr [(c,v) | ((r,c),v) <- vals, r == r'] | r' <- values sr]
>             where
>             sr = fst . snd . bounds $ m
>             vals = assocs m

To turn a set of rows back into a matrix, assumed to be square:

> unRows :: Set (Row a) -> Matrix a
> unRows = liftM2 newMatrix length addRowValues
>          where
>          addRowValue r = map (\ (c,v) -> ((r,c),v)) . assocs
>          addRowValues = concat . zipWith addRowValue [1..]


Similarly for columns:

> getCols :: Matrix a -> Set (Row a)
> getCols m = [newRow sc [(r,v) | ((r,c),v) <- vals, c == c'] | c' <- values sc]
>             where
>             sc = snd . snd . bounds $ m
>             vals = assocs m
>
> unCols :: Set (Row a) -> Matrix a
> unCols = liftM2 newMatrix length addColValues
>          where
>          addColValue c = map (\ (r,v) -> ((r,c),v)) . assocs
>          addColValues = concat . zipWith addColValue [1..]

We thus have getRows . unRows = id = getCols . unCols.  To simplify calling
these, we shall bundle them up:

> rows, cols :: (Matrix a -> Set (Row a), Set (Row a) -> Matrix a)
> rows = (getCols, unCols)
> cols = (getCols, unCols)

Creating a new Latin Square
---------------------------

Creates a new Latin Square of the given size filled with unsetValue

> blank :: Int -> Matrix Value
> blank = blankMatrix unsetValue

>
> createLatinSquare   :: Matrix Value -> LatinSquare
> createLatinSquare m = LS m
>
> blankLatinSquare :: Int -> LatinSquare
> blankLatinSquare = createLatinSquare . blank

To recover the matrix from the Latin Square:

> getMatrix        :: LatinSquare -> Matrix Value
> getMatrix (LS m) = m

Validity Checking
-----------------

A Latin Square is "complete" (i.e. finished) if there is only one 
possible choice available for each position

> complete :: Matrix Choices -> Bool
> complete =  (all single) . getVals

Similarly, a Latin Square is "void" if some square contains no choices:

> void :: Matrix Choices -> Bool
> void =  (any empty) . getVals

A Latin Square is "safe" when all rows and columns have no duplicates
of the same single choice.

> safe   :: Matrix Choices -> Bool
> safe m =  all consistent (getRows m) 
>           && all consistent (getCols m)
>
> consistent :: Row Choices -> Bool
> consistent =  nodups . getSingles
>
> getSingles :: (Ix i) => Array i Choices -> Set Value
> getSingles = getSinglesSet . getVals
>              where
>              getSinglesSet r = [ v | [v] <- r, isSet v]
>
> nodups        :: Choices -> Bool
> nodups []     = True
> nodups (x:xs) = notElem x xs && nodups xs

Finally, a Latin Square is "blocked" if it is void or unsafe:

> blocked   :: Matrix Choices -> Bool
> blocked m = void m || not (safe m)

To determine how many elements in a Latin Square are set or unset:

> numSetRow :: Row Value -> Int
> numSetRow = length . filter isSet . getVals

Generating all Possibilities
----------------------------

Constructing a Matrix where each position has a list of all
possible values that might go there

> choices   :: Int -> LatinSquare -> Matrix Choices
> choices n =  prune . newMatrix n . map chooser . assocs . getMatrix
>              where
>              chooser (i,v) = (i, choice v)
>              choice v = if isUnset v then vals else [v]
>              vals = values n

The prune function goes through and eliminates all set values from the list 
of possible values in each row and column.  The minus function is an adapted
form of the (\\) function, such that it ignores single cells.

> prune	:: Matrix Choices -> Matrix Choices
> prune =  pruneBy cols . pruneBy rows
>          where 
>          pruneBy (f,f') = f' . map reduce . f
>
> reduce     :: Row Choices -> Row Choices
> reduce r =  array size (map reducer vals)
>             where
>             size = bounds r
>             vals = assocs r
>             reducer (c,vs) = (c, vs `minus` singles)
>             singles = getSingles r
>
> minus 	:: Choices -> Choices -> Choices
> xs `minus` ys	=  if single xs then xs else xs \\ ys

To ensure that whenever we are collapsing a matrix of Choices that the Latin
Square created at each step is still valid, we only expand the first cell
with more than once choice.  When pruned after each expansion, the Latin Squares
produced are guarunteed to fulfill the criteria that each value between 1 and n
occurs at most one time in each row and column.  An extra advantage of this approach
is that if the Latin Square produced does not fulfill other criteria, then
the expansion of that particular matrix of Choices can be halted, saving computation
time.  Note: as a consequence of this, all outputs of this function should be
'safe', but it is best to use the safe function to make sure (i.e. in case
overlaps from various pre-set values cause un-safeness).

Since the values are stored in matrices in a list of associations, this
simplifies the matter of choosing a single value.

> expand   :: Matrix Choices -> Set (Matrix Choices)
> expand m = map (array size . (++) before . flip (:) after . (,) i . return) vs
>            where
>            size = bounds m
>            (before, (i,vs):after) = break (not . single . snd) . assocs $ m

Solving a Latin Square
----------------------

To solve a Partial Latin Square, we construct matrix of Choices,
with the known values set.  We then go and expand each possible unset
position at a time, and see if that provides us with a solution.

> solve   :: Int -> LatinSquare -> SolutionSet
> solve n = solutionSearch . (choices n)
>
> solutionSearch :: Matrix Choices -> Set LatinSquare
> solutionSearch m
>     | blocked m  = []
>     | complete m = [fixVals m]
>     | otherwise  = [g | m' <- expand m,
>                     g <- solutionSearch (prune m')]

fixVals is used to turn a matrix of Choices for a Latin Square into that Latin
Square by 'fixing' the values in place.  Make sure you check that it isn't 
void first!!!!!!!  The extra else segment for non-single matrices of Choices 
is in case this function is used to test a Latin Square that hasn't been 
fully expanded as yet.

> fixVals :: Matrix Choices -> LatinSquare
> fixVals = createLatinSquare . valFixer
>
> valFixer   :: Matrix Choices -> Matrix Value
> valFixer m = array size . map fixer $ vals
>              where
>              size = bounds m
>              vals = assocs m
>              fixer (i,(x:xs)) = if empty xs then (i,x) else (i,unsetValue)

Partial Latin Square (PLS) Properties
-------------------------------------

The functions completeable, premature and uniquelyCompleteable are designed to
receive the input of the solve function.

A PLS is completeable if it has at least one solution, premature otherwise

> completeable :: SolutionSet -> Bool
> completeable = not . empty

> premature :: SolutionSet -> Bool
> premature = empty

A PLS is uniquely completeable if it has exactly one solution
(note that, this function returns false for non-completeable PLSs
as well, i.e. if uniquely completeable, then it is completeable).

> uniquelyCompleteable :: SolutionSet -> Bool
> uniquelyCompleteable = single

The following functions are the same as those above, but take in a Latin Square
and solve it first.

> isCompleteable   :: Int -> LatinSquare -> Bool
> isCompleteable n = completeable . solve n
>
> isPremature   :: Int -> LatinSquare -> Bool
> isPremature n = premature . solve n
>
> isUniquelyCompleteable   :: Int -> LatinSquare -> Bool
> isUniquelyCompleteable n = uniquelyCompleteable . solve n

A PLS is a critical set if it is uniquely completeable, but all subsets 
have more than one completion (note that we do not need to check if the subsets
are completeable, since if the given Latin Square is completeable then the 
sub-squares must be).

> isCriticalSet      :: Int -> LatinSquare -> Bool
> isCriticalSet n ls = isUniquelyCompleteable n ls
>                        && all (not . isUniquelyCompleteable n) subSquares
>                      where
>                      subSquares = getSubSquares ls

Helper functions for creating Partial Latin Squares (PLS)
---------------------------------------------------------

Similar to choice, partialChoices constructs a Matrix of Choices, except that
it includes the unsetValue as well.

> partialChoices   :: Int -> LatinSquare -> Matrix Choices
> partialChoices n = newMatrix n . addUnset . assocs . (choices n)
>                    where
>                    addUnset = map (\ (i,vs) -> (i,unsetValue:vs))

Since Latin Squares are O(n^(n^2)), we want to try and reduce the number
of them considered as soon as possible.  As such, we shall try and construct
exactly _one_ from each isotopy class.

Two Latin Squares are in the same isotopic class if one can be transformed into
another by performing combinations of the following:

    1) Row swaps
    2) Column swaps
    3) Symbol swaps (e.g. replace all 1s with 2s and vice versa)
    4) Transposition

This works because all that defines a PLS is that each number from 1 to n appears
at most once in each row or column (for a non-Partial Latin Square, replace
"at most" with "exactly").  As such, none of the above four transformations will
change this behaviour.

Since swapping rows and columns does not change the structural definition
of the Latin Square, we define the "simplest isotopic form" of a PLS as 
the one where the number of set values in each row or column is greater than
or equal to the number of set values in the next row or column.  Furthermore,
if we consider the first time any particular (set) number appears in the Latin 
Square, then they should be in ascending order.

As such, we need functions that will check to see if a given matrix of Choices
or a Latin Square fulfill this criteria.

First of all, we compare the rows and columns.  A row or column has a higher
precedence than another row or column if it has a higher weighting, or -- if
they have the same weighting -- by finding which of the two first has a set
value where the other one doesn't (if they both have values in the same positions,
give precedence to the one with the lower value).

> isSimplestRows :: Matrix Value -> Bool
> isSimplestRows = isSimplestBy getRows
>
> isSimplestCols :: Matrix Value -> Bool
> isSimplestCols = isSimplestBy getCols
>
> isSimplestBy :: (Matrix Value -> Set (Row Value)) 
>                      -> Matrix Value -> Bool
> isSimplestBy f = and . ap (zipWith comesBefore) tail . f
>
> comesBefore :: Row Value -> Row Value -> Bool
> comesBefore r1 r2 = (r1 == r2) || s1 > s2 || ((s1 == s2) && (isLessThan fr1 fr2))
>                     where
>                     s1 = numSetRow r1
>                     s2 = numSetRow r2
>                     joined = zip (getVals r1) (getVals r2)
>                     (fr1,fr2) = dropUntil dropFunc joined
>                     dropFunc (x,y) = (isSet x) /= (isSet y)
>                     isLessThan v1 v2 = (isUnset v2) || ((isSet v1) && (v1 < v2))

Checking if the Matrix of Choices is in the simplest numerical order
i.e. each new, unique numerical value must be in ascending order.

> isSimplestNumbers   :: Int -> Matrix Choices -> Bool
> isSimplestNumbers n = simplestNumbers n . valOrder
>
> valOrder :: Matrix Choices -> Set Value
> valOrder = nub . getSingles
>
> simplestNumbers :: Int -> Set Value -> Bool
> simplestNumbers = (and .) . zipWith (==) . values

Since it is possible to transpose a Latin Square and still have it retain
is isotopy-ness, we need a way to check if a Latin Square would be simpler as
it is or as its transpose (if the latter, dump it).  Ultimately, this compares
the shape of the given Latin Square to its transpose, and determines which is
simpler.  If the shape is invariant under transposition, use sameShapeCheck.

> transposeCheck   :: Matrix Value -> Bool
> transposeCheck m = validWeightings || (equalWeightings && sameShapeCheck m)
>                    where
>                    dropped = dropUntil (\ (x1,x2) -> x1 /= x2)
>                    comparer = zip (weightOf getRows) (weightOf getCols)
>                    weightOf f = map numSetRow . f $ m
>                    droppedWeightings = dropped  comparer
>                    validWeightings =  uncurry (>) droppedWeightings
>                    equalWeightings = uncurry (==) droppedWeightings

If the shape is invariant under transposition, then preference is given to the
LatinSquare where - when the elements in the kth column are paired up with the
elements in the kth row - the first pair where both elements are set has a lower 
or equal value in the column than the row.  Note that if the shape is invariant, 
then if there is a set value at (i,j), then there is also a set value at (j,i).

> sameShapeCheck   :: Matrix Value -> Bool
> sameShapeCheck m = all comparer firsts
>                    where
>                    getVs f = map getVals . f
>                    zippedRows = zip (getVs getRows m) (getVs getCols m)
>                    zippedVals = map (uncurry zip) zippedRows
>                    firsts = map (dropUntil dropFun) zippedVals
>                    dropFun (r,_) = isSet r
>                    comparer (r,c) = r >= c

Generating PLSs that are in simplest form
-----------------------------------------

Since values are expanded along each row first, when creating the Set of PLSs
in simplest form, at each stage we check if the _rows_ are simplest, and leave
the checking of columns to the end.  To see why this is important, consider
the following PLS:

     0 1 2
     1 0 0
     2 0 0

If we check both rows and columns at each stage of expansion, then any PLS
where the top-left-hand element is not set will automatically fail, as
if we then set any other element in the first row then the criteria for that
intermediary PLS to be simplest isn't satisfied (but row-wise will be OK).
As such, we try and cut down as many as possible during the expansion stage so
that non-simplest PLSs aren't needlessly expanded.

> generatePartials :: Int -> Set LatinSquare
> generatePartials n = partialSearch n partialOptions
>                      where
>                      partialOptions = partialChoices n (blankLatinSquare n)
>
> partialSearch :: Int -> Matrix Choices -> Set LatinSquare
> partialSearch n m
>     | blocked m  = []
>     | not . isSimplestRows . valFixer $ m = []
>     | not (isSimplestNumbers n m) = []
>     | complete m = createPartial m
>     | otherwise  = [g | m' <- expand m,
>                     g <- partialSearch n (prune m')]

createPartial checks a few extra criteria to ensure that the Latin Square
is in the simplest form.  Note that its input _must_ fulfill "complete m"
(i.e. each Set of Values should be single).

> createPartial     :: Matrix Choices -> Set LatinSquare
> createPartial m = if valid then [createLatinSquare fixed] else []
>                   where
>                   fixed = valFixer m
>                   valid = (isSimplestCols fixed) && (transposeCheck fixed)

To simplify the construction of PLSs that fulfill a certain criteria, use this
function (it is just a wrapper for filtering the results of generatePartials).

> generatePartialType :: (Int -> LatinSquare -> Bool) -> Int -> Set LatinSquare
> generatePartialType p n = filter (p n) $! partials
>                           where
>                           partials = generatePartials n

Generating Critical Sets:

> generateCriticalSets :: Int -> Set LatinSquare
> generateCriticalSets = generatePartialType isCriticalSet

Matrix Printing
---------------

Converts a Matrix to a String (used to show Latin Squares).

> matToString     :: (a -> String) -> Matrix a -> String
> matToString f m = flip (++) "\n" . unlines . map unwords $ mStr'
>                   where
>                   mStr = map ((map f) . getVals) . getRows $ m
>                   strLen = maxStrLen mStr
>                   mStr' = map (map (bulkStr strLen)) mStr

> maxStrLen :: [[String]] -> Int
> maxStrLen = maximum . (map (maximum . map length))

> bulkStr :: Int -> String -> String
> bulkStr l = (++) =<< flip replicate space . (-) l . length
>             where
>             space = ' '

Attachment: signature.asc
Description: This is a digitally signed message part

_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe

Reply via email to