Después de trabajar con 2-3 árboles de dedos durante bastante tiempo, me ha impresionado su velocidad en la mayoría de las operaciones. Sin embargo, el único problema con el que me he encontrado es la gran sobrecarga asociada con la creación inicial de un gran árbol de dedos. Debido a que la construcción se define como una secuencia de operaciones de concatenación, terminas construyendo una gran cantidad de estructuras de árbol de dedos que no son necesarias.
Debido a la naturaleza compleja de los árboles de 2-3 dedos, no veo ningún método intuitivo para arrancarlos, y todas mis búsquedas han quedado vacías. Entonces, la pregunta es, ¿cómo podría usted arrancar un árbol de 2-3 dedos con una sobrecarga mínima?
Para ser explícito: dada una secuencia de longitud conocida genere la representación del árbol de dedos de con operaciones mínimas.
La manera ingenua de lograr es llamadas sucesivas a la operación contras (en la literatura el operador ' '). Sin embargo, esto creará estructuras de árbol de dedos distintas que representan todos los cortes de para .n S [ 1 .. i ]
fuente
Respuestas:
GHCO ( lgn )
Data.Sequence
'sreplicate
función construye una fingertree en el tiempo y el espacio, pero esto es posible gracias conocer los elementos que intervienen en la columna derecha del árbol dedo desde el primer momento. Esta biblioteca fue escrita por los autores del artículo original sobre árboles de 2-3 dedos.Si desea construir un árbol de dedos mediante concatenación repetida, puede reducir el uso de espacio transitorio mientras construye cambiando la representación de las espinas. Las espinas de los árboles de 2-3 dedos se almacenan inteligentemente como listas sincronizadas de enlaces individuales. Si, en cambio, almacena las espinas como calcas, puede ser posible ahorrar espacio al concatenar árboles. La idea es que concatenar dos árboles de la misma altura ocupa espacio reutilizando las espinas de los árboles. Al concatenar 2-3 árboles de dedos como se describió originalmente, las espinas internas del nuevo árbol ya no se pueden usar como están.O( 1 )
Las "Representaciones puramente funcionales de listas ordenadas catenables" de Kaplan y Tarjan describen una estructura de árbol de dedos más complicada. Este documento (en la sección 4) también analiza una construcción similar a la sugerencia de deque que hice anteriormente. Creo que la estructura que describen puede concatenar dos árboles de igual altura en tiempo y espacio. Para construir árboles de dedos, ¿es suficiente para usted ahorrar espacio?O ( 1 )
NB: Su uso de la palabra "bootstrapping" significa algo un poco diferente a su uso anterior. Significan almacenar parte de una estructura de datos utilizando una versión más simple de la misma estructura.
fuente
Refiriéndose a la excelente respuesta de jbapple con respecto a
replicate
, pero usandoreplicateA
(quereplicate
se basa en) en su lugar, se me ocurrió lo siguiente:myFromList
(en una versión ligeramente más eficiente) ya está definido y utilizado internamente enData.Sequence
para la construcción de árboles de los dedos que son resultados de las clases.En general, la intuición para
replicateA
es simple.replicateA
está construido encima de la función applytiveTree .applicativeTree
toma un pedazo de un árbol de un tamañom
y produce un árbol bien equilibrado que contienen
copias de este. Las cajas paran
hasta 8 (un soloDeep
dedo) están codificadas. Cualquier cosa por encima de esto, y se invoca recursivamente. El elemento "aplicativo" es simplemente que intercala la construcción del árbol con efectos de enhebrado, como, en el caso del código anterior, el estado.La
go
función, que se replica, es simplemente una acción que obtiene el estado actual, saca un elemento de la parte superior y reemplaza el resto. En cada invocación, por lo tanto, baja más abajo en la lista proporcionada como entrada.Algunas notas más concretas
En algunas pruebas simples, esto produjo una compensación de rendimiento interesante. La función principal anterior se ejecutó casi 1/3 más bajo con myFromList que con
fromList
. Por otro lado,myFromList
usó un montón constante de 2 MB, mientras que el estándarfromList
usó hasta 926 MB. Ese 926 MB surge de la necesidad de mantener toda la lista en la memoria a la vez. Mientras tanto, la soluciónmyFromList
es capaz de consumir la estructura de forma perezosa. El problema con la velocidad resulta del hecho de quemyFromList
debe realizar aproximadamente el doble de asignaciones (como resultado de la construcción / destrucción del par de la mónada estatal) quefromList
. Podemos eliminar esas asignaciones moviéndonos a una mónada de estado transformada por CPS, pero eso da como resultado retener mucha más memoria en un momento dado, porque la pérdida de la pereza requiere atravesar la lista de manera no continua.Por otro lado, si en lugar de forzar la secuencia completa con un espectáculo, me muevo a solo extraer la cabeza o el último elemento,
myFromList
inmediatamente presenta una ganancia mayor: extraer el elemento de la cabeza es casi instantáneo y extraer el último elemento es 0.8s . Mientras tanto, con el estándarfromList
, extraer la cabeza o el último elemento cuesta ~ 2.3 segundos.Todo esto son detalles, y es consecuencia de la pureza y la pereza. En una situación con mutación y acceso aleatorio, me imagino que la
replicate
solución es estrictamente mejor.Sin embargo, plantea la cuestión de si hay una manera de reescribir
applicativeTree
tal quemyFromList
sea estrictamente más eficiente. Creo que el problema es que las acciones aplicativas se ejecutan en un orden diferente al que se atraviesa naturalmente el árbol, pero no he trabajado completamente sobre cómo funciona esto, o si hay una manera de resolverlo.fuente
fromList
cuando se fuerza toda la secuencia. (2) Tal vez esta respuesta sea demasiado pesada y dependiente del lenguaje para cstheory.stackexchange.com. Sería genial si pudiera agregar una explicación de cómoreplicateA
funciona de manera independiente del idioma.Si bien terminas con una gran cantidad de estructuras intermedias de árbol de dedos, comparten la gran mayoría de su estructura entre sí. Al final, asigna como máximo el doble de memoria que en el caso idealizado, y el resto se libera con la primera colección. Los asintóticos de esto son los mismos que pueden obtener, ya que al final necesita un dedo lleno de n valores.
Puede construir el árbol de dedos usando
Data.FingerTree.replicate
y usandoFingerTree.fmapWithPos
para buscar sus valores en una matriz que desempeña el papel de su secuencia finita, o usandotraverseWithPos
para despegarlos de una lista u otro contenedor de tamaño conocido.replicateA
mapAccumL
TL; DR Si tuviera que hacer esto, probablemente usaría:
e indexar en una matriz de tamaño fijo que simplemente proporcionaría
(arr !)
paraf
arriba.fuente