Матрица строк, с уникальными столбцами и строками, латинский квадрат - PullRequest
0 голосов
/ 26 января 2020

Я пытаюсь написать функцию, которая для n дает матрицу n * n с уникальными строками и столбцами (латинский квадрат). Я получил функцию, которая дает мой список строк "1" .. "2" .. "n"

numSymbol:: Int -> [String]

Я попытался сгенерировать все перестановки этого, и все они кортежи n-длины перестановок, и они проверяют уникальность строки / столбца. Но сложность (n!) ^ 2 прекрасно работает для 2 и 3, но при n> 3 она занимает вечность. Можно построить латинский квадрат из перестановок напрямую, например, из

permutation ( numSymbol 3) = [["1","2","3"],["1","3","2"],["2","1","3"],["2","3","1"],["3","1","2"],["3","2","1"]] 

get

[[["1","2","3",],["2","1","3"],["3","1","2"]] , ....]

без создания списка типа [["1", ...], [" 1 ", ...], ...], когда мы узнаем, что первый элемент дисквалифицирует его?

1 Ответ

2 голосов
/ 27 января 2020

Примечание: , поскольку мы можем легко взять латинский квадрат, заполненный числами от 1 до n , и пометить его как угодно, мы можем написать код, который использует целочисленные символы, ничего не отдавая, поэтому давайте придерживаться этого.

В любом случае, отслеживание состояния / недетерминированность c монада:

type StateList s = StateT s []

полезно для такого рода проблем.

Вот идея. Мы знаем, что каждый символ s будет появляться ровно один раз в каждой строке r, поэтому мы можем представить это с помощью урны всех возможных упорядоченных пар (r,s):

my_rs_urn = [(r,s) | r <- [1..n], s <- [1..n]]

Аналогично, как каждый символ s появляется ровно один раз в каждом столбце c, мы можем использовать второй урн:

my_cs_urn = [(c,s) | c <- [1..n], s <- [1..n]]

Создание латинского квадрата - это вопрос заполнения каждой позиции (r,c) символом s удаляя совпадающие шары (r,s) и (c,s) (т.е. удаляя два шара, по одному из каждой урны), чтобы каждый шар использовался ровно один раз. Наше состояние будет содержимым урн.

Нам нужно вернуться назад, потому что мы можем достичь точки, в которой для конкретной позиции (r,c) не существует s, такой, чтобы (r,s) и (c,s) были оба все еще доступны в их соответствующих урнах. Кроме того, приятным побочным эффектом обратного отслеживания / недетерминированности на основе списка является то, что он будет генерировать все возможные латинские квадраты, а не только первый найденный.

Учитывая это, наше состояние будет выглядеть следующим образом:

type Urn = [(Int,Int)]

data S = S
  { size :: Int
  , rs :: Urn
  , cs :: Urn }

Я включил size в состояние для удобства. Он никогда не будет изменен, поэтому на самом деле он должен быть вместо Reader, но это проще.

Мы представим квадрат списком содержимого ячеек в порядке следования строк ( т.е. символы в позициях [(1,1),(1,2),...,(1,n),(2,1),...,(n,n)]):

data Square = Square
  Int   -- square size
  [Int] -- symbols in row-major order
  deriving (Show)

Теперь действие monadi c для генерации латинских квадратов будет выглядеть так:

type M = StateT S []

latin :: M Square
latin = do
  n <- gets size
  -- for each position (r,c), get a valid symbol `s`
  cells <- forM (pairs n) (\(r,c) -> getS r c)
  return $ Square n cells

pairs :: Int -> [(Int,Int)]
pairs n = -- same as [(x,y) | x <- [1..n], y <- [1..n]]
          (,) <$> [1..n] <*> [1..n]

Рабочая функция getS выбирает s, чтобы (r,s) и (c,s) были доступны в соответствующих урнах, удаляя эти пары из урн как побочный эффект. Обратите внимание, что getS написан недетерминированно, поэтому он попробует все возможные способы выбора s и связанных шаров из урн:

getS :: Int -> Int -> M Int
getS r c = do
  -- try each possible `s` in the row
  s <- pickSFromRow r
  -- can we put `s` in this column?
  pickCS c s
  -- if so, `s` is good
  return s

Большая часть работы выполняется помощниками pickSFromRow и pickCS. Первый, pickSFromRow выбирает s из данной строки:

pickSFromRow :: Int -> M Int
pickSFromRow r = do
  balls <- gets rs
  -- "lift" here non-determinstically picks balls
  ((r',s), rest) <- lift $ choices balls
  -- only consider balls in matching row
  guard $ r == r'
  -- remove the ball
  modify (\st -> st { rs = rest })
  -- return the candidate "s"
  return s

Он использует хелпер choices, который генерирует все возможные способы вытаскивания одного элемента из списка:

choices :: [a] -> [(a,[a])]
choices = init . (zipWith f <$> inits <*> tails)
  where f a (x:b) = (x, a++b)
        f _ _ = error "choices: internal error"

Второй, pickCS проверяет, есть ли (c,s) в урне cs, и удаляет его, если он:

pickCS :: Int -> Int -> M ()
pickCS c s = do
  balls <- gets cs
  -- only continue if the required ball is available
  guard $ (c,s) `elem` balls
  -- remove the ball
  modify (\st -> st { cs = delete (c,s) balls })

С соответствующим драйвером для нашей монады:

runM :: Int -> M a -> [a]
runM n act = evalStateT act (S n p p)
  where p = pairs n

это может генерировать все 12 латинских квадратов размером 3:

λ> runM 3 latin
[Square 3 [1,2,3,2,3,1,3,1,2],Square 3 [1,2,3,3,1,2,2,3,1],...]

или 576 латинских квадратов размером 4:

λ> length $ runM 4 latin
576

Скомпилировано с -O2, это достаточно быстро, чтобы перечислить все 161280 квадратов размера 5 за пару секунд:

main :: IO ()
main = print $ length $ runM 5 latin

Представленное выше представление urn на основе списка не очень эффективно. С другой стороны, поскольку длины списков довольно малы, не так уж много, чтобы получить , находя более эффективные представления.

Тем не менее, вот полный код, который использует эффективные Map / Установите представления с учетом способа использования урн rs и cs. Скомпилированный с -O2, он работает в постоянном пространстве. При n = 6 он может обрабатывать около 100000 латинских квадратов в секунду, но это все равно означает, что ему потребуется несколько часов, чтобы перечислить все 800 миллионов из них.

{-# OPTIONS_GHC -Wall #-}

module LatinAll where

import Control.Monad.State
import Data.List
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Map (Map, (!))
import qualified Data.Map as Map

data S = S
  { size :: Int
  , rs :: Map Int [Int]
  , cs :: Set (Int, Int) }

data Square = Square
  Int   -- square size
  [Int] -- symbols in row-major order
  deriving (Show)

type M = StateT S []

-- Get Latin squares
latin :: M Square
latin = do
  n <- gets size
  cells <- forM (pairs n) (\(r,c) -> getS r c)
  return $ Square n cells

-- All locations in row-major order [(1,1),(1,2)..(n,n)]
pairs :: Int -> [(Int,Int)]
pairs n = (,) <$> [1..n] <*> [1..n]

-- Get a valid `s` for position `(r,c)`.
getS :: Int -> Int -> M Int
getS r c = do
  s <- pickSFromRow r
  pickCS c s
  return s

-- Get an available `s` in row `r` from the `rs` urn.
pickSFromRow :: Int -> M Int
pickSFromRow r = do
  urn <- gets rs
  (s, rest) <- lift $ choices (urn ! r)
  modify (\st -> st { rs = Map.insert r rest urn })
  return s

-- Remove `(c,s)` from the `cs` urn.
pickCS :: Int -> Int -> M ()
pickCS c s = do
  balls <- gets cs
  guard $ (c,s) `Set.member` balls
  modify (\st -> st { cs = Set.delete (c,s) balls })

-- Return all ways of removing one element from list.
choices :: [a] -> [(a,[a])]
choices = init . (zipWith f <$> inits <*> tails)
  where f a (x:b) = (x, a++b)
        f _ _ = error "choices: internal error"

-- Run an action in the M monad.
runM :: Int -> M a -> [a]
runM n act = evalStateT act (S n rs0 cs0)
  where rs0 = Map.fromAscList $ zip [1..n] (repeat [1..n])
        cs0 = Set.fromAscList $ pairs n

main :: IO ()
main = do
  print $ runM 3 latin
  print $ length (runM 4 latin)
  print $ length (runM 5 latin)

Несколько замечательно, модифицируя программа для получения только уменьшенных латинских квадратов (т. е. с символами [1..n] по порядку как в первой строке, так и в первом столбце) требует изменения только двух функций:

-- All locations in row-major order, skipping first row and column
-- i.e., [(2,2),(2,3)..(n,n)]
pairs :: Int -> [(Int,Int)]
pairs n = (,) <$> [2..n] <*> [2..n]

-- Run an action in the M monad.
runM :: Int -> M a -> [a]
runM n act = evalStateT act (S n rs0 cs0)
  where -- skip balls [(1,1)..(n,n)] for first row
        rs0 = Map.fromAscList $ map (\r -> (r, skip r)) [2..n]
        -- skip balls [(1,1)..(n,n)] for first column
        cs0 = Set.fromAscList $ [(c,s) | c <- [2..n], s <- skip c]
        skip i = [1..(i-1)]++[(i+1)..n]

С этими модификациями, результирующий Square будет включать символы в порядке следования строк, но пропуская первую строку и столбец. Например:

λ> runM 3 latin
[Square 3 [3,1,1,2]]

означает:

1 2 3  fill in question marks  1 2 3
2 ? ?  =====================>  2 3 1
3 ? ?    in row-major order    3 1 2

Это достаточно быстро, чтобы перечислить все 16 942 080 сокращенных латинских квадратов размера 7 за несколько минут:

$ stack ghc -- -O2  -main-is LatinReduced LatinReduced.hs && time ./LatinReduced
[1 of 1] Compiling LatinReduced     ( LatinReduced.hs, LatinReduced.o )
Linking LatinReduced ...
16942080

real    3m9.342s
user    3m8.494s
sys     0m0.848s
...