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 nestá 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 foldrsobrepasar la lista de entrada, pero no he podido resolverlo.
fuente

foldrlo que te gustaunfoldr (Just . combWith comb)para listas infinitas. Por desgracia, como he mencionado en mi respuestacombWithes O (n), por lo tanto, la respuesta aceptadasplitAtes significativamente más eficiente.Respuestas:
Haga trozos de tamaño creciente:
Luego solo transponga dos veces:
Pruébalo en ghci:
fuente
transposesea 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
diagonalizefunció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
diagonalizefunció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
combfiltro.Entonces, ¿qué hace el
combfiltro ...? Es comosplitAtpero 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 coresspondingTrueyFalseen el peine. Tal que;Ahora todo lo que tenemos que hacer es peinar nuestra lista infinita y tomar el
fstcomo la primera fila y continuar peinandosndcon 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 encombWithninguna parte es tan rápido comospilitAt.transposeparecen infundados. Además de eso, parece más amigable con la pereza que el mapa de Cantor. Bien hecho !snddelsplitAt's se obtiene en O (1) perofsttodavía debería ser O (n). De alguna manera, esto se refleja en el rendimiento general como O (nlogn).splitAtlugar de llamardropy portakeseparado.