I'm trying to create a simple display of NBA west leaders in order by seed using the following json file:
http://data.nba.com/data/v2014/json/mobile_teams/nba/2014/00_standings.json
Right now I have the following:
$(document).ready(function() {
$.getJSON('http://data.nba.com/data/v2014/json/mobile_teams/nba/2014/00_standings.json',function(info){
var eastHead = info.sta.co[0].val;
var divi = info.sta.co[0].di[0].val;
/*evaluate East*/
for(i=0;i < divi.length;i++){
var visTeam ='<li>' + divi + '</li>';
document.getElementById("eastHead").innerHTML=eastHead;
}
var seed = info.sta.co[0].di[0].t[0].see;
$.each(menuItems.data, function (i) {
var eastSeed ='<li>' + seed + '</li>';
console.log(eastSeed)
document.getElementById("eastSeed").innerHTML=eastSeed;
});//$.each(menuItems.data, function (i) {
});//getJSON
});//ready
I'm looking just to list out the leaders in order. So right now we have
Golden State 2. Memphis 3. Houston 4. Portland 5. L.A. Clippers 6. Dallas .... and so
forth.
This is based off of the "see" value which means seed in the west.
This issue is I'm getting a single value rather than an iteration.
Updated:
$(document).ready(function() {
$.getJSON('http://data.nba.com/data/v2014/json/mobile_teams/nba/2014/00_standings.json',function(info){
/**************************************************/
//Get info above here
var westDivision = info.sta.co[1].di;
westDivision.forEach(function (subdivision)
{
subdivision.t.forEach(function (team)
{
westTeams.push({
city: team.tc,
name: team.tn,
seed: team.see
});
});
});
function compare(a,b) {
if (a.see < b.see)
return -1;
if (a.see > b.see)
return 1;
return 0;
}
var sorted = westTeams.sort(compare);
sorted.forEach(function (el,i)
{
console.log(i+'. '+el.city+' '+el.name);
});
/**************************************************/
});//getJSON
});//ready
console output :
Portland Trail Blazers
Oklahoma City Thunder
Denver Nuggets
Utah Jazz
Minnesota Timberwolves
Golden State Warriors
Los Angeles Clippers
Phoenix Suns
Sacramento Kings
Los Angeles Lakers
Memphis Grizzlies
Houston Rockets
Dallas Mavericks
San Antonio Spurs
New Orleans Pelicans
I like to iterate with forEach. Rather then having to worry about indexes you can directly reference each item of the array.
Using this code you can put the data you want into an array.
//Get info above here
var westTeams = [];
var westDivision = info.sta.co[1].di;
westDivision.forEach(function (subdivision)
{
subdivision.t.forEach(function (team)
{
westTeams.push({
city: team.tc,
name: team.tn,
seed: team.see
});
});
});
Then you can sort them using obj.sort
function compare(a,b) {
if (a.seed < b.seed)
return -1;
if (a.seed > b.seed)
return 1;
return 0;
}
var sorted = westTeams.sort(compare);
Finally, you can print them in order.
sorted.forEach(function (el,i)
{
console.log((i+1)+'. '+el.city+' '+el.name);
});
Querying a large JavaScript object graph can be a tedious thing, especially if you want to have dynamic output. Implementing support for different filter criteria, sort orders, "top N" restrictions, paging can be difficult. And whatever you come up with tends to be inflexible.
To cover these cases you can (if you don't mind the learning curve) use linq.js (reference), a library that implements .NET's LINQ for JavaScript.
The following showcases what you can do with it. Long post, bear with me.
Preparation
Your NBA data object follows a parent-child hierarchy, but it misses a few essential things:
there are no parent references
the property that contains the children is called differently on every level (i.e. co, di, t)
In order to make the whole thing uniform (and therefore traversable), we first need to build a tree of nodes from it. A tree node would wrap objects from your input graph and would look like this:
{
obj: o, /* the original object, e.g. sta.co[1] */
parent: p, /* the parent tree node, e.g. the one that wraps sta */
children: [] /* array of tree nodes built from e.g. sta.co[1].di */
}
The building of this structure can be done recursively in one function:
function toNode(obj) {
var node = {
obj: obj,
parent: this === window ? null : this,
// we're interested in certain child arrays, either of:
children: obj.co || obj.di || obj.t || []
};
// recursive step (with reference to the parent node)
node.children = node.children.map(toNode, node);
// (*) explanation below
node.parents = Enumerable.Return(node.parent)
.CascadeDepthFirst("$ ? [$.parent] : []").TakeExceptLast(1);
return node;
}
(*) The node.parents property is a convenience facility. It contains an enumeration of all parent nodes except the last one (i.e. the root node, which is null). This enumeration can be used for filtering as shown below.
The result of this function is a nice-and-uniform interlinked tree of nodes. (Expand the code snippet, but unfortunately it currently does not run due to same-origin browser restrictions. Maybe there is something in the NBA REST API that needs to be turned on first.)
function toNode(obj) {
var node = {
obj: obj,
parent: this === window ? null : this,
children: obj.co || obj.di || obj.t || []
};
node.children = node.children.map(toNode, node);
node.parents = Enumerable.Return(node.parent)
.CascadeDepthFirst("$ ? [$.parent] : []").TakeExceptLast(1);
return node;
}
$(function () {
var standingsUrl = 'http://data.nba.com/data/v2014/json/mobile_teams/nba/2014/00_standings.json';
$.getJSON(standingsUrl, function(result) {
var sta = toNode(result.sta);
console.log(sta);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Result
Now that we have a fully traversable tree of nodes, we can use LINQ queries to do complex things with only a few lines of code:
// first build our enumerable stats tree
var stats = Enumerable.Return(toNode(result.sta));
// then traverse into children; the ones with a tid are teams
var teams = stats.CascadeDepthFirst("$.children")
.Where("$.obj.tid");
OK, we have identified all teams, so we can...
// ...select all that have a parent with val 'West' and order them by 'see'
var westernTeams = teams.Where(function (node) {
return node.parents.Any("$.obj.val === 'West'");
})
.OrderByDescending("$.obj.see");
// ...insert the top 5 into our page as list items
westernTeams.Take(5).Do(function (node) {
$("<li></li>", {text: node.obj.tc + ' ' + node.obj.tn}).appendTo("#topFiveList");
});
// ...turn them as an array of names
var names = westernTeams.Select("$.obj.tc + ' ' + $.obj.tn").ToArray();
console.log(names);
Of course what I have done there in several steps could be done in one:
// request details for all Northwest and Southeast teams who have won more than one game (*)
var httpRequests = Enumerable.Return(toNode(result.sta))
.CascadeDepthFirst("$.children")
.Where("$.obj.tid")
.Where(function (node) {
var str = node.obj.str.split(" ");
return str[0] === "W" && str[1] > 1 &&
node.parents.Any("$.obj.val==='Northwest' || $.obj.val==='Southeast'");
})
.Select(function (node) {
return $.getJSON(detailsUrl, {tid: node.obj.tid});
})
.ToArray();
$.when.apply($, httpRequests).done(function () {
var results = [].slice.call(arguments);
// all detail requests have been fetched, do sth. with the results
});
(*) correct me if I'm wrong, I have no idea what the data in the JSON file actually means
Related
I have just started to pick up coding seriously. :)
I came across a problem that seems too complicated for me.
How to group the following products by promotions type?
var data = [
{
name:'product1',
price:'40',
promotion:[
{
name:'Buy 3 get 30% off',
code:'ewq123'
},
{
name:'Free Gift',
code:'abc140'
}
]
},
{
name:'product2',
price:'40',
promotion:[
{
name:'Buy 3 get 30% off',
code:'ewq123'
}
]
},
{
name:'product3',
price:'40',
promotion:[
{
name:'Buy 3 get 30% off',
code:'ewq123'
}
]
},
{
name:'product4',
price:'40'
},
{
name:'product5',
price:'40',
promotion:[
{name:'30% off', code:'fnj245'}
]
},
{
name:'product6',
price:'0',
promotion:[
{
name:'Free Gift',
code:'abc140'
}
]
}
];
I would like to get result in the following format
result =[
{
name : 'Buy 3 get 30% off',
code: 'ewq123',
products: [
... array of products
]
},
{
name : '30% off',
code: 'fnj245',
products: [
... array of products
]
},
{
...
}
];
I am able to get a list of products by promotion code, but how can I make it generic?
function productHasPromo(product, promotion){
if(!product.hasOwnProperty('promotion')) return false;
var productPromo = product.promotion;
for(var i=0; i<productPromo.length; i++){
if(productPromo[i].code === promotion){
return true;
}
}
return false;
}
function groupProductByPromo(products, promotion){
var arr = [];
for(var i=0; i<products.length; i++){
if(productHasPromo(products[i], promotion)){
arr.push(products[i]);
}
}
return arr;
}
Explanation
You could write a function that loops through your array and search for the unique values within a specified property. That is easily done when working with simple data types, but can be done with more complex structures as arrays of objects (like in your example), using a helper grouping function.
Since you also need the output to be in a specific format after the grouping, we will have to work on a transformer also. This transformer will receive the original data and the unique values extracted by the grouping function, and will generate the desired output.
The following functions were used in the example:
Array.prototype.groupBy = function (property, grouping, transformer) {
var values = [];
this.forEach(function (item) {
grouping.call(this, item, property).forEach(function (item) {
if (!values.contains(property, item[property])) {
values.push(item);
}
});
});
return transformer.call(this, values);
};
Array.prototype.contains = function (key, value) {
return this.find(function (elm) {
return elm[key] === value;
});
};
function transformerFunction(values) {
this.forEach(function (item) {
if (!item.promotion) return;
item.promotion.forEach(function (promotion) {
values.forEach(function (option) {
if (option.code === promotion.code) {
if (option.products) {
option.products.push(item);
} else {
option.products = [item];
}
}
});
});
});
return values;
}
function groupingFunction(item, property) {
if (!item.promotion) return [];
var values = [];
item.promotion.forEach(function (promotion) {
if (!values.contains(property, promotion[property])) {
values.push(promotion);
}
});
return values;
}
Usage as follows:
var items = data.groupBy('code', groupFunction, transformFunction);
Example
Check the example i've prepared at jsfiddle
Welcome to the coding world. A lot of people start off with a problem by trying to write some code, then they wonder why it doesn't work and scratch their heads, don't know the basics of debugging it, and then post here to SO. They're missing the crucial first step in programming which is to figure out how you are going to do it. This is also called designing the algorithm. Algorithms are often described using something called pseudo-code. It has the advantage that it can be looked at and understood and established to do the right thing, without getting bogged down in all the mundane details of a programming language.
There are some algorithms that are figured out by some very smart people--like the Boyer-Moore algorithm for string matching--and then there are other algorithms that programmers devise every day as part of their job.
The problem with SO is that all too often someone posts a question which essentially about an algorithm, and then all the keyboard-happy code jockeys pounce it and come up with a code fragment, which in many cases is so contorted and obtuse that one cannot even see what the underlying algorithm is.
What is the algorithm you propose for solving your problem? You could post that, and people would probably give you reasonable comments, and/or if you also give an actual implementation that doesn't work for some reason, help you understand where you've gone wrong.
At the risk of robbing you the pleasure of devising your own algorithm for solving this problem, here's an example:
Create an empty array for the results.
Loop through the products in the input.
For each product, loop through its promotions.
Find the promotion in the array of results.
If there is no such promotion in the array of results, create a new one, with an empty list of products.
Add the product to the array of products in the promotion entry in the array.
In pseudo-code:
results = new Array // 1
for each product in products (data) // 2
for each promotion in promotions field of product // 3
if results does not contain promotion by that name // 4
add promotion to results, with empty products field // 5
add product to products field of results.promotion // 6
If we believe this is correct, we can now try writing this in JavaScript.
var result = []; // 1
for (var i = 0; i < data.length; i++) { // 2
var product = data[i];
var promotions = product.promotion;
for (var j = 0; j < promotions.length; j++) { // 3
var promotion = promotions[i];
var name = promotion.name;
var result_promotion = find_promotion_by_name(name);
if (!result_promotion) { // 4
result_promotion = { name: name, products: [], code: promotion.code };
result.push(result_promotion); // 5
}
result_promotion.products.push(name); // 6
}
}
This code is OK, and it should get the job done (untested). However, it is still a bit unreadable. It does not follow the pseudo-code very closely. It somehow still hides the algorithm. It is hard to be sure that it is completely correct. So, we want to rewrite it. Functions like Array#foreach make it easier to do this. the top level can simply be:
var result = [];
data.forEach(processProduct);
In other words, call the processProduct function for each element of data (the list of products). It will be very hard for this code to be wrong, as long as `processProduct is implemented incorrectly.
function processProduct(product) {
product.promotion.forEach(processPromotion);
}
Again, this logic is provably correct, assuming processPromotion is implemented correctly.
function processPromotion(promotion) {
var result_promotion = getPromotionInResults(promotion);
result_promotion.products.push(name);
}
This could hardly be clearer. We obtain the entry for this promotion in the results array, then add the product to its list of products.
Now we need to simply implement getPromotionInResults. This will include the logic to create the promotion element in the results array if it doesn't exist.
function getPromotionInResults(promotion) {
var promotionInResults = findPromotionInResultsByName(promotion.name);
if (!promotionInResults) {
promotionInResults = {name: promotion.name, code: promotion.code, products: []};
result.push(promotionInResults);
}
return promotionInResults;
}
This also seems demonstrably correct. But we still have to implement findPromotionInResultsByName. For that, we can use Array#find, or some equivalent library routine or polyfill:
function findPromotionInResultsByName(name) {
return result.find(function(promotion) {
return promotion.name === name;
});
}
The entire solution is thus
function transform(data) {
// Given a product, update the result accordingly.
function processProduct(product) {
product.promotion.forEach(processPromotion);
}
// Given a promotion, update its list of products in results.
function processPromotion(promotion) {
var result_promotion = getPromotionInResults(promotion);
result_promotion.products.push(name);
}
// Find or create the promotion entries in results.
function getPromotionInResults(promotion) {
var promotionInResults = findPromotionInResultsByName(promotion.name);
if (!promotionInResults) {
promotionInResults = {name: promotion.name, code: promotion.code, products: []};
result.push(promotionInResults);
}
return promotionInResults;
}
// Find an existing entry in results, by its name.
function findPromotionInResultsByName(name) {
return result.find(function(promotion) {
return promotion.name === name;
});
}
var result = [];
data.forEach(processProduct);
return result;
}
Ok, after a few hours of works, with lots of help online and offline, I finally made it works. Thanks for the people who has helped.
Please do comment if you have a more elegant solution, always love to learn.
For people who ran into similar problem:
Here is my solution
function groupProductsByPromo(data){
var result = [];
// filter only product with promotion
var productsWithPromo = data.filter(function(product){
return product.hasOwnProperty('promotions');
});
// create promotions map
var mappedProducts = productsWithPromo.map(function(product) {
var mapping = {};
product.promotions.forEach(function(promotion) {
mapping[promotion.code] = {
promotion: promotion
};
});
return mapping;
});
// reduce duplicates in promotion map
mappedProducts = mappedProducts.reduce(function(flattenObject, mappedProducts) {
for (var promoCode in mappedProducts) {
if (flattenObject.hasOwnProperty(promoCode)) {
continue;
}
flattenObject[promoCode] = {
code: promoCode,
name: mappedProducts[promoCode].promotion.name
};
}
return flattenObject;
}, {});
// add products to promo item
for(var promoCode in mappedProducts){
mappedProducts[promoCode].products = productsWithPromo.filter(function(product){
return product.promotions.some(function(promo){
return promo.code === promoCode;
});
});
result.push(mappedProducts[promoCode]);
}
return result;
}
Check out lodash - a nifty library for doing all sorts of transforms.
lodash.groupBy is what you're looking for.
I have an array whose items I want to group, and then display in this grouped fashion. It's all terribly confusing:
App.GroupedThings = Ember.ArrayProxy.extend({
init: function(modelToStartWith) {
this.set('content', Ember.A());
this.itemsByGroup = {};
modelToStartWith.addArrayObserver(this, {
willChange: function(array, offset, removeCount, addCount) {},
didChange: function(array, offset, removeCount, addCount) {
if (addCount > 0)
// Sort the newly added items into groups
this.add(array.slice(offset, offset + addCount))
}
});
},
add : function(things) {
var this$ = this;
// Group all passed things by day
things.forEach(function(thing) {
var groupKey = thing.get('date').clone().hours(0).minutes(0).seconds(0);
// Create data structure for new groups
if (!this$.itemsByGroup[groupKey]) {
var newArray = Ember.A();
this$.itemsByGroup[groupKey] = newArray;
this$.get('content').pushObject({'date': groupKey, 'items': newArray});
}
// Add to correct group
this$.itemsByGroup[groupKey].pushObject(thing);
});
}
});
App.ThingsRoute = Ember.Route.extend({
model: function() {
return new App.GroupedThings(this.store.find('thing'));
},
});
This only works if I use the following template:
{{#each model.content }}
These don't render anything (an ArrayController is used):
{{#each model }}
{{#each content }}
{{#each}}
Why? Shouldn't the ArrayController proxy to "model" (which is GroupedThings), which should proxy to "content"?
The reason this becomes a problem is that I then want to sort these groups, and as soon as I change the entire contents array (even using ArrayProxy.replaceContent()), the whole views rebuilt, even if only a single item is added to a single group.
Is there a different approach to this entirely?
I've tended to use ArrayProxies slightly differently when doing such things.
I'd probably get Ember to do all the heavy lifting, and for sorting get it to create ArrayProxies based around a content collection, that way you can sort them automatically:
(note I haven't run this code, but it should push you off in the right direction)
App.GroupedThings = Em.ArrayProxy.extend({
groupKey: null,
sortKey: null,
groupedContent: function() {
var content = this.get('content');
var groupKey = this.get('groupKey');
var sortKey = this.get('sortKey');
var groupedArrayProxies = content.reduce(function(previousValue, item) {
// previousValue is the reduced value - ie the 'memo' or 'sum' if you will
var itemGroupKeyValue = item.get('groupKey');
currentArrayProxyForGroupKeyValue = previousValue.get(itemGroupKeyValue);
// if there is no Array Proxy set up for this item's groupKey value, create one
if(Em.isEmpty(currentArrayProxyForGroupKeyValue)) {
var newArrayProxy = Em.ArrayProxy.createWithMixins(Em.SortableMixin, {sortProperties: [sortKey], content: Em.A()});
previousValue.set(itemGroupKeyValue, newArrayProxy);
currentArrayProxyForGroupKeyValue = newArrayProxy;
}
currentArrayProxyForGroupKeyValue.get('content').addObject(item);
return previousValue;
}, Em.Object.create());
return groupedArrayProxies;
}.property('content', 'groupKey', 'sortKey')
);
You'd then Create a GroupedThings instance like this:
var aGroupedThings = App.GroupedThings.create({content: someArrayOfItemsThatYouWantGroupedThatHaveBothSortKeyAndGroupKeyAttributes, sortKey: 'someSortKey', groupKey: 'someGroupKey'});
If you wanted to get the groupedContent, you'd just get your instance and get.('groupedContent'). Easy! :)
...and it'll just stay grouped and sorted (the power of computed properties)... if you want an 'add' convenience method you could add one to the Em.Object.Extend def above, but you can just as easily use the native ones in Em.ArrayProxy, which are better IMHO:
aGroupedThings.addObjects([some, set, of, objects]);
or
aGroupedThings.addObject(aSingleObject);
H2H
Some question about ability of writing hybrid getters/setters... For my chess app I have an object diagram much more similar to a graph than a tree. So I decide to pool the various kind of objects in a data structure holding both object instances and relations between them.
Something like:
// in js/app/base/pools.js
var Pool = function() {
this.items = {};
this.relations = {};
};
/**
* store an object into the pool
* and augment it with relation/pooling capabilities
* #param string key
* #param Object obj
*/
Pool.prototype.store = function(key, obj) {
var self = this;
Object.defineProperty(obj.constructor.prototype, "pool", {
set : undefined,
get : function() {
return self;
}
});
this.items[key] = obj;
};
/**
* looks for an object in the pool
* #param string
*/
Pool.prototype.find = function(key) {
return this.items[key];
};
Relations are stored as pairs [obj1, obj2] in the "relations" property
of the Pool instance. I have basically two kinds of relationship:
One-to-one: unary correspondences like Chessman <--> Location, or interface implementation like Chessman <--> Pawn | ... | King
One-to-many like Board [x1] <--> [x64] Tile
Those relations are designed (by the pool way) to be bi-directional and have to be set atomically (e.g. transactions) since object cross-references need to be "ACID", for the above example, 1 Board contain 64 Tiles, and each tile knows about its standing board.
For 1-to-1 relations, maybe no problem, since I can set:
chessman.location = location;
// AND AT THE SAME TIME :
location.chessman = chessman;
// with two Object.defineProperty(...) combined
The trouble comes with 1-N relations since I would be able to write:
// 1st : defining relation
// ... (see below)
// 2nd setting a relation
board1.tiles.add(tile_63);
// and
tile_63.board = board1;
// 3rd getting values
board1.tiles --> a collection of tiles (array)
tile_63.board --> board1 object
In the main program the relation is given to the Pool instance by passing a parameter object:
pool.defineRelation("board-contains-tiles", {
tiles : { subject : boards.Board, multiple : true },
board : { subject : tiles.Tile, multiple : false }
});
To define the relation, 1-side is a normal getter/setter, but N-side is more a getter-adder since we have to populate (the board with tiles). So this doesn't work:
Pool.prototype.defineRelation = function(alias, definition) {
this.relations[alias] = [];
var self = this, linker;
var relation = self.relations[alias];
var subject, multiple;
// iterate through relation short names
for(name in definition) {
subject = definition[name].subject;
multiple = definition[name].multiple;
console.log("loop with name : " + name + " subject is : " + subject);
var getter = function() {
var result = [];
for(r = 0; r < relation.length; r++) {
// [x,y] storing
if(relation[r][0] == this)
result.push( relation[r][1]);
// [y,x] storing
if(relation[r][1] == this)
result.push( relation[r][0]);
return result;
};
var setter;
if(multiple) {
setter = function() {};
setter.add = function(x) { relation.push([this, x]); };
} else {
setter = function(x) { relation.push([this, x]); };
}
Object.defineProperty(subject.prototype, name, {
set : setter,
get : getter
});
}
};
Question:
I'm figuring it's possible to do that, but how? Or in a better way, like Delphi's TComponent, or like the DOM tree?
SEE ALSO:
The old, ugly, and messy codebase can be found on my website:
www.eozine.fr --> Jet d'echecs --> ColorChess
If you want to see a previous result (2009)
I am almost there with this but cannot seem to get this functionality going as planned.
I have json 'jsonData' it contains formula of different terms
"jsonData":{
"a" : "b + c",
"b" : "d + e",
"d" : "h + i",
"c" : "f + g"
}
What I am trying to do is to have a function pass one arguments 'mainItem'(ie. one of the key in 'jsonData' for example a in 'jsonData'). Within this function it will get the formula from the json data(for example a is the 'mainitem' and b + c is the formula) and check the dependency of child component of the formula i.e it will check whether b and c have any dependency down the line in json data. If it has any dependency it will be added as a child component to the parent for example if b have a formula in json data. b will be added as the 'mainitem' in the child component of the parent 'mainitem' a. At the end of the code, this is what I would like to get.
{
mainitem : "a",
formula : "b+c",
childComponent: {
mainitem: "b",
formula : "d+e",
childcomponent: {
mainitem: "d",
formula : "h+i"
}
},
{
mainitem: "c",
formula : "f+g"
},
}
The issue is that I am able to create the parent object. But I have no idea how to create the child component for the parent and If the child component have sub child it will also embedded as the child of the child component and so on. It is like a parent child hierarchy series
function getJson(mainItem) {
var json = {};
json['mainitem'] = mainItem;
$.each(jsonData, function(key, value){
if(mainitem == key){
json['formula'] = value;
}
})
}
Any insight into this would highly be appreciated. Thank you.
You need/could to write a recursive function that splits up the "formula" into each composing component/item and then check each component/item for their dependencies.
Here is a solution for you: http://jsfiddle.net/mqchen/4x7cD/
function getJson(item, data) {
if(!data["jsonData"].hasOwnProperty(item)) return null;
var out = {
mainItem: item,
formula: data["jsonData"][item]
};
// Break up formula
var components = out.formula.split(" ");
for(var i = 0; i < components.length; i++) {
var child = getJson(components[i], data); // Recursive call to get childComponents
if(child !== null) {
out["childComponent"] = out["childComponent"] == undefined ? [] : out["childComponent"];
out["childComponent"].push(child);
}
}
return out;
}
// Call it
getJson("a", data)
Note: It does not consider circular dependencies, i.e. if you have a: "b + c", b: "d + a".
This is a dependency problem. I can already tell you ahead of time that you will need to figure out a way to handle circular dependencies (you don't want to get thrown into an infinite loop/recursion when trying to generate the output). Let's go through the basic algorithm. What are some things we need?
We need a way to parse the formulas so that they're meaningful. For this example, I'm gonna assume that we'll get an input that's of the form a + b + c, where I only expect the item and + to delimit each item.
We need a way to recurse down an item's dependencies to create nested childcomponents.
// assuming jsonData is available in this scope
function getStructure (elem) {
elem = $.trim(elem);
// handling an element that doesn't exist in jsonData
if (!jsonData[elem]) {
return {
'mainitem': elem,
'formula': 'none' // or whatever you want to put
};
}
var result = {},
formula = jsonData[elem],
children = formula.split('+'),
i;
result['mainitem'] = elem;
result['formula'] = formula;
// or however you want to store the child components
result['childComponent'] = [];
for (i = 0; i < children.length; i += 1) {
result['childComponent'].push(getStructure(children[i]));
}
return result;
}
Yeah this is some sort of building Syntax Tree as in classic parser/compiler problem.
Any ways I have written this simple recursive function that does what you want. Though if your goal is to build some sort of parser then you must think of following parser / compiler building principles since that will keep things manageable and graspable once functions start growing.
function getJson(mainitem,outjson)
{
formula = jsonData[mainitem];
outjson.mainitem = mainitem;
if (formula != null)
{
outjson.formula = formula;
var firstItem = formula.toString().charAt(0);
var secondItem = formula.charAt(formula.length - 1);
outjson.firstchild = {};
outjson.secondchild = {};
getJson(firstItem, outjson.firstchild);
getJson(secondItem, outjson.secondchild);
}
}
What all you have to do is to create an empty object and pass it to getJson() along with the operand in problem i.e. mainitem:
var outjson = {};
getJson("a", outjson);
I have used JSON library to convert outjson object to JSON text.
Also I have logged this outjson so that you can examine it in the embedded firebug lites' Console window.
Find it at JSFiddle.
There is approved answer already but here are my 5 cents.
as #Mahesha999 said you need to build "Syntax Tree as in classic parser/compiler".
For some theory + examples look at those videos
They are focused on antlr but also contains a lot theory about parsers. Also antlr have javascript plugin that can be used.
I think it's better than any expression evaluation.
I'm building a web-app that needs to process nested geographical data to both display in a treeview, but also be searchable. The raw data looks something like this:
id:1, name:UK
id:2: name: South-East, parentId: 1
id:3: name: South-West, parentId:1
id:4: name: Berkshire, parentId: 2
id:5: name: Reading, parentId: 4
and I want it to look something like this:
id:1: name UK, children[
{id: 2, name: South-East, children:[
{id:4: name: Berkshire, children: [
{id:5: name: Reading}
]
},
{id:3: name: South-West}
]
so that each geographical location has a "children" array property, which contains all the sub-areas, each of which has another "children" array property, and so on. It would probably make sense to have a "parent" property as well, so I could navigate from any child item up to its parent.
I also need to be able to search the list - searching each branch of the tree may take some time, so perhaps I need to also keep the list in flat format.
I know how I could do this in JavaScript (possibly using jLinq for filtering, grouping and sorting), but I don't know how fast it would be. Has anyone already had a go at this in JavaScript or know of any general algorithms/patterns that solve this?
It's actually not that difficult to make the flat array into a tree and do it pretty quickly, I think the slowest bit will be getting the definition of the data structure onto the page (hence why you're lazy loading was successful!). This can be helped though by making the data structure definition smaller.
In Javascript I did it like this:
//Make the data definition as small as possible..
//each entry is [ name, parent_pos_in_array]..
//note: requires that a parent node appears before a child node..
var data = [
["UK", -1], //root..
["South-East", 0],
["South-West", 0],
["Berkshire", 1],
["Reading", 3]
//...etc...
];
//Turns given flat arr into a tree and returns root..
//(Assumes that no child is declared before parent)
function makeTree(arr){
//Array with all the children elements set correctly..
var treeArr = new Array(arr.length);
for(var i = 0, len = arr.length; i < len; i++){
var arrI = arr[i];
var newNode = treeArr[i] = {
name: arrI[0],
children: []
};
var parentI = arrI[1];
if(parentI > -1){ //i.e. not the root..
treeArr[parentI].children.push(newNode);
}
}
return treeArr[0]; //return the root..
}
var root = makeTree(data);
To test the speed on a larger list you can run:
var data = [['root', -1]];
for(var i = 1; i < 100000; i++){
var parentI = Math.floor(Math.random()*(i-1));
data.push(['a_name', parentI]);
}
var start = new Date().getTime();
var tree = makeTree(data);
var end = new Date().getTime();
console.log('Took: ' + (end-start) + 'ms.');
With 100000 elements in the array it takes < 200ms on my old desktop. Not sure what kind of performance is acceptable though!
If you have a simple id & parent-id objects array with no other clue on the level, it's a tough task to generate the nested form. I would assume recursive approaches won't be practical in long lists. The best method that i could come up with so far is sorting that array in such a way that all children come after their parents. Parents and children and even the root objects can be mixed but a child must come after it's parent. Assuming that the object structure is like var data = [{id: KL442, pid: HN296}, {id: ST113, pid: HN296}, {id: HN296, pid: "root"},...] Yet sorting is the first phase of the job. While sorting we can generate a LUT (look up table) to access the parents almost at no cost. At the exit of the outer loop just one single instruction lut[a[i].id]=i; is sufficient for this. This makes the job enormously fast at the nesting stage. This is the sorting and LUT preparation phase.
function sort(a){
var len = a.length,
fix = -1;
for (var i = 0; i < len; i++ ){
while(!!~(fix = a.findIndex(e => a[i].pid == e.id)) && fix > i) [a[i],a[fix]] = [a[fix],a[i]];
lut[a[i].id]=i;
}
return a;
}
Once you have it sorted than a reverse iteration is the only thing you have to do to get your nested structure. So that you now have your data array sorted and LUT prepared, then this is the code for nesting.
for (var i = sorted.length-1; i>=0; i--)
sorted[i].pid != "root" && (!! sorted[lut[sorted[i].pid]].children
&& sorted[lut[sorted[i].pid]].children.push(sorted.splice(i,1)[0])
|| (sorted[lut[sorted[i].pid]].children = [sorted.splice(i,1)[0]]));
For a working sample you can check a previous answer of mine.
With Lodash:
var index = _.mapKeys(data,'id');
var obj = {};
_.each(index,function(v){
if(!v.parentId){
obj[v.id]=v;
}else{
if(!index[v.parentId].children){
index[v.parentId].children=[];
}
index[v.parentId].children.push(v);
}
});
Demo is here