Получение всех диагоналей матрицы в Haskell

двумерный список выглядит так:

1 | 2 | 3
- - - - -
4 | 5 | 6
- - - - -
7 | 8 | 9

или в чистом haskell

[ [1,2,3], [4,5,6], [7,8,9] ]

ожидаемый результат для diagonals [ [1,2,3], [4,5,6], [7,8,9] ] is

[ [1], [4, 2], [7, 5, 3], [8, 6], [9] ]

писать allDiagonals (включить анти-диагонали) тогда банальный:

allDiagonals :: [[a]] -> [[a]]
allDiagonals xss = (diagonals xss) ++ (diagonals (rotate90 xss))

мои исследования по этой проблеме

аналогичный вопрос здесь, в StackOverflow

  • Python этот вопрос о той же проблеме в Python, но Python и Haskell очень разные, поэтому ответы на этот вопрос не относятся ко мне.

  • единственный этот вопрос и ответ в Haskell, но только о центральной диагонали.

Hoogle

Поиск [[a]] -> [[a]] не дал мне интересного результата.

независимое мышление

я думаю, что индексацию следует вид счета в базе x, где x-количество измерений в матрице, посмотрите на:

1 | 2
- - -
3 | 4

по диагонали [ [1], [3, 2], [4] ]

  • 1 можно найти в matrix[0][0]
  • 3 можно найти в matrix[1][0]
  • 2 можно найти в matrix[0][1]
  • 1 можно найти в matrix[1][1]

это похоже на подсчет в базе 2 до 3, то есть размер матрицы минус один. Но это далеко слишком расплывчато для перевода в код.

5 ответов


начиная с Вселенная-база-1.0.2.1, вы можете просто позвонить diagonals функция:

Data.Universe.Helpers> diagonals [ [1,2,3], [4,5,6], [7,8,9] ]
[[1],[4,2],[7,5,3],[8,6],[9]]

реализация в полном объеме выглядит так:

diagonals :: [[a]] -> [[a]]
diagonals = tail . go [] where
    -- it is critical for some applications that we start producing answers
    -- before inspecting es_
    go b es_ = [h | h:_ <- b] : case es_ of
        []   -> transpose ts
        e:es -> go (e:ts) es
        where ts = [t | _:t <- b]

ключевая идея заключается в том, что мы храним два списка: прямоугольный кусок, который мы не начали проверять, и пятиугольный кусок (прямоугольник с вырезанным верхним левым треугольником! что у нас есть. Для пятиугольного куска выбор первого элемента из каждого списка дает нам еще одну диагональ. Затем мы можем добавить новую строку из прямоугольного, не проверенного куска к тому, что осталось после удаления этой диагонали.

реализация может выглядеть немного неестественно, но она должна быть довольно эффективной и ленивой: единственное, что мы делаем для списков, это разрушаем их в голову и хвост, поэтому это должно быть O(n) в общем количестве элементов в Матрице; и мы производим элементы, как только закончим разрушение, поэтому это довольно лениво/дружелюбно к сбору мусора. Он также работает хорошо с бесконечно большими матрицами.

(Я нажал этот релиз только для вас: предыдущая ближайшая вещь, которую вы могли получить, использовала diagonal, который только даст вам [1,4,2,7,5,3,8,6,9] без дополнительной структуры вы хотите.)


вот рекурсивная версия, предполагая, что вход всегда хорошо сформирован:

diagonals []       = []
diagonals ([]:xss) = xss
diagonals xss      = zipWith (++) (map ((:[]) . head) xss ++ repeat [])
                                  ([]:(diagonals (map tail xss)))

Он работает рекурсивно, переходя от колонны к колонне. Значения из одного столбца объединяются с диагоналями из Матрицы, уменьшенными на один столбец, сдвинутыми на одну строку, чтобы фактически получить диагонали. Надеюсь, это объяснение имеет смысл.

для примера:

diagonals [[1,2,3],[4,5,6],[7,8,9]]
= zipWith (++) [[1],[4],[7],[],[],...] [[],[2],[5,3],[8,6],[9]]
= [[1],[4,2],[7,5,3],[8,6],[9]]

другая версия, которая работает на строках вместо столбцов, но на основе того же идея:

diagonals []       = repeat []
diagonals (xs:xss) = takeWhile (not . null) $
    zipWith (++) (map (:[]) xs ++ repeat [])
                 ([]:diagonals xss)

по сравнению с указанным результатом результирующие диагонали меняются местами. Это, конечно, можно исправить, применив map reverse.


вот один подход:

f :: [[a]] -> [[a]]
f vals = 
    let n = length vals
    in [[(vals !! y) !! x | x <- [0..(n - 1)], 
                            y <- [0..(n - 1)], 
                            x + y == k] 
        | k <- [0 .. 2*(n-1)]]

например, используя его в GHCi:

Prelude> let f vals = [ [(vals !! y) !! x | x <- [0..(length vals) - 1], y <- [0..(length vals) - 1], x + y == k] | k <- [0 .. 2*((length vals) - 1)]]

Prelude> f [ [1,2,3], [4,5,6], [7,8,9] ]
[[1],[4,2],[7,5,3],[8,6],[9]]

если предположить, что площадь n x n матрица, будет n + n - 1 диагонали (это k перебирает) и для каждой диагонали инвариант заключается в том, что индекс строки и столбца суммируется с диагональным значением (начиная с нулевого индекса для верхнего левого). Вы можете поменять порядок доступа к элементу (swap !! y !! x С !! x !! y) для изменения порядка сканирования растра матрица.


import Data.List

rotate90 = reverse . transpose
rotate180 = rotate90 . rotate90

diagonals = (++) <$> transpose . zipWith drop [0..]
                 <*> transpose . zipWith drop [1..] . rotate180

он сначала получает основной ([1,5,9]) и верхних диагоналей ([2,6] и [3]); затем нижние диагонали:[8,4] и [7].

если вы заботитесь о заказе (т. е. вы думаете, что он должен сказать [4,8] вместо [8,4]), вставить map reverse . на последней строчке.


одно другое решение:

diagonals = map concat
          . transpose
          . zipWith (\ns xs -> ns ++ map (:[]) xs)
                    (iterate ([]:) [])

в основном, мы поворачиваем

[1, 2, 3]
[4, 5, 6]
[7, 8, 9]

на

[[1], [2], [3]]
[[] , [4], [5], [6]]
[[] , [] , [7], [8], [9]]

затем transpose и concat списки. Диагонали в обратном порядке.

но это не очень эффективно и не работает для бесконечных списков.