Это, по-видимому, напрямую связано с аргументом теории множеств, доказывающим, что набор целочисленных пар находится в взаимно однозначном соответствии с набором целых чисел ( denumerable ). Аргумент включает в себя так называемую функцию связывания Кантора .
Итак, из любопытства, давайте посмотрим, сможем ли мы получить функцию diagonalize
таким способом. Определите бесконечный список пар Кантора рекурсивно в Haskell:
auxCantorPairList :: (Integer, Integer) -> [(Integer, Integer)]
auxCantorPairList (x,y) =
let nextPair = if (x > 0) then (x-1,y+1) else (x+y+1, 0)
in (x,y) : auxCantorPairList nextPair
cantorPairList :: [(Integer, Integer)]
cantorPairList = auxCantorPairList (0,0)
И попробуйте это внутри ghci:
λ> take 15 cantorPairList
[(0,0),(1,0),(0,1),(2,0),(1,1),(0,2),(3,0),(2,1),(1,2),(0,3),(4,0),(3,1),(2,2),(1,3),(0,4)]
λ>
Мы можем нумеровать пары и, например, извлечь числа для тех пар, которые имеют нулевую координату х:
λ>
λ> xs = [1..]
λ> take 5 $ map fst $ filter (\(n,(x,y)) -> (x==0)) $ zip xs cantorPairList
[1,3,6,10,15]
λ>
Мы понимаем, что это верхняя строка из результата ОП в тексте вопроса. Аналогично для следующих двух строк:
λ>
λ> makeRow xs row = map fst $ filter (\(n,(x,y)) -> (x==row)) $ zip xs cantorPairList
λ> take 5 $ makeRow xs 1
[2,5,9,14,20]
λ>
λ> take 5 $ makeRow xs 2
[4,8,13,19,26]
λ>
Оттуда мы можем написать наш первый черновик функции diagonalize
:
λ>
λ> printAsLines xs = mapM_ (putStrLn . show) xs
λ> diagonalize xs = takeWhile (not . null) $ map (makeRow xs) [0..]
λ>
λ> printAsLines $ diagonalize [1..19]
[1,3,6,10,15]
[2,5,9,14]
[4,8,13,19]
[7,12,18]
[11,17]
[16]
λ>
РЕДАКТИРОВАТЬ: обновление производительности
Для списка из 1 миллиона элементов время выполнения равно 18 se c, а для 4 миллионов элементов - 145 секунд. Как уже упоминалось в Redu, это похоже на сложность O (n√n).
Распределение пар между различными целевыми подсписками неэффективно, так как большинство операций фильтрации завершаются неудачно.
Для повышения производительности мы можно использовать структуру Data.Map для целевых подсписков.
{-# LANGUAGE ExplicitForAll #-}
{-# LANGUAGE ScopedTypeVariables #-}
import qualified Data.List as L
import qualified Data.Map as M
type MIL a = M.Map Integer [a]
buildCantorMap :: forall a. [a] -> MIL a
buildCantorMap xs =
let ts = zip xs cantorPairList -- triplets (a,(x,y))
m0 = (M.fromList [])::MIL a
redOp m (n,(x,y)) = let afn as = case as of
Nothing -> Just [n]
Just jas -> Just (n:jas)
in M.alter afn x m
m1r = L.foldl' redOp m0 ts
in
fmap reverse m1r
diagonalize :: [a] -> [[a]]
diagonalize xs = let cm = buildCantorMap xs
in map snd $ M.toAscList cm
С этой второй версией производительность выглядит намного лучше: 568 mse c для списка из 1 миллиона элементов, 2669 mse c для списка предметов 4 миллиона. Так что это близко к сложности O (n * Log (n)), на которую мы могли надеяться.