Подтвердить что ты не робот

Каков наилучший способ извлечь диагональ из матрицы в Haskell?

Мне было предложено написать функцию, которая будет извлекать диагональ матрицы, хранящейся в виде списка списков. Первая версия заключалась в том, чтобы извлечь число, индексируя списки, но вскоре я пришел к выводу, что это не хороший алгоритм для Haskell и написал еще одну функцию:

getDiagonal :: (Num a) => [[a]] -> [a]
getDiagonal [[]]       = []
getDiagonal (xs:[])    = [head xs]
getDiagonal (x:xs)     = head x : getDiagonal (map tail xs)

Поскольку я только начал изучать Haskell, я не уверен, написано ли это идиоматическим способом или если он будет работать хорошо.

Итак, мой вопрос заключается в том, есть ли лучший способ извлечь диагональ из матрицы, хранящейся в таком представлении, или если нет лучшего алгоритма, который можно было бы построить, если бы матрица была представлена ​​с использованием более высоких порядков Haskell, например алгебраических типов? Также существует ли разница в производительности между деконструкцией списка в сопоставлении с образцом (так: (x: _): xs) или с функцией головы, как показано выше?

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

4b9b3361

Ответ 1

Вы можете упростить свое первоначальное определение:

mainDiagonal :: [[a]] -> [a]
mainDiagonal []     = []
mainDiagonal (x:xs) = head x : getDiagonal (map tail xs)

Нет ничего плохого в использовании индексации для этого, что позволяет упростить его:

mainDiagonal xs = zipWith (!!) xs [0..]

Представление на основе массива

Вы также можете представлять матрицы, используя Data.Array, индексированные (i,j). Это позволяет использовать математическое определение основной диагонали почти дословно:

import Data.Array

mainDiagonal :: (Ix i) => Array (i, i) e -> [e]
mainDiagonal xs = [ e | ((i,j),e) <- assocs xs, i == j ]

Вы можете использовать это как:

-- n×n matrix helper
matrix n = listArray ((0,0),(n-1,n-1))

> mainDiagonal $ matrix 3 [1..]
[1,5,9]

Эффективность

Предыдущее определение mainDiagonal по-прежнему неэффективно: для этого все еще нужны тесты O (N²) i == j. Аналогично версии zipWith ее можно фиксировать и обобщать следующим образом:

mainDiagonal xs = (xs !) `map` zip [n..n'] [m..m']
                      where ((n,m),(n',m')) = bounds xs

Эта версия только индексирует массив O (N) раз. (В качестве бонуса он также работает с прямоугольными матрицами и не зависит от базы индексирования.)

Ответ 2

Я думаю, что использование индексации в порядке, если вы можете предположить, что аргумент является квадратной матрицей. В любом случае диагональ с этим представлением будет O (N 2), так как вам нужно пройти по спискам.

diag x = zipWith (!!) x [0..]

Ответ 3

sdcwc ответил на исходный вопрос. Я хотел бы отметить, что представление матрицы в виде списка списков обычно неэффективно. Списки хороши там, где длина неизвестна, матрицы обычно имеют фиксированный размер. Вы можете использовать плоский ассоциативный список или карту для построения матрицы и все, что имеет постоянное время доступа к элементу, когда вы фактически выполняете вычисления с помощью этой матрицы. Data.Array - хороший выбор (см. ответ Пита).

Если вы запускаете численные вычисления в Haskell, вы можете использовать пакет hmatrix. Он имеет собственный тип данных матрицы, Data.Packed.Matrix, и он имеет функцию takeDiag для извлечения диагонали.

Пример Data.Packed.Matrix

Например, если m является вашей матрицей

ghci> let m = (3><3) [1..]
ghci> :t m
m :: Matrix Double
ghci> m
(3><3)
 [ 1.0, 2.0, 3.0
 , 4.0, 5.0, 6.0
 , 7.0, 8.0, 9.0 ]

то вы можете извлечь его диагональ следующим образом:

ghci> takeDiag m
3 |> [1.0,5.0,9.0]