Entonces tengo un árbol simple:
class MyNode
{
public MyNode Parent;
public IEnumerable<MyNode> Elements;
int group = 1;
}
Tengo un IEnumerable<MyNode>
. Quiero obtener una lista de todos MyNode
(incluidos los objetos de nodo interno ( Elements
)) como una lista plana Where
group == 1
. ¿Cómo hacer tal cosa a través de LINQ?
Elements
es nulo o vacío?Respuestas:
Puedes aplanar un árbol como este:
A continuación, puede filtrar
group
utilizandoWhere(...)
.Para ganar algunos "puntos por estilo", conviértalo
Flatten
en una función de extensión en una clase estática.Para ganar más puntos por "un estilo aún mejor", conviértalo
Flatten
a un método de extensión genérico que toma un árbol y una función que produce descendientes de un nodo:Llame a esta función así:
Si prefiere aplanar en preorden en lugar de posorder, cambie los lados del
Concat(...)
.fuente
Concat
debería sernew[] {e}
, nonew[] {c}
(ni siquiera se compilaríac
allí).c
. El usoe
no se compila. También puede agregarif (e == null) return Enumerable.Empty<T>();
para hacer frente a listas de niños nulas.El problema con la respuesta aceptada es que es ineficaz si el árbol es profundo. Si el árbol es muy profundo, hace volar la pila. Puede resolver el problema utilizando una pila explícita:
Suponiendo n nodos en un árbol de altura h y un factor de ramificación considerablemente menor que n, este método es O (1) en el espacio de la pila, O (h) en el espacio del montón y O (n) en el tiempo. El otro algoritmo dado es O (h) en la pila, O (1) en el montón y O (nh) en el tiempo. Si el factor de ramificación es pequeño en comparación con n, entonces h está entre O (lg n) y O (n), lo que ilustra que el algoritmo ingenuo puede usar una cantidad peligrosa de pila y una gran cantidad de tiempo si h está cerca de n.
Ahora que tenemos un recorrido, su consulta es sencilla:
fuente
Traverse
a todos los elementos. O puede modificarTraverse
para tomar una secuencia y hacer que inserte todos los elementos de la secuenciastack
. Recuerde,stack
son "elementos que aún no he atravesado". O puede hacer una raíz "ficticia" donde su secuencia sean sus hijos y luego atravesar la raíz ficticia.foreach (var child in current.Elements.Reverse())
, obtendrá un aplanamiento más esperado. En particular, los niños aparecerán en el orden en que aparecen en lugar del último niño primero. Esto no debería importar en la mayoría de los casos, pero en mi caso necesitaba que el aplanamiento estuviera en un orden predecible y esperado..Reverse
cambiando elStack<T>
por aQueue<T>
Reverse
es que crea iteradores adicionales, que es lo que se pretende evitar con este enfoque. @RubensFarias La sustituciónQueue
deStack
los resultados en el recorrido primero en amplitud.Solo para completar, aquí está la combinación de las respuestas de dasblinkenlight y Eric Lippert. Unidad probada y todo. :-)
fuente
Actualizar:
Para personas interesadas en el nivel de anidación (profundidad). Una de las cosas buenas de la implementación explícita de la pila de enumeradores es que en cualquier momento (y en particular cuando se obtiene el elemento)
stack.Count
representa la profundidad de procesamiento actual. Entonces, teniendo esto en cuenta y utilizando las tuplas de valor de C # 7.0, podemos simplemente cambiar la declaración del método de la siguiente manera:y
yield
declaración:Entonces podemos implementar el método original aplicando simple
Select
en lo anterior:Original:
Sorprendentemente, nadie (ni siquiera Eric) mostró el puerto iterativo "natural" de una DFT recursiva de reserva, así que aquí está:
fuente
e
cada vez que llamaelementSelector
para mantener el pedido anticipado; si el orden no importa, ¿podría cambiar la función para procesar cada unoe
una vez iniciado?Queue<T>
. De todos modos, la idea aquí es mantener una pequeña pila con enumeradores, muy similar a lo que está sucediendo en la implementación recursiva.Stack
daría como resultado un primer recorrido de ancho en zig-zag.Encontré algunos pequeños problemas con las respuestas dadas aquí:
Se basó en las respuestas anteriores y se le ocurrió lo siguiente:
Y las pruebas unitarias:
fuente
En caso de que alguien más encuentre esto, pero también necesite saber el nivel después de haber aplanado el árbol, esto amplía la combinación de Konamiman de dasblinkenlight y las soluciones de Eric Lippert:
fuente
Una opción realmente diferente es tener un diseño orientado a objetos adecuado.
por ejemplo, pregunte al
MyNode
que devuelva todo aplanar.Me gusta esto:
Ahora puede pedirle al MyNode de nivel superior que obtenga todos los nodos.
Si no puede editar la clase, esta no es una opción. Pero de lo contrario, creo que esto podría preferirse de un método LINQ separado (recursivo).
Esto está usando LINQ, así que creo que esta respuesta es aplicable aquí;)
fuente
fuente
Combinando la respuesta de Dave e Ivan Stoev en caso de que necesite el nivel de anidamiento y la lista aplanada "en orden" y no invertida como en la respuesta dada por Konamiman.
fuente
Sobre la base de la respuesta de Konamiman y el comentario de que el orden es inesperado, aquí hay una versión con un parámetro de clasificación explícito:
Y un uso de muestra:
fuente
A continuación se muestra el código de Ivan Stoev con la característica adicional de decir el índice de cada objeto en la ruta. Por ejemplo, busque "Item_120":
devolvería el elemento y una matriz int [1,2,0]. Obviamente, el nivel de anidamiento también está disponible, como longitud de la matriz.
fuente
Aquí hay una implementación lista para usar usando Queue y devolviéndome el árbol Flatten primero y luego a mis hijos.
fuente
De vez en cuando trato de abordar este problema e idear mi propia solución que admita estructuras arbitrariamente profundas (sin recursividad), realice un primer recorrido amplio y no abuse de demasiadas consultas LINQ o ejecute de forma preventiva la recursividad en los hijos. Después de buscar en la fuente .NET y probar muchas soluciones, finalmente se me ocurrió esta solución. Terminó estando muy cerca de la respuesta de Ian Stoev (cuya respuesta solo vi ahora), sin embargo, la mía no utiliza bucles infinitos ni tiene un flujo de código inusual.
Puede encontrar un ejemplo práctico aquí .
fuente