Donc j'ai un arbre simple:
class MyNode
{
public MyNode Parent;
public IEnumerable<MyNode> Elements;
int group = 1;
}
J'ai un IEnumerable<MyNode>
. Je veux obtenir une liste de tous MyNode
(y compris les objets de nœud interne ( Elements
)) sous la forme d'une liste plate Where
group == 1
. Comment faire une telle chose via LINQ?
Elements
est nul ou vide?Réponses:
Vous pouvez aplatir un arbre comme ceci:
Vous pouvez ensuite filtrer en
group
utilisantWhere(...)
.Pour gagner des "points pour le style", convertissez-vous
Flatten
en une fonction d'extension dans une classe statique.Pour gagner plus de points pour un "style encore meilleur", convertissez-vous
Flatten
en une méthode d'extension générique qui prend un arbre et une fonction qui produit des descendants à partir d'un nœud:Appelez cette fonction comme ceci:
Si vous préférez aplatir en pré-commande plutôt qu'en post-commande, changez les côtés du fichier
Concat(...)
.la source
Concat
devrait êtrenew[] {e}
, nonnew[] {c}
(il ne serait même pas compilé avecc
).c
. L'utilisatione
ne compile pas. Vous pouvez également ajouterif (e == null) return Enumerable.Empty<T>();
pour gérer les listes enfants nulles.Le problème avec la réponse acceptée est qu'elle est inefficace si l'arbre est profond. Si l'arbre est très profond, il fait exploser la pile. Vous pouvez résoudre le problème en utilisant une pile explicite:
En supposant n nœuds dans un arbre de hauteur h et un facteur de branchement considérablement inférieur à n, cette méthode est O (1) dans l'espace de pile, O (h) dans l'espace de tas et O (n) dans le temps. L'autre algorithme donné est O (h) dans la pile, O (1) dans le tas et O (nh) dans le temps. Si le facteur de branchement est petit par rapport à n, alors h est compris entre O (lg n) et O (n), ce qui montre que l'algorithme naïf peut utiliser une quantité dangereuse de pile et une grande quantité de temps si h est proche de n.
Maintenant que nous avons un parcours, votre requête est simple:
la source
Traverse
à tous les éléments. Ou vous pouvez modifierTraverse
pour prendre une séquence et lui faire pousser tous les éléments de la séquencestack
. Souvenez-vous,stack
c'est "des éléments que je n'ai pas encore traversés". Ou vous pouvez créer une racine «factice» où votre séquence est ses enfants, puis traverser la racine factice.foreach (var child in current.Elements.Reverse())
vous obtiendrez un aplatissement plus attendu. En particulier, les enfants apparaîtront dans l'ordre dans lequel ils apparaissent plutôt que le dernier enfant en premier. Cela ne devrait pas avoir d'importance dans la plupart des cas, mais dans mon cas, j'avais besoin que l'aplatissement soit dans un ordre prévisible et attendu..Reverse
en échangeant leStack<T>
pour unQueue<T>
Reverse
est qu'il crée des itérateurs supplémentaires, ce que cette approche est censée éviter. @RubensFarias La substitutionQueue
desStack
résultats dans le parcours en largeur d'abord.Pour être complet, voici la combinaison des réponses de dasblinkenlight et d'Eric Lippert. Unité testée et tout. :-)
la source
Mettre à jour:
Pour les personnes intéressées par le niveau de nidification (profondeur). L'un des avantages de l'implémentation explicite de la pile d'énumérateurs est qu'à tout moment (et en particulier lors de la production de l'élément), le
stack.Count
représente la profondeur de traitement en cours. Donc, en prenant cela en compte et en utilisant les tuples de valeur C # 7.0, nous pouvons simplement modifier la déclaration de méthode comme suit:et
yield
déclaration:Ensuite, nous pouvons implémenter la méthode originale en appliquant simple
Select
sur ce qui précède:Original:
Étonnamment, personne (même Eric) n'a montré le port itératif "naturel" d'un DFT de pré-commande récursif, alors le voici:
la source
e
chaque fois que vous appelezelementSelector
pour maintenir la pré-commande - si la commande n'avait pas d'importance, pourriez-vous changer la fonction pour traiter chacune
une fois commencé?Queue<T>
. Quoi qu'il en soit, l'idée ici est de garder une petite pile avec des énumérateurs, très similaire à ce qui se passe dans l'implémentation récursive.Stack
cela entraînerait une première traversée en largeur en zig-zag.J'ai trouvé quelques petits problèmes avec les réponses données ici:
Basé sur les réponses précédentes et est venu avec ce qui suit:
Et les tests unitaires:
la source
Au cas où quelqu'un d'autre trouverait cela, mais aurait également besoin de connaître le niveau après avoir aplati l'arbre, cela étend la combinaison de Konamiman de dasblinkenlight et des solutions d'Eric Lippert:
la source
Une autre option consiste à avoir une conception OO appropriée.
par exemple, demandez au
MyNode
pour retourner tout aplatir.Comme ça:
Vous pouvez maintenant demander au MyNode de niveau supérieur d'obtenir tous les nœuds.
Si vous ne pouvez pas modifier la classe, ce n'est pas une option. Mais sinon, je pense que cela pourrait être préféré d'une méthode LINQ séparée (récursive).
Ceci utilise LINQ, donc je pense que cette réponse est applicable ici;)
la source
la source
Combiner la réponse de Dave et Ivan Stoev au cas où vous auriez besoin du niveau d'imbrication et de la liste aplatie "dans l'ordre" et non inversée comme dans la réponse donnée par Konamiman.
la source
Sur la base de la réponse de Konamiman et du commentaire selon lequel la commande est inattendue, voici une version avec un paramètre de tri explicite:
Et un exemple d'utilisation:
la source
Vous trouverez ci-dessous le code d'Ivan Stoev avec la fonctionnalité supplémentaire de dire l'index de chaque objet dans le chemin. Par exemple, recherchez "Item_120":
retournerait l'élément et un tableau int [1,2,0]. Évidemment, le niveau d'imbrication est également disponible, en tant que longueur du tableau.
la source
Voici quelques implémentations prêtes à l'emploi utilisant Queue et renvoyant l'arborescence Flatten moi d'abord, puis mes enfants.
la source
De temps en temps, j'essaie de gratter ce problème et de concevoir ma propre solution qui prend en charge des structures arbitrairement profondes (pas de récursivité), effectue une première traversée en largeur et n'abuse pas trop de requêtes LINQ ou n'exécute pas de manière préventive la récursivité sur les enfants. Après avoir fouillé dans la source .NET et essayé de nombreuses solutions, j'ai enfin trouvé cette solution. Cela a fini par être très proche de la réponse d'Ian Stoev (dont je n'ai vu que la réponse tout à l'heure), mais la mienne n'utilise pas de boucles infinies ou n'a pas de flux de code inhabituel.
Un exemple fonctionnel peut être trouvé ici .
la source