Estoy interesado en escribir una función eficiente de Haskell triangularize :: [a] -> [[a]]
que tome una lista (quizás infinita) y la "triangularice" en una lista de listas. Por ejemplo, triangularize [1..19]
debería volver
[[1, 3, 6, 10, 15]
,[2, 5, 9, 14]
,[4, 8, 13, 19]
,[7, 12, 18]
,[11, 17]
,[16]]
Por eficiente, quiero decir que quiero que se ejecute a O(n)
tiempo donde n
está la longitud de la lista.
Tenga en cuenta que esto es bastante fácil de hacer en un lenguaje como Python, porque agregar al final de una lista (matriz) es una operación de tiempo constante. Una función de Python muy imprescindible que logra esto es:
def triangularize(elements):
row_index = 0
column_index = 0
diagonal_array = []
for a in elements:
if row_index == len(diagonal_array):
diagonal_array.append([a])
else:
diagonal_array[row_index].append(a)
if row_index == 0:
(row_index, column_index) = (column_index + 1, 0)
else:
row_index -= 1
column_index += 1
return diagonal_array
Esto surgió porque he estado usando Haskell para escribir algunas secuencias "tabl" en la Enciclopedia en línea de secuencias enteras (OEIS), y quiero poder transformar una secuencia ordinaria (unidimensional) en un (2- dimensional) secuencia de secuencias exactamente de esta manera.
Quizás haya alguna forma inteligente (o no tan inteligente) de foldr
sobrepasar la lista de entrada, pero no he podido resolverlo.
fuente
foldr
lo que te gustaunfoldr (Just . combWith comb)
para listas infinitas. Por desgracia, como he mencionado en mi respuestacombWith
es O (n), por lo tanto, la respuesta aceptadasplitAt
es significativamente más eficiente.Respuestas:
Haga trozos de tamaño creciente:
Luego solo transponga dos veces:
Pruébalo en ghci:
fuente
transpose
sea O (n). Tampoco estoy muy seguro de que no lo sea, ¡su implementación es algo complicada!take 3 . map (take 3) . diagonalize $ [1..]
da[[1,3,6],[2,5,9],[4,8,13]]
, lo que parece estar bien.take 10 $ map (take 10) $ diagonalize [1..]
de hecho da los primeros diez elementos de las primeras diez filas.Esto parece estar directamente relacionado con el argumento de la teoría de conjuntos que demuestra que el conjunto de pares de enteros están en correspondencia uno a uno con el conjunto de enteros ( numerable ). El argumento involucra una llamada función de emparejamiento de Cantor .
Entonces, por curiosidad, veamos si podemos obtener una
diagonalize
función de esa manera. Defina la lista infinita de pares de Cantor recursivamente en Haskell:Y prueba eso dentro de ghci:
Podemos numerar los pares y, por ejemplo, extraer los números de esos pares que tienen una coordenada cero x:
Reconocemos que esta es la fila superior del resultado del OP en el texto de la pregunta. Del mismo modo para las siguientes dos filas:
A partir de ahí, podemos escribir nuestro primer borrador de una
diagonalize
función:EDITAR: actualización de rendimiento
Para una lista de 1 millón de elementos, el tiempo de ejecución es de 18 segundos y 145 segundos para 4 millones de elementos. Como mencionó Redu, esto parece una complejidad O (n√n).
La distribución de los pares entre las diversas sublistas objetivo es ineficiente, ya que la mayoría de las operaciones de filtro fallan.
Para mejorar el rendimiento, podemos usar una estructura Data.Map para las sublistas objetivo.
Con esa segunda versión, el rendimiento parece ser mucho mejor: 568 ms para la lista de elementos de 1 millón, 2669 ms para la lista de elementos de 4 millones. Por lo tanto, está cerca de la complejidad O (n * Log (n)) que podríamos haber esperado.
fuente
Puede ser una buena idea crear un
comb
filtro.Entonces, ¿qué hace el
comb
filtro ...? Es comosplitAt
pero en lugar de la división en un único índice que tipo de cremalleras la lista infinita dada con el peine dada para separar los elementos de coresspondingTrue
yFalse
en el peine. Tal que;Ahora todo lo que tenemos que hacer es peinar nuestra lista infinita y tomar el
fst
como la primera fila y continuar peinandosnd
con la mismacomb
.Vamos a hacerlo;
También parece ser perezoso también :)
Creo que la complejidad podría ser como O (n√n) pero no puedo asegurarme. Algunas ideas..?
fuente
:set +s
. Al hacerlo, la respuesta aceptada de @Daniel Wagner parece estar funcionando bastante rápido con el tipo de lista. ¿Podrías comprobar si se compara con el tuyo? Tenía la esperanza de lograr un rendimiento similar, pero encombWith
ninguna parte es tan rápido comospilitAt
.transpose
parecen infundados. Además de eso, parece más amigable con la pereza que el mapa de Cantor. Bien hecho !snd
delsplitAt
's se obtiene en O (1) perofst
todavía debería ser O (n). De alguna manera, esto se refleja en el rendimiento general como O (nlogn).splitAt
lugar de llamardrop
y portake
separado.