Portanto, tenho uma árvore simples:
class MyNode
{
public MyNode Parent;
public IEnumerable<MyNode> Elements;
int group = 1;
}
Eu tenho um IEnumerable<MyNode>
. Quero obter uma lista de todos MyNode
(incluindo objetos de nó interno ( Elements
)) como uma lista plana Where
group == 1
. Como fazer isso via LINQ?
Elements
é nulo ou vazio?Respostas:
Você pode achatar uma árvore assim:
Você pode filtrar
group
usandoWhere(...)
.Para ganhar alguns "pontos por estilo", converta
Flatten
para uma função de extensão em uma classe estática.Para ganhar mais pontos por um "estilo ainda melhor", converta
Flatten
para um método de extensão genérico que pega uma árvore e uma função que produz descendentes de um nó:Chame essa função assim:
Se você preferir achatar na pré-encomenda em vez de na pós-encomenda, alterne as laterais do
Concat(...)
.fonte
Concat
deveria sernew[] {e}
, nãonew[] {c}
(ele nem seria compilado comc
lá).c
. Usare
não compila. Você também pode adicionarif (e == null) return Enumerable.Empty<T>();
para lidar com listas de filhos nulos.O problema com a resposta aceita é que ela é ineficiente se a árvore for profunda. Se a árvore for muito profunda, ela estraga a pilha. Você pode resolver o problema usando uma pilha explícita:
Assumindo n nós em uma árvore de altura he um fator de ramificação consideravelmente menor que n, este método é O (1) no espaço da pilha, O (h) no espaço da pilha e O (n) no tempo. O outro algoritmo fornecido é O (h) na pilha, O (1) na pilha e O (nh) no tempo. Se o fator de ramificação for pequeno em comparação com n, então h está entre O (lg n) e O (n), o que ilustra que o algoritmo ingênuo pode usar uma quantidade perigosa de pilha e uma grande quantidade de tempo se h for próximo de n.
Agora que temos uma travessia, sua consulta é direta:
fonte
Traverse
todos os elementos. Ou você pode modificarTraverse
para obter uma sequência e fazer com que ela insira todos os elementos da sequênciastack
. Lembre-se,stack
é "elementos que ainda não atravessei". Ou você pode fazer uma raiz "dummy" onde sua sequência são seus filhos e, em seguida, atravessar a raiz fictícia.foreach (var child in current.Elements.Reverse())
isso, obterá um nivelamento mais esperado. Em particular, os filhos aparecerão na ordem em que aparecem, em vez do último filho primeiro. Isso não deveria importar na maioria dos casos, mas no meu caso eu precisava que o nivelamento estivesse em uma ordem previsível e esperada..Reverse
trocando oStack<T>
por umQueue<T>
Reverse
é que ela cria iteradores adicionais, que é o que essa abordagem pretende evitar. @RubensFarias SubstituindoQueue
porStack
resultados em travessia em largura.Apenas para completar, aqui está a combinação das respostas de dasblinkenlight e Eric Lippert. Unidade testada e tudo mais. :-)
fonte
Atualizar:
Para pessoas interessadas no nível de aninhamento (profundidade). Uma das coisas boas sobre a implementação explícita da pilha do enumerador é que a qualquer momento (e em particular ao produzir o elemento), o
stack.Count
representa a profundidade de processamento atual. Portanto, levando isso em consideração e utilizando as tuplas de valor do C # 7.0, podemos simplesmente alterar a declaração do método da seguinte maneira:e
yield
declaração:Então podemos implementar o método original aplicando simples
Select
no acima:Original:
Surpreendentemente, ninguém (nem mesmo Eric) mostrou a porta iterativa "natural" de uma DFT de pré-encomenda recursiva, então aqui está:
fonte
e
cada vez que ligarelementSelector
para manter a encomenda - se a ordem não importasse, você poderia alterar a função para processar tudo umae
vez iniciado?Queue<T>
. De qualquer forma, a ideia aqui é manter uma pequena pilha com os enumeradores, bem parecida com o que está acontecendo na implementação recursiva.Stack
resultaria em um primeiro cruzamento de largura em zigue-zague.Encontrei alguns pequenos problemas com as respostas fornecidas aqui:
Com base nas respostas anteriores e chegou ao seguinte:
E os testes de unidade:
fonte
Caso alguém encontre isso, mas também precise saber o nível depois de achatar a árvore, isso se expande na combinação de dasblinkenlight da Konamiman e nas soluções de Eric Lippert:
fonte
Uma opção realmente diferente é ter um design OO adequado.
por exemplo, peça ao
MyNode
para retornar todo plano.Como isso:
Agora você pode pedir ao nível superior MyNode para obter todos os nós.
Se você não pode editar a classe, então esta não é uma opção. Mas, caso contrário, acho que isso poderia ser preferido de um método LINQ separado (recursivo).
Isso é usando LINQ, então eu acho que esta resposta é aplicável aqui;)
fonte
fonte
Combinando a resposta de Dave e Ivan Stoev no caso de você precisar do nível de aninhamento e da lista nivelada "na ordem" e não invertida como na resposta dada pela Konamiman.
fonte
Com base na resposta da Konamiman e no comentário de que a ordem é inesperada, aqui está uma versão com um parâmetro de classificação explícito:
E um exemplo de uso:
fonte
Abaixo está o código de Ivan Stoev com o recurso adicional de informar o índice de cada objeto no caminho. Por exemplo, pesquise "Item_120":
retornaria o item e um array int [1,2,0]. Obviamente, o nível de aninhamento também está disponível, conforme o comprimento da matriz.
fonte
Aqui está uma implementação pronta para usar usando Queue e retornando a árvore Flatten para mim primeiro e depois para meus filhos.
fonte
De vez em quando, tento resolver esse problema e conceber minha própria solução que ofereça suporte a estruturas arbitrariamente profundas (sem recursão), execute a travessia de largura primeiro e não abuse de muitas consultas LINQ ou execute preventivamente a recursão nos filhos. Depois de vasculhar o código - fonte .NET e tentar muitas soluções, finalmente descobri essa solução. Acabou ficando muito próximo da resposta de Ian Stoev (cuja resposta eu só vi agora), porém o meu não utiliza loops infinitos ou tem fluxo de código incomum.
Um exemplo prático pode ser encontrado aqui .
fonte