How To Sort Nested ObjectData Flutter - sorting

I am trying to sort a JSON object that has nested data. I want to refresh the List of object each time a button is pressed but, I am having issues doing so. I am trying to sort by price and this is my code so far:
Data:
{
"id": 152,
"name": "Advil",
"short_description": "Advil 20 Pack",
"usage": "Headaches",
"effect": "MoodSwings",
"sizes": [
{
"id": 307,
"size": 4,
"product_id": 152,
"price": 4.99
},
{
"id": 308,
"size": 5,
"product_id": 152,
"price": 12.66
}
],
}
My model is set up like this:
Product(
{this.id,
this.name,
this.shortDescription,
this.image,
this.image2,
this.image3,
this.type,
this.sizes,
this.usage,
this.filter,
this.category,
this.prodSize});
factory Product.fromJSON(Map<String, dynamic> json) {
var sizes = json['sizes'] as List;
print(sizes.runtimeType);
List<ProductSizes> sizeList =
sizes.map((i) => ProductSizes.fromJSON(i)).toList();
return Product(
id: json['id'],
name: json['name'],
shortDescription: json['short_description'],
image: json['image'],
image2: json['image2'],
image3: json['image3'],
type: json['Product_Type'],
sizes: sizeList,
usage: json['usage'],
filter: json['filter'],
category: json['Product_Category'],
);
}
}
class ProductSizes {
int id;
int size;
int productId;
double price;
ProductSizes({this.id, this.size, this.productId, this.price});
factory ProductSizes.fromJSON(Map<String, dynamic> json) {
return ProductSizes(
id: json['id'],
size: json['size'],
productId: json['product_id'],
price: json['price']);
}
}
My switch case that is tied to a dropdownbutton:
void _sortProductsDropDown(_filterDropdown, productList) {
var tempProducts;
setState(() {
switch (_filterDropdown) {
case 'Price: Low-High':
for (int i = 0; i < productList.length; i++) {
tempProducts.add(productList[i]
.sizes
.sort((a, b) => b.$price.reversed(a.price)));
}
_productTabFilter = tempProducts;
print(tempProducts.toString());
break;
case 'Price: High-Low':
print(_filterDropdown);
for (int i = 0; i < productList.length; i++) {
tempProducts.add(productList[i]
.sizes
.sort((a, b) => a.$price.compareTo(b.price)));
}
_productTabFilter = tempProducts;
print(tempProducts.toString());
break;
}
});
// return productList;
}
That is being called by the onchange method for the drop down button here:
void _changeFilterList(String value) {
//use _filterDropdown for switch statement
setState(() {
_filterDropdown = value;
});
print(' the value of the dropdown is $_filterDropdown');
_sortProductsDropDown(_filterDropdown, productList);
}
Then I use a gridview builder to make the data
return GridView.builder(
itemCount: _productTabFilter.length,
controller: ScrollController(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 20,
childAspectRatio: 0.7),
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemBuilder: (context, i) {
Then it throws this error:
[VERBOSE-2:ui_dart_state.cc(157)] Unhandled Exception: type '(dynamic, dynamic) => dynamic' is not a subtype of type '(ProductSizes, ProductSizes) => int' of 'compare'
Any help would be appreciated

Try casting that object to a List and then use the sort function on that list.
like this:
(productList[i]['sizes'] as List).sort((a, b) => a['price'].compareTo(b['price']))

Related

How to create a new key and add or append an array of objects on AWS DynamoDB

I have this object in my dynamodb table which looks like
{
"id": "b31de483"
}
I want to add another object that looks like
{
"id": "b31de483",
"players": [{"playerId": "1234"}]
}
This is my code
const addPlayerToGame = async (gameId, playerId) => {
const params = {
TableName: process.env.DYNAMODB_GAMES_TABLE,
Key: {
id: gameId
},
UpdateExpression: 'set players = list_append(if_not_exists(players, :players)',
ExpressionAttributeValues: {
':players': {
"L": [
{ "S": playerId }
]
}
}
};
return await documentClient.update(params);
}
This throws an error but I cannot understand how to fix it. I am looking at documentation here
Figured it out
const addPlayerToGame = async (gameId, playerId) => {
const params = {
TableName: process.env.DYNAMODB_GAMES_TABLE,
Key: {
id: gameId
},
UpdateExpression: 'set players = list_append(players, :players)',
ExpressionAttributeValues: {
':players': [{
"playerId": playerId
}]
}
};
return await documentClient.update(params).promise();
}

Apollo Server / GraphQL - Properties of Nested Array Returning Null

Bear with me, I will explain this the best I can. Please let me know if more information is needed, I am trying to keep this as brief as possible.
I am using Apollo Server and the 'apollo-datasource-rest' plugin to access a REST API. When attempting to get the property values from a nested array of objects I get a null response for each field/property. In addition, the array being queried is only showing a single iteration when multiple are available.
The field in question is the 'cores' field within the Rocket type, i.e., launch.rocket.firstStage.cores
I have attempted various ways of mapping through 'cores' (thinking this was what it wanted) with no success.
To keep things short and simple I'm only including the code for the specific issue. All other parts of the query are operating as expected.
You can view the API response I am hitting here: https://api.spacexdata.com/v3/launches/77
schema.js
const { gql } = require('apollo-server');
const typeDefs = gql`
type Query {
singleLaunch(flightNumber: Int!): Launch
}
type Launch {
flightNumber: Int!
rocket: Rocket
}
type Rocket {
firstStage: Cores
}
type Cores {
cores: [CoreFields]
}
type CoreFields {
flight: Int
gridfins: Boolean
legs: Boolean
reused: Boolean
landingType: String
landingVehicle: String
landingSuccess: Boolean
}
`;
module.exports = typeDefs;
Data Source - launch.js
const { RESTDataSource } = require('apollo-datasource-rest');
class LaunchAPI extends RESTDataSource {
constructor() {
super();
this.baseURL = 'https://api.spacexdata.com/v3/';
}
async getLaunchById({ launchId }) {
const res = await this.get('launches', {
flight_number: launchId,
});
return this.launchReducer(res[0]);
}
launchReducer(launch) {
return {
flightNumber: launch.flight_number || 0,
rocket: {
firstStage: {
cores: [
{
flight: launch.rocket.first_stage.cores.flight,
gridfins: launch.rocket.first_stage.cores.gridfins,
legs: launch.rocket.first_stage.cores.legs,
landingType: launch.rocket.first_stage.cores.landing_type,
landingVehicle: launch.rocket.first_stage.cores.landing_vehicle,
landingSuccess: launch.rocket.first_stage.cores.landing_success,
},
],
},
};
}
}
module.exports = LaunchAPI;
resolvers.js
module.exports = {
Query: {
singleLaunch: (_, { flightNumber }, { dataSources }) =>
dataSources.launchAPI.getLaunchById({ launchId: flightNumber }),
},
};
Query
query GetLaunchById($flightNumber: Int!) {
singleLaunch(flightNumber: $flightNumber) {
flightNumber
rocket {
firstStage {
cores {
flight
gridfins
legs
reused
landingType
landingVehicle
landingSuccess
}
}
}
}
}
Expected Result
{
"data": {
"singleLaunch": {
"flightNumber": 77,
"rocket": {
"firstStage": {
"cores": [
{
"flight": 1,
"gridfins": true,
"legs": true,
"reused": true,
"landingType": "ASDS",
"landingVehicle": "OCISLY",
"landSuccess": true,
},
{
"flight": 1,
"gridfins": true,
"legs": true,
"reused": false,
"landingType": "RTLS",
"landingVehicle": "LZ-1",
"landSuccess": true
},
{
"flight": 1,
"gridfins": true,
"legs": true,
"reused": false,
"landingType": "RTLS",
"landingVehicle": "LZ-2",
"landSuccess": true
},
]
}
},
}
}
}
Actual Result (Through GraphQL Playground)
{
"data": {
"singleLaunch": {
"flightNumber": 77,
"rocket": {
"firstStage": {
"cores": [
{
"flight": null,
"gridfins": null,
"legs": null,
"reused": null,
"landingType": null,
"landingVehicle": null,
"landingSuccess": null
}
]
}
},
}
}
}
Any suggestions as to what I am doing wrong here would be greatly appreciated. Again, let me know if more information is needed.
Thank you!
Missing base url
There should be
await this.get( this.baseURL + 'launches'
IMHO there should be a map used within launchReducer to return an array, sth like:
launchReducer(launch) {
return {
flightNumber: launch.flight_number || 0,
rocket: {
firstStage: {
cores: launch.rocket.first_stage.cores.map(core => ({
flight: core.flight,
gridfins: core.gridfins,
legs: core.legs,
landingType: core.landing_type,
landingVehicle: core.landing_vehicle,
landSuccess: core.land_success,
})),
},
},
};
}
.map(core => ({ is for returning object [literal], the same as/shorter version of .map(core => { return {

How to merge the result of two observations (with different type)

Assume I have these two observations:
const thread = of({
thread: {
name: "Name",
author: null
}
})
const author = of({name:"Snoob"})
How can i get the merged result of these observations:
const threadWithAuthor = .....;
threadWithAuthor.subscribe(it=>console.log(it))
// {
// thread: {
// name: "Name",
// author: { name: "Snoob" }
// }
// }
Here's an example of how you can do it using combineLatest, pipe, and map:
var {of, combineLatest } = require('rxjs')
var { map } = require('rxjs/operators')
var mergeByAuthor = ([t, a]) => {
var x = Object.assign({}, t)
x.thread.author = a
return x
}
var thread = of({
thread: {
name: 'Name',
author: null
}
})
var author = of({name:'Snoob'})
var threadWithAuthor = combineLatest(thread, author).pipe(
map(mergeByAuthor)
)
threadWithAuthor.subscribe(x => console.log(JSON.stringify(x, null, 2)))
Output
{
"thread": {
"name": "Name",
"author": {
"name": "Snoob"
}
}
}

Handsontable: Updating a cell renderer at runtime

I'm using handsontable, and I want to change the background color of a cell if its value is edited and changed. I can do this easily if my data source is an array of arrays (see fiddle: http://jsfiddle.net/chiman24/3o2c3c7m/).
document.addEventListener("DOMContentLoaded", function() {
// Row Styles
var blank = function(instance, td, row, col, prop, value,
cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
td.style.backgroundColor = '#ABAAAA'
};
var align = function(instance, td, row, col, prop, value,
cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
td.style.verticalAlign = 'middle';
td.style.fontWeight = 'bold';
};
var highlight1 = function(instance, td, row, col, prop, value,
cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
td.style.backgroundColor = '#BDD7EE';
td.style.textAlign = 'right';
};
var changedBackgroundColor = '#cbd9e4';
var defaultBackgroundColor = 'white';
var hasChanged = function(instance, td, row, col, prop, value,
cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
td.style.backgroundColor = changedBackgroundColor;
};
var noChange = function(instance, td, row, col, prop, value,
cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
td.style.backgroundColor = defaultBackgroundColor;
};
var data = [
["1", "Hear us from heaven", "New Life Worship",
"Anderson, Jared", "something"
],
["2", "Spirit Break Out", "Kim Walker", "Walker, Kim",
"Still Believe"
]
],
dataCopy = [
["1", "Hear us from heaven", "New Life Worship",
"Anderson, Jared", "something"
],
["2", "Spirit Break Out", "Kim Walker", "Walker, Kim",
"Still Believe"
]
],
container = document.getElementById('example1'),
hot1;
//Table Row and Col Options
hot1 = new Handsontable(container, {
data: data,
fixedColumnsLeft: 1,
columnSorting: true,
colHeaders: ["id", "title", "artist", "author", "album"],
columns: [{
type: "text"
}, {
type: "text"
}, {
type: "text"
}, {
type: "text"
}, {
type: "text"
}]
});
hot1.addHook('afterChange', afterChange);
function afterChange(changes, source) {
if (source == 'edit' || source == 'autofill') {
$.each(changes, function(index, element) {
var change = element;
var rowIndex = change[0];
var columnIndex = change[1];
var oldValue = change[2];
var newValue = change[3];
var cellChange = {
'rowIndex': rowIndex,
'columnIndex': columnIndex
};
if (oldValue != newValue) {
var cellProperties = hot1.getCellMeta(
rowIndex, columnIndex);
if (newValue != dataCopy[rowIndex][
columnIndex
]) {
cellProperties.renderer = hasChanged;
} else { //data changed back to original value.
cellProperties.renderer = noChange;
}
hot1.render();
}
});
}
}
});
// noSideScroll class added to fix some containers while side scrolling the table
$(window).scroll(function() {
$('.noSideScroll').css({
'left': $(this).scrollLeft()
});
});
However, when using an array of objects, I can't get it to work. (see fiddle: http://jsfiddle.net/chiman24/24mpavga/).
var data = [{
"id": 1,
"title": "First Loved Me",
"artist": "Israel and New Breed",
"author": "Houghton, Israel",
"album": "Covered: Alive In Asia"
}, {
"id": 2,
"title": "One Thing Remains",
"artist": "Israel and New Breed",
"author": "Houghton, Israel",
"album": "Covered: Alive In Asia"
}],
dataCopy = [{
"id": 1,
"title": "First Loved Me",
"artist": "Israel and New Breed",
"author": "Houghton, Israel",
"album": "Covered: Alive In Asia"
}, {
"id": 2,
"title": "One Thing Remains",
"artist": "Israel and New Breed",
"author": "Houghton, Israel",
"album": "Covered: Alive In Asia"
}],
container = document.getElementById('example1'),
hot1;
//Table Row and Col Options
hot1 = new Handsontable(container, {
data: data,
fixedColumnsLeft: 1,
columnSorting: true,
colHeaders: ["id", "title", "artist", "author", "album"],
columns: [{
data: "id"
}, {
data: "title"
}, {
data: "artist"
}, {
data: "author"
}, {
data: "album"
}]
});
hot1.addHook('afterChange', afterChange);
function afterChange(changes, source) {
if (source == 'edit' || source == 'autofill') {
$.each(changes, function(index, element) {
var change = element;
var rowIndex = change[0];
var columnIndex = change[1];
var oldValue = change[2];
var newValue = change[3];
var cellChange = {
'rowIndex': rowIndex,
'columnIndex': columnIndex
};
if (oldValue != newValue) {
var cellProperties = hot1.getCellMeta(
rowIndex, columnIndex);
if (newValue != dataCopy[rowIndex][
columnIndex
]) {
cellProperties.renderer = hasChanged;
} else { //data changed back to original value.
cellProperties.renderer = noChange;
}
hot1.render();
}
});
}
}
Is there a way to accomplish this? I want to get it working using an array of objects because my data coming from the server will be in JSON format. I've scoured the handsontable documentation for a couple of days to no avail. Any help will be much appreciated. Thanks.
I got some help from the handsontable github forum.
Apparently, if the data source is an array of objects, then when calling "getCellMeta", instead of passing in a numerical column index, you have to pass in the column index as a property like this:
hot.getCellMeta(2, hot.propToCol(columnIndex));
Here's the updated demo
Other way to change background color of cell is use the Cell Option
...
if (oldValue != newValue){
aCell.push(
{ row: rowIndex,
col: hot.propToCol(columnIndex),
className: "cssWithBackgroundColor" });
hot.updateSettings({ cell: aCell });
}
If the user undo change you can
if ( source == 'UndoRedo.undo'){
aCell.pop();
hot.updateSettings({ cell: aCell });
}

How to efficiently build a tree from a flat structure?

I have a bunch of objects in a flat structure. These objects have an ID and a ParentID property so they can be arranged in trees. They are in no particular order.
Each ParentID property does not necessarily matches with an ID in the structure. Therefore their could be several trees emerging from these objects.
How would you process these objects to create the resulting trees ?
I'm not so far from a solution but I'm sure it is far from optimal...
I need to create these trees to then insert Data into a database, in proper order.
There are no circular references. A Node is a RootNode when ParentID == null or when ParentID can't be found in the other objects
Store IDs of the objects in a hash table mapping to the specific object. Enumerate through all the objects and find their parent if it exists and update its parent pointer accordingly.
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);
}
Here is a simple JavaScript algorithm to parse a flat table into a parent/child tree structure that runs in N time:
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);
Based on the answer of Mehrdad Afshari and the comment of Andrew Hanlon for a speedup, here is my take.
Important difference to the original task: A root node has 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;
}
Python solution
def subtree(node, relationships):
return {
v: subtree(v, relationships)
for v in [x[0] for x in relationships if x[1] == node]
}
For example:
# (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)
Produces:
{
"1": {
"4": {
"10": {},
"11": {
"16": {},
"17": {
"24": {},
"25": {}
}
}
},
"5": {
"8": {},
"9": {
"20": {},
"12": {
"22": {},
"23": {
"2": {},
"27": {},
"26": {}
}
},
"21": {},
"7": {}
}
}
}
}
JS version that returns one root or an array of roots each of which will have a Children array property containing the related children. Does not depend on ordered input, decently compact, and does not use recursion. Enjoy!
// 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)
Found an awesome JavaScript version here: http://oskarhane.com/create-a-nested-array-recursively-in-javascript/
Let’s say you have an array like this:
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}
];
And you want to have the objects nested like this:
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}
]
}
];
Here’s a recursive function that makes it happen.
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);
Here is java solution of the answer by 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;
}
}
For anyone interested in a C# version of Eugene's solution, note that node_list is accessed as a map, so use a Dictionary instead.
Keep in mind that this solution only works if table is sorted by 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 = 8 },
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]);
}
Node is defined as follows.
class Node
{
public int id { get; set; }
public int parent_id { get; set; }
public List<Node> children = new List<Node>();
}
I wrote a generic solution in C# loosely based on #Mehrdad Afshari answer:
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;
}
}
Most of the answers assume you are looking to do this outside of the database. If your trees are relatively static in nature and you just need to somehow map the trees into the database, you may want to consider using nested set representations on the database side. Check out books by Joe Celko (or here
for an overview by Celko). If tied to Oracle dbs anyway, check out their CONNECT BY for straight SQL approaches. With either approach, you could completely skip mapping the trees before you load the data up in the database. Just thought I would offer this up as an alternative, it may be completely inappropriate for your specific needs. The whole "proper order" part of the original question somewhat implies you need the order to be "correct" in the db for some reason? This might push me towards handling the trees there as well.
Vague as the question seems to me, I would probably create a map from the ID to the actual object. In pseudo-java (I didn't check whether it works/compiles), it might be something like:
Map<ID, FlatObject> flatObjectMap = new HashMap<ID, FlatObject>();
for (FlatObject object: flatStructure) {
flatObjectMap.put(object.ID, object);
}
And to look up each parent:
private FlatObject getParent(FlatObject object) {
getRealObject(object.ParentID);
}
private FlatObject getRealObject(ID objectID) {
flatObjectMap.get(objectID);
}
By reusing getRealObject(ID) and doing a map from object to a collection of objects (or their IDs), you get a parent->children map too.
I can do this in 4 lines of code and O(n log n) time, assuming that Dictionary is something like a 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, and now I read that some parentIDs are fake, so forget the above, and do this:
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.
It's not exactly the same as what the asker looked for, but I had difficulty wrapping my head around the ambiguously phrased answers provided here, and I still think this answer fits under the title.
My answer is for mapping a flat structure to a directly-on-object tree where all you have is a ParentID on each object. ParentID is null or 0 if it is a root. Opposite of the asker, I assume all valid ParentID's point to something else in the list:
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;
here is a ruby implementation:
It will catalog by attribute name or the result of a method call.
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>]}}}
The accepted answer looks way too complex to me so I am adding a Ruby and NodeJS versions of it
Suppose that flat nodes list have the following structure:
nodes = [
{ id: 7, parent_id: 1 },
...
] # ruby
nodes = [
{ id: 7, parentId: 1 },
...
] # nodeJS
The functions which will turn the flat list structure above into a tree look the following way
for 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
for 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)
};
one elegant way to do this is to represent items in the list as string holding a dot separated list of parents, and finally a value:
server.port=90
server.hostname=localhost
client.serverport=90
client.database.port=1234
client.database.host=localhost
When assembling a tree, you would end up with something like:
server:
port: 90
hostname: localhost
client:
serverport=1234
database:
port: 1234
host: localhost
I have a configuration library that implements this override configuration (tree) from command line arguments (list).
The algorithm to add a single item to the list to a tree is here.
Golang solution utilizing pointer. Since each iteration will basically point to same object, resulting in N space & time complexity
https://go.dev/play/p/lrPBTawy7Su
func TestCommentTree(t *testing.T) {
var listRaw = `[{"id":5,"parentPostID":0,"parentID":null,"authorID":1,"content":"asadas","replies":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"},{"id":9,"parentPostID":0,"parentID":5,"authorID":1,"content":"yeet comment","replies":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"},{"id":10,"parentPostID":0,"parentID":9,"authorID":1,"content":"yeet comment","replies":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"},{"id":11,"parentPostID":0,"parentID":5,"authorID":1,"content":"yeet comment","replies":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"},{"id":12,"parentPostID":0,"parentID":10,"authorID":1,"content":"yeet comment","replies":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"},{"id":13,"parentPostID":0,"parentID":10,"authorID":1,"content":"yeet comment","replies":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"},{"id":14,"parentPostID":0,"parentID":10,"authorID":1,"content":"yeet comment","replies":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"},{"id":15,"parentPostID":0,"parentID":12,"authorID":1,"content":"yeet comment","replies":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"},{"id":16,"parentPostID":0,"parentID":11,"authorID":1,"content":"yeet comment","replies":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"},{"id":17,"parentPostID":0,"parentID":16,"authorID":1,"content":"yeet comment","replies":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"},{"id":18,"parentPostID":0,"parentID":17,"authorID":1,"content":"yeet comment","replies":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"},{"id":19,"parentPostID":0,"parentID":18,"authorID":1,"content":"yeet comment","replies":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"}]`
type Comment struct {
ID uint64 `db:"id" json:"id"`
ParentPostID uint64 `db:"parent_post_id" json:"parentPostID"`
ParentID *int `db:"parent_id" json:"parentID"` // allow nullable on lvl 0 comment
AuthorID uint64 `db:"author_id" json:"authorID"`
Content string `db:"content" json:"content"`
Replies []*Comment `json:"replies"`
CreatedAt time.Time `db:"created_at" json:"createdAt"`
UpdatedAt time.Time `db:"updated_at" json:"updatedAt"`
}
var c []*Comment
err := json.Unmarshal([]byte(listRaw), &c)
if err != nil {
panic(err)
}
node := make(map[uint64]*Comment)
for _, v := range c {
node[v.ID] = v
}
for _, comm := range node {
if comm.ParentID == nil {
continue
}
parent := node[uint64(*comm.ParentID)]
if parent == nil {
panic(fmt.Sprintf("parent nil %d", *comm.ParentID))
}
if parent.Replies == nil {
parent.Replies = make([]*Comment, 0)
parent.Replies = append(parent.Replies, comm)
} else {
parent.Replies = append(parent.Replies, comm)
}
}
res := make([]*Comment, 0)
for _, comm := range node {
if comm.ParentID != nil {
continue
}
res = append(res, comm)
}
s, _ := json.MarshalIndent(res, "", "\t")
fmt.Println(string(s))
}
Are you stuck using only those attributes? If not, it might be nice to create an array of child nodes, where you can cycle through all these objects once to build such attributes. From there, select the node with children but no parents and iteratively build your tree from the top down.
java version
// node
#Data
public class Node {
private Long id;
private Long parentId;
private String name;
private List<Node> children = new ArrayList<>();
}
// flat list to tree
List<Node> nodes = new ArrayList();// load nodes from db or network
Map<Long, Node> nodeMap = new HashMap();
nodes.forEach(node -> {
if (!nodeMap.containsKey(node.getId)) nodeMap.put(node.getId, node);
if (nodeMap.containsKey(node.getParentId)) {
Node parent = nodeMap.get(node.getParentId);
node.setParentId(parent.getId());
parent.getChildren().add(node);
}
});
// tree node
List<Node> treeNode = nodeMap .values().stream().filter(n -> n.getParentId() == null).collect(Collectors.toList());
See below a complete Java 8+ solution with simple test program.
this is a solution with a modification to the #"Vimal Bhatt" version that accepts more than one root
package tree;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Stream;
public class Tree {
private <T> void swap(T[] input, int a, int b) {
T tmp = input[a];
input[a] = input[b];
input[b] = tmp;
}
public static void main(String[] args) {
Random r = new Random(8);
MyObject[] myObjects = new MyObject[]{
new MyObject(6, 3),
new MyObject(7, 5),
new MyObject(8, 0),
new MyObject(1, 0),
new MyObject(15, 12),
new MyObject(12, 0),
new MyObject(3, 5),
new MyObject(4, 3),
new MyObject(5, 2),
new MyObject(2, 1),
new MyObject(21, 8),
new MyObject(9, 1)
};
Tree t = new Tree();
// cinco trocas arbitrarias de posição
for (int i = 0; i < 5; i++) {
int a = r.nextInt(7) + 1;
int b = r.nextInt(7) + 1;
t.swap(myObjects, a, b);
}
System.out.println("The list have " + myObjects.length + " objects");
for (MyObject o: myObjects) {
System.out.print(" " + o);
}
Iterator<Node> iterator = t.buildTreeAndGetRoots(Arrays.asList(myObjects));
int counter = 0;
System.out.println("");
while (iterator.hasNext()) {
Node obj = iterator.next();
System.out.println(++counter + "\t" + obj.associatedObject.id + "\t-> " + obj.associatedObject.parentId);
}
}
Iterator<Node> buildTreeAndGetRoots(List<MyObject> actualObjects) {
Node root = null;
Map<Integer, Node> lookup = new HashMap<>();
actualObjects.forEach(x -> lookup.put(x.id, new Node(x)));
Stream<Node> roots = actualObjects.stream()
.filter(x -> x.parentId == 0)
.map(x -> new Node(x));
Consumer<Node> nodeConsumer = item -> {
Node proposedParent;
if (lookup.containsKey(item.associatedObject.parentId)) {
proposedParent = lookup.get(item.associatedObject.parentId);
item.parent = proposedParent;
proposedParent.children.add(item);
}
};
lookup.values().forEach(nodeConsumer);
Stream<Node> s2 = lookup.values().stream().filter(e -> e.associatedObject.parentId != 0);
return Stream.concat(roots, s2).iterator();
}
}
class MyObject { // The actual object
public int parentId;
public int id;
MyObject(int id, int parent) {
this.parentId = parent;
this.id = id;
}
#Override
public String toString() {
return "{ " +
"parent: " + parentId +
", id: " + id +
" }" ;
}
}
class Node {
public List<Node> children = new ArrayList<Node>();
public Node parent;
public MyObject associatedObject;
public Node(MyObject associatedObject) {
this.associatedObject = associatedObject;
}
}
This solution is the same as the selected answer, but in JavaScript:
/**
* #param {{id: any, parentId: any}[]} nodes
*/
function arrayToTree(nodes) {
const map = new Map(nodes.map((x) => [x.id, { key: x.id, children: [] }]));
for (const n of nodes) {
if (n.parentId) {
map.get(n.parentId)?.children?.push(map.get(n.id));
}
}
return nodes.filter((x) => x.parentId === null).map((x) => map.get(x.id));
}
Based on Euguene's answer, here is a functional approach. If the items are not pre-sorted, the nodeList will not have the parent ready to add children.
const sortByParentId = (
{ parentId: a1, id: a2 },
{ parentId: b1, id: b2 }
) => a1 - b1 || a2 - b2
const buildTree = (items) => {
const
root = { id: 0, parentId: null, children: [] },
nodeList = { 0 : root };
items
.sort(sortByParentId)
.forEach(({ id, parentId }) => {
nodeList[id] = { id, parentId, children: [] };
nodeList[parentId].children.push(nodeList[id]);
});
return root;
};
// Reversed (does not work without proper sorting)
const items = [
{ id: 10, parentId: 3 },
{ id: 9, parentId: 8 },
{ id: 8, parentId: 7 },
{ id: 7, parentId: 2 },
{ id: 6, parentId: 1 },
{ id: 5, parentId: 1 },
{ id: 4, parentId: 1 },
{ id: 3, parentId: 0 },
{ id: 2, parentId: 0 },
{ id: 1, parentId: 0 },
];
const tree = buildTree(items);
console.log(tree);
.as-console-wrapper { top: 0; max-height: 100% !important; }
<!--
// Best-case order
const items = [
{ id: 1, parentId: 0 },
{ id: 2, parentId: 0 },
{ id: 3, parentId: 0 },
{ id: 4, parentId: 1 },
{ id: 5, parentId: 1 },
{ id: 6, parentId: 1 },
{ id: 7, parentId: 2 },
{ id: 8, parentId: 7 },
{ id: 9, parentId: 8 },
{ id: 10, parentId: 3 }
];
-->
Here is an example without pre-sorting (with OOP):
class Node {
constructor({ id, parentId }) {
this.id = id;
this.parentId = parentId;
this.children = [];
}
};
const buildTree = (items) => {
const
root = new Node({ id: 0, parentId: null }),
nodeList = { 0 : root };
items.forEach(({ id, parentId }) => {
if (!nodeList[id]) {
nodeList[id] = new Node({ id, parentId });
} else {
nodeList[id].parentId = parentId;
}
if (!nodeList[parentId]) {
nodeList[parentId] = new Node({ id: parentId });
}
nodeList[parentId].children.push(nodeList[id]);
});
return root;
};
const items = [
{ id: 10, parentId: 3 },
{ id: 8, parentId: 7 },
{ id: 6, parentId: 1 },
{ id: 4, parentId: 1 },
{ id: 2, parentId: 0 },
{ id: 9, parentId: 8 },
{ id: 7, parentId: 2 },
{ id: 5, parentId: 1 },
{ id: 3, parentId: 0 },
{ id: 1, parentId: 0 },
];
const tree = buildTree(items);
console.log(tree);
.as-console-wrapper { top: 0; max-height: 100% !important; }

Resources