Получение всех диагоналей матрицы в 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
списки. Диагонали в обратном порядке.
но это не очень эффективно и не работает для бесконечных списков.