J'ai un tas d'objets dans une structure plate. Ces objets ont une propriété ID
et une propriété ParentID
afin qu'ils puissent être disposés dans des arbres. Elles ne sont dans aucun ordre particulier . Chaque propriété ParentID
ne correspond pas nécessairement à un ID
dans la structure. Par conséquent, il pourrait y avoir plusieurs arbres émergeant de ces objets.
Comment traiteriez-vous ces objets pour créer les arbres résultants?
Je ne suis pas si loin d'une solution mais je suis sûr que c'est loin d'être optimal ...
J'ai besoin de créer ces arbres pour ensuite insérer des données dans une base de données, dans le bon ordre.
Il n'y a pas de références circulaires. Un nœud est un RootNode lorsque ParentID == null ou lorsque ParentID est introuvable dans les autres objets.
Enregistre les identifiants des objets dans une table de hachage mappée sur l'objet spécifique. Énumérez tous les objets et recherchez leur parent s'il existe et mettez à jour son pointeur parent en conséquence.
class MyObject
{ // The actual object
public int ParentID { get; set; }
public int ID { get; set; }
}
class Node
{
public List<Node> Children = new List<Node>();
public Node Parent { get; set; }
public MyObject AssociatedObject { get; set; }
}
IEnumerable<Node> BuildTreeAndGetRoots(List<MyObject> actualObjects)
{
Dictionary<int, Node> lookup = new Dictionary<int, Node>();
actualObjects.ForEach(x => lookup.Add(x.ID, new Node { AssociatedObject = x }));
foreach (var item in lookup.Values) {
Node proposedParent;
if (lookup.TryGetValue(item.AssociatedObject.ParentID, out proposedParent)) {
item.Parent = proposedParent;
proposedParent.Children.Add(item);
}
}
return lookup.Values.Where(x => x.Parent == null);
}
Basé sur la réponse de Mehrdad Afshari et le commentaire d'Andrew Hanlon pour une accélération, voici ce que je pense.
Différence importante par rapport à la tâche d'origine: un nœud racine a un ID == parentID.
class MyObject
{ // The actual object
public int ParentID { get; set; }
public int ID { get; set; }
}
class Node
{
public List<Node> Children = new List<Node>();
public Node Parent { get; set; }
public MyObject Source { get; set; }
}
List<Node> BuildTreeAndGetRoots(List<MyObject> actualObjects)
{
var lookup = new Dictionary<int, Node>();
var rootNodes = new List<Node>();
foreach (var item in actualObjects)
{
// add us to lookup
Node ourNode;
if (lookup.TryGetValue(item.ID, out ourNode))
{ // was already found as a parent - register the actual object
ourNode.Source = item;
}
else
{
ourNode = new Node() { Source = item };
lookup.Add(item.ID, ourNode);
}
// hook into parent
if (item.ParentID == item.ID)
{ // is a root node
rootNodes.Add(ourNode);
}
else
{ // is a child row - so we have a parent
Node parentNode;
if (!lookup.TryGetValue(item.ParentID, out parentNode))
{ // unknown parent, construct preliminary parent
parentNode = new Node();
lookup.Add(item.ParentID, parentNode);
}
parentNode.Children.Add(ourNode);
ourNode.Parent = parentNode;
}
}
return rootNodes;
}
Voici un algorithme JavaScript simple permettant d'analyser une table plate dans une arborescence parent/enfant s'exécutant dans un temps N:
var table = [
{parent_id: 0, id: 1, children: []},
{parent_id: 0, id: 2, children: []},
{parent_id: 0, id: 3, children: []},
{parent_id: 1, id: 4, children: []},
{parent_id: 1, id: 5, children: []},
{parent_id: 1, id: 6, children: []},
{parent_id: 2, id: 7, children: []},
{parent_id: 7, id: 8, children: []},
{parent_id: 8, id: 9, children: []},
{parent_id: 3, id: 10, children: []}
];
var root = {id:0, parent_id: null, children: []};
var node_list = { 0 : root};
for (var i = 0; i < table.length; i++) {
node_list[table[i].id] = table[i];
node_list[table[i].parent_id].children.Push(node_list[table[i].id]);
}
console.log(root);
Solution python
def subtree(node, relationships):
return {
v: subtree(v, relationships)
for v in [x[0] for x in relationships if x[1] == node]
}
Par exemple:
# (child, parent) pairs where -1 means no parent
flat_tree = [
(1, -1),
(4, 1),
(10, 4),
(11, 4),
(16, 11),
(17, 11),
(24, 17),
(25, 17),
(5, 1),
(8, 5),
(9, 5),
(7, 9),
(12, 9),
(22, 12),
(23, 12),
(2, 23),
(26, 23),
(27, 23),
(20, 9),
(21, 9)
]
subtree(-1, flat_tree)
Produit:
{
"1": {
"4": {
"10": {},
"11": {
"16": {},
"17": {
"24": {},
"25": {}
}
}
},
"5": {
"8": {},
"9": {
"20": {},
"12": {
"22": {},
"23": {
"2": {},
"27": {},
"26": {}
}
},
"21": {},
"7": {}
}
}
}
}
Version JS qui renvoie une racine ou un tableau de racines, chacune d'elles ayant une propriété de tableau d'enfants contenant les enfants associés. Ne dépend pas d'une entrée ordonnée, décente compacte et n'utilise pas de récursivité. Prendre plaisir!
// creates a tree from a flat set of hierarchically related data
var MiracleGrow = function(treeData, key, parentKey)
{
var keys = [];
treeData.map(function(x){
x.Children = [];
keys.Push(x[key]);
});
var roots = treeData.filter(function(x){return keys.indexOf(x[parentKey])==-1});
var nodes = [];
roots.map(function(x){nodes.Push(x)});
while(nodes.length > 0)
{
var node = nodes.pop();
var children = treeData.filter(function(x){return x[parentKey] == node[key]});
children.map(function(x){
node.Children.Push(x);
nodes.Push(x)
});
}
if (roots.length==1) return roots[0];
return roots;
}
// demo/test data
var treeData = [
{id:9, name:'Led Zep', parent:null},
{id:10, name:'Jimmy', parent:9},
{id:11, name:'Robert', parent:9},
{id:12, name:'John', parent:9},
{id:8, name:'Elec Gtr Strings', parent:5},
{id:1, name:'Rush', parent:null},
{id:2, name:'Alex', parent:1},
{id:3, name:'Geddy', parent:1},
{id:4, name:'Neil', parent:1},
{id:5, name:'Gibson Les Paul', parent:2},
{id:6, name:'Pearl Kit', parent:4},
{id:7, name:'Rickenbacker', parent:3},
{id:100, name:'Santa', parent:99},
{id:101, name:'Elf', parent:100},
];
var root = MiracleGrow(treeData, "id", "parent")
console.log(root)
J'ai trouvé une superbe version JavaScript ici: http://oskarhane.com/create-a-nested-array-recursively-in-javascript/
Disons que vous avez un tableau comme celui-ci:
const models = [
{id: 1, title: 'hello', parent: 0},
{id: 2, title: 'hello', parent: 0},
{id: 3, title: 'hello', parent: 1},
{id: 4, title: 'hello', parent: 3},
{id: 5, title: 'hello', parent: 4},
{id: 6, title: 'hello', parent: 4},
{id: 7, title: 'hello', parent: 3},
{id: 8, title: 'hello', parent: 2}
];
Et vous voulez avoir les objets imbriqués comme ceci:
const nestedStructure = [
{
id: 1, title: 'hello', parent: 0, children: [
{
id: 3, title: 'hello', parent: 1, children: [
{
id: 4, title: 'hello', parent: 3, children: [
{id: 5, title: 'hello', parent: 4},
{id: 6, title: 'hello', parent: 4}
]
},
{id: 7, title: 'hello', parent: 3}
]
}
]
},
{
id: 2, title: 'hello', parent: 0, children: [
{id: 8, title: 'hello', parent: 2}
]
}
];
Voici une fonction récursive qui le rend possible.
function getNestedChildren(models, parentId) {
const nestedTreeStructure = [];
const length = models.length;
for (let i = 0; i < length; i++) { // for-loop for perf reasons, huge difference in ie11
const model = models[i];
if (model.parent == parentId) {
const children = getNestedChildren(models, model.id);
if (children.length > 0) {
model.children = children;
}
nestedTreeStructure.Push(model);
}
}
return nestedTreeStructure;
}
Usuage:
const models = [
{id: 1, title: 'hello', parent: 0},
{id: 2, title: 'hello', parent: 0},
{id: 3, title: 'hello', parent: 1},
{id: 4, title: 'hello', parent: 3},
{id: 5, title: 'hello', parent: 4},
{id: 6, title: 'hello', parent: 4},
{id: 7, title: 'hello', parent: 3},
{id: 8, title: 'hello', parent: 2}
];
const nestedStructure = getNestedChildren(models, 0);
J'ai écrit une solution générique en C # basée sur la réponse de @Mehrdad Afshari:
void Example(List<MyObject> actualObjects)
{
List<TreeNode<MyObject>> treeRoots = actualObjects.BuildTree(obj => obj.ID, obj => obj.ParentID, -1);
}
public class TreeNode<T>
{
public TreeNode(T value)
{
Value = value;
Children = new List<TreeNode<T>>();
}
public T Value { get; private set; }
public List<TreeNode<T>> Children { get; private set; }
}
public static class TreeExtensions
{
public static List<TreeNode<TValue>> BuildTree<TKey, TValue>(this IEnumerable<TValue> objects, Func<TValue, TKey> keySelector, Func<TValue, TKey> parentKeySelector, TKey defaultKey = default(TKey))
{
var roots = new List<TreeNode<TValue>>();
var allNodes = objects.Select(overrideValue => new TreeNode<TValue>(overrideValue)).ToArray();
var nodesByRowId = allNodes.ToDictionary(node => keySelector(node.Value));
foreach (var currentNode in allNodes)
{
TKey parentKey = parentKeySelector(currentNode.Value);
if (Equals(parentKey, defaultKey))
{
roots.Add(currentNode);
}
else
{
nodesByRowId[parentKey].Children.Add(currentNode);
}
}
return roots;
}
}
une façon élégante de faire ceci est de représenter les éléments de la liste en tant que chaîne contenant une liste de parents séparés par des points, et enfin une valeur:
server.port=90
server.hostname=localhost
client.serverport=90
client.database.port=1234
client.database.Host=localhost
Lorsque vous assemblez un arbre, vous obtenez quelque chose comme:
server:
port: 90
hostname: localhost
client:
serverport=1234
database:
port: 1234
Host: localhost
J'ai un bibliothèque de configuration qui implémente cette configuration de remplacement (arborescence) à partir d'arguments de ligne de commande (liste). L'algorithme pour ajouter un seul élément à la liste dans un arbre est ici .
Aussi vague que la question me paraisse, je créerais probablement une carte de l'identifiant à l'objet réel. En pseudo-Java (je n'ai pas vérifié si ça marche/compile), ça pourrait être quelque chose comme:
Map<ID, FlatObject> flatObjectMap = new HashMap<ID, FlatObject>();
for (FlatObject object: flatStructure) {
flatObjectMap.put(object.ID, object);
}
Et pour chercher chaque parent:
private FlatObject getParent(FlatObject object) {
getRealObject(object.ParentID);
}
private FlatObject getRealObject(ID objectID) {
flatObjectMap.get(objectID);
}
En réutilisant getRealObject(ID)
et en créant une carte d'un objet à une collection d'objets (ou leurs ID), vous obtenez également une carte parent-> enfants.
Si vous êtes intéressé par une version C # de la solution d'Eugene, notez que node_list est accessible en tant que carte, utilisez donc un dictionnaire.
N'oubliez pas que cette solution ne fonctionne que si table est trié par parent_id .
var table = new[]
{
new Node { parent_id = 0, id = 1 },
new Node { parent_id = 0, id = 2 },
new Node { parent_id = 0, id = 3 },
new Node { parent_id = 1, id = 4 },
new Node { parent_id = 1, id = 5 },
new Node { parent_id = 1, id = 6 },
new Node { parent_id = 2, id = 7 },
new Node { parent_id = 7, id = 9 },
new Node { parent_id = 8, id = 9 },
new Node { parent_id = 3, id = 10 },
};
var root = new Node { id = 0 };
var node_list = new Dictionary<int, Node>{
{ 0, root }
};
foreach (var item in table)
{
node_list.Add(item.id, item);
node_list[item.parent_id].children.Add(node_list[item.id]);
}
Le nœud est défini comme suit.
class Node
{
public int id { get; set; }
public int parent_id { get; set; }
public List<Node> children = new List<Node>();
}
Je peux le faire en 4 lignes de code et en O (n log n), en supposant que Dictionary ressemble à un TreeMap.
dict := Dictionary new.
ary do: [:each | dict at: each id put: each].
ary do: [:each | (dict at: each parent) addChild: each].
root := dict at: nil.
EDIT: Ok, et maintenant je lis que certains parentsID sont des faux, alors oubliez ce qui précède et faites ceci:
dict := Dictionary new.
dict at: nil put: OrderedCollection new.
ary do: [:each | dict at: each id put: each].
ary do: [:each |
(dict at: each parent ifAbsent: [dict at: nil])
add: each].
roots := dict at: nil.
La réponse acceptée a l'air bien trop complexe pour moi, alors j'en ajoute une version Ruby et NodeJS
Supposons que la liste de nœuds plats ait la structure suivante:
nodes = [
{ id: 7, parent_id: 1 },
...
] # Ruby
nodes = [
{ id: 7, parentId: 1 },
...
] # nodeJS
Les fonctions qui transformeront la structure de la liste à plat ci-dessus en une arborescence se présentent de la manière suivante
pour Ruby:
def to_tree(nodes)
nodes.each do |node|
parent = nodes.find { |another| another[:id] == node[:parent_id] }
next unless parent
node[:parent] = parent
parent[:children] ||= []
parent[:children] << node
end
nodes.select { |node| node[:parent].nil? }
end
pour NodeJS:
const toTree = (nodes) => {
nodes.forEach((node) => {
const parent = nodes.find((another) => another.id == node.parentId)
if(parent == null) return;
node.parent = parent;
parent.children = parent.children || [];
parent.children = parent.children.concat(node);
});
return nodes.filter((node) => node.parent == null)
};
Voici la solution Java de la réponse de Mehrdad Afshari.
import Java.util.ArrayList;
import Java.util.HashMap;
import Java.util.Iterator;
import Java.util.List;
import Java.util.Map;
public class Tree {
Iterator<Node> buildTreeAndGetRoots(List<MyObject> actualObjects) {
Map<Integer, Node> lookup = new HashMap<>();
actualObjects.forEach(x -> lookup.put(x.id, new Node(x)));
//foreach (var item in lookup.Values)
lookup.values().forEach(item ->
{
Node proposedParent;
if (lookup.containsKey(item.associatedObject.parentId)) {
proposedParent = lookup.get(item.associatedObject.parentId);
item.parent = proposedParent;
proposedParent.children.add(item);
}
}
);
//return lookup.values.Where(x =>x.Parent ==null);
return lookup.values().stream().filter(x -> x.parent == null).iterator();
}
}
class MyObject { // The actual object
public int parentId;
public int id;
}
class Node {
public List<Node> children = new ArrayList<Node>();
public Node parent;
public MyObject associatedObject;
public Node(MyObject associatedObject) {
this.associatedObject = associatedObject;
}
}
La plupart des réponses supposent que vous envisagez de le faire en dehors de la base de données. Si vos arbres sont de nature relativement statique et que vous devez simplement les mapper dans la base de données, vous pouvez envisager d’utiliser des représentations d’ensembles imbriquées du côté de la base de données. Consultez les livres de Joe Celko (ou ici Pour un aperçu de Celko).
De toute façon, s'ils sont liés à Oracle dbs, consultez leur méthode CONNECT BY pour les approches SQL directes.
Quelle que soit l'approche choisie, vous pouvez complètement ignorer le mappage des arbres avant de charger les données dans la base de données. Je pensais que je pourrais proposer cela comme alternative, cela pourrait être totalement inapproprié pour vos besoins spécifiques. Toute la partie "ordre approprié" de la question initiale implique quelque peu que vous ayez besoin que l'ordre soit "correct" dans la base de données pour une raison quelconque? Cela pourrait aussi me pousser à manipuler les arbres.
Êtes-vous coincé en utilisant uniquement ces attributs? Sinon, il peut être intéressant de créer un tableau de nœuds enfants, dans lequel vous pouvez parcourir tous ces objets une fois pour créer ces attributs. A partir de là, sélectionnez le noeud avec les enfants mais pas les parents et construisez votre arbre de manière itérative du haut vers le bas.
voici une implémentation de Ruby:
Il cataloguera par nom d'attribut ou par le résultat d'un appel de méthode.
CatalogGenerator = ->(depth) do
if depth != 0
->(hash, key) do
hash[key] = Hash.new(&CatalogGenerator[depth - 1])
end
else
->(hash, key) do
hash[key] = []
end
end
end
def catalog(collection, root_name: :root, by:)
method_names = [*by]
log = Hash.new(&CatalogGenerator[method_names.length])
tree = collection.each_with_object(log) do |item, catalog|
path = method_names.map { |method_name| item.public_send(method_name)}.unshift(root_name.to_sym)
catalog.Dig(*path) << item
end
tree.with_indifferent_access
end
students = [#<Student:0x007f891d0b4818 id: 33999, status: "on_hold", tenant_id: 95>,
#<Student:0x007f891d0b4570 id: 7635, status: "on_hold", tenant_id: 6>,
#<Student:0x007f891d0b42c8 id: 37220, status: "on_hold", tenant_id: 6>,
#<Student:0x007f891d0b4020 id: 3444, status: "ready_for_match", tenant_id: 15>,
#<Student:0x007f8931d5ab58 id: 25166, status: "in_partnership", tenant_id: 10>]
catalog students, by: [:tenant_id, :status]
# this would out put the following
{"root"=>
{95=>
{"on_hold"=>
[#<Student:0x007f891d0b4818
id: 33999,
status: "on_hold",
tenant_id: 95>]},
6=>
{"on_hold"=>
[#<Student:0x007f891d0b4570 id: 7635, status: "on_hold", tenant_id: 6>,
#<Student:0x007f891d0b42c8
id: 37220,
status: "on_hold",
tenant_id: 6>]},
15=>
{"ready_for_match"=>
[#<Student:0x007f891d0b4020
id: 3444,
status: "ready_for_match",
tenant_id: 15>]},
10=>
{"in_partnership"=>
[#<Student:0x007f8931d5ab58
id: 25166,
status: "in_partnership",
tenant_id: 10>]}}}
Ce n'est pas exactement la même chose que ce que recherchait le demandeur, mais j'avais du mal à comprendre les réponses formulées de manière ambiguë ici et je pense toujours que cette réponse correspond au titre.
Ma réponse est pour mapper une structure plate sur une arborescence directement sur un objet où tout ce que vous avez est un ParentID
sur chaque objet. ParentID
est null
ou 0
s'il s'agit d'une racine. En face du demandeur, je suppose que tous les ParentID
valides pointent vers un autre élément de la liste:
var rootNodes = new List<DTIntranetMenuItem>();
var dictIntranetMenuItems = new Dictionary<long, DTIntranetMenuItem>();
//Convert the flat database items to the DTO's,
//that has a list of children instead of a ParentID.
foreach (var efIntranetMenuItem in flatIntranetMenuItems) //List<tblIntranetMenuItem>
{
//Automapper (nuget)
DTIntranetMenuItem intranetMenuItem =
Mapper.Map<DTIntranetMenuItem>(efIntranetMenuItem);
intranetMenuItem.Children = new List<DTIntranetMenuItem>();
dictIntranetMenuItems.Add(efIntranetMenuItem.ID, intranetMenuItem);
}
foreach (var efIntranetMenuItem in flatIntranetMenuItems)
{
//Getting the equivalent object of the converted ones
DTIntranetMenuItem intranetMenuItem = dictIntranetMenuItems[efIntranetMenuItem.ID];
if (efIntranetMenuItem.ParentID == null || efIntranetMenuItem.ParentID <= 0)
{
rootNodes.Add(intranetMenuItem);
}
else
{
var parent = dictIntranetMenuItems[efIntranetMenuItem.ParentID.Value];
parent.Children.Add(intranetMenuItem);
//intranetMenuItem.Parent = parent;
}
}
return rootNodes;