I am a beginner, prompt algorithm for constructing a tree from an array of the form:
var data = [
{id: 1,level:1,left_key:1,right_key:12, caption: "Books"},
{id: 2,level:2,left_key:2,right_key:11, caption: "Programming"},
{id: 3,level:3,left_key:3,right_key:4, caption: "Languages"},
{id: 4,level:3,left_key:5,right_key:10, caption: "Databases"},
{id: 5,level:4,left_key:6,right_key:7, caption: "MongoDB"},
{id: 6,level:4,left_key:8,right_key:9, caption: "dbm"}
];
The data format is taken from here link.
From the data in this format on request from the Mongodb database to be built tree species:
<ol>
<li>
<span> Books </span>
<ol>
<li>
<span> Programming </span>
</li>
</ol>
</li>
</ol>
I can not understand the principle of tree traversal.
P.S. I'd like to do without the third-party libraries
I guess you need something like this
function make_tree() {
var tree = document.getElementById("tree");
data.forEach(function(elem){
var elem_parent = document.getElementById("tree_" + elem.level);
if(elem_parent){
var cur_element = document.createElement("li");
cur_element.setAttribute('id','tree_' + elem.level + "_" + elem.id);
cur_element.innerHTML = "<span>" + elem.caption + "</span>";
elem_parent.appendChild(cur_element);
}
else {
var cur_parent = document.createElement("ol");
cur_parent.setAttribute('id','tree_' + elem.level);
var cur_element = document.createElement("li");
cur_element.setAttribute('id','tree_' + elem.level + "_" + elem.id);
cur_element.innerHTML = "<span>" + elem.caption + "</span>";
cur_parent.appendChild(cur_element);
parent_before = document.getElementById("tree_" + (elem.level - 1));
if (parent_before) parent_before.appendChild(cur_parent);
else tree.appendChild(cur_parent);
}
});
}
EXAMPLE FIDDLE
The better answer to this is to simply store the tree structure within your MongoDB document:
{
"0": {
"caption": "Books",
"items": {
"0": {
"caption": "Programming",
"items": {
"0": {
"caption": "Languages",
},
"1": {
"caption": "Databases",
"items": {
"0": {
"caption": "MongoDB"
},
"1": {
"caption": "dbm"
}
}
}
}
}
}
}
}
That has a natural order to it and provides in a single read a very easy way to get a structure that can be traversed in the concept of building a menu.
It is also very simple to update, so to add a new item to, say the "Languages" level, then you have a clear path to update:
db.collection.update(
{},
{
"$set": {
"0.items.0.items.0.items.0.caption": "C++"
}
}
)
And to add another:
db.collection.update(
{},
{
"$set": {
"0.items.0.items.0.items.1.caption": "Pascal"
}
}
)
Now this as with all tree structures does require some knowledge of the level you are inserting at. The design here ( and horribly over-engineered ) is at least allowing easy updates. It is a structure that would be notoriously bad to query but that is not the point.
But of course the limitation here is for "small" tree structures (which is still under 16MB and a quite a lot of tree), but for anything that was particularly large that is where larger model implementations come in
The thing with processing trees in the way as presented in the sample data is that each node needs to be pulled in one at at time. So exactly as stated in the documentation you are pulling results like this:
var databaseCategory = db.categories.findOne( { _id: "Databases" } );
db.categories.find({
left: { $gt: databaseCategory.left },
right: { $lt: databaseCategory.right }
});
So there is a a way of pulling all the children out from a given node, but now consider what you need to do in order to add any new items:
So in order to add something in the languages node as was done earlier, not only are you dealing with inserting a new value, but also updating all the other node values in the tree to represent there position as shown in the example documentation.
There are ways around that by "bucketing" the values on nodes in order to allow for insertion and removal, but much as the whole subject of modelling tree structures like this, is way out of scope for an answer on this site.
So for your given use case of producing a menu, you are better off using the native data structure features of your storage and simply storing and calling the data with a native structure to how you are going to use it.
Related
I'm a little bit new to programming and very new to JS so I apologize for the beginner question.
I'm trying to iterate through this data and get each tracks name and artist but I'm having an issue. Currently I'm trying something like this.
If anybody has any insight or suggestions I would appreciate it greatly.
I'm using a rails backend with JS frontend. Thank you!
function selectTracks(){
fetch(BACKEND_URL)
.then(response => response.json())
.then(playlist => {
playlist.data.forEach(playlist => {
`<h4> ${playlist.attributes.track.name}</h4>
<h4>${playlist.attributes.track.artist}></h4> `
// let newPlaylist = new Playlist(playlist, playlist.attributes)
console.log(fetch)
// document.getElementById("playlist-container").innerHTML += newPlaylist.renderPlaylistCard();
debugger
}
)}
)
}
My serializer looks like this
{
data: [
{
id: "1",
type: "playlist",
attributes: {
name: "Country Songs",
id: 1,
track_id: 10,
track: {
id: 10,
name: "First Song",
artist: "Randy",
created_at: "2020-06-17T02:09:07.152Z",
updated_at: "2020-06-17T02:09:07.152Z"
}
},
relationships: {
track: {
data: {
id: "10",
type: "track"
}
}
}
}
]
}
You need to replace forEach with map. The 'forEachloop doesn't return anything. But themapmethod return an array. (An array of HTML elements in your case
fetch(BACKEND_URL)
.then(response => response.json())
.then(playlist => {
return playlist.data.map(playlist => {
`<h4> ${playlist.attributes.track.name}</h4>
<h4>${playlist.attributes.track.artist}></h4> `
// let newPlaylist = new Playlist(playlist, playlist.attributes)
console.log(fetch)
// document.getElementById("playlist-container").innerHTML += newPlaylist.renderPlaylistCard();
debugger
}
)}
)
Your code technically works assuming that the BACKEND_URL is correct and the json is valid. But, in its current state, it doesn't do anything with the data. If you output the h4 tags, for instance, you should see them written to the screen.
document.write(
`<h4> ${playlist.attributes.track.name}</h4>
<h4>${playlist.attributes.track.artist}</h4> `
)
Or alternatively you could log the values out to prove that you are processing the data correctly:
console.log(playlist.attributes.track.name, playlist.attributes.track.artist)
If not, the next thing to check is the validity of your json. I'm assuming that you copied your json from the browser which will strip some quotes for readability. View the source to ensure that the key names are correctly wrapped in quotes like this:
{
"data": [
{
"id": "1",
"type": "playlist",
"attributes": {
"name": "Country Songs",
...
If you are using ActiveModel Serializers, they should be formatted correctly.
If your json is valid and you can write the h4 tags to the page with the correct data in them, then the problem probably lies in your Playlist class.
Another handy tool for diagnosing fetch() problems is in Chrome Developer Tools. Go to the Network and click the XHR filter. This will allow you to inspect the fetch request and see if the response is valid and the data is what you expect. Other browsers have a similar feature.
I use Meteor to query a mongo collection. It has for example the following entry:
{
"_id": "uCfwxKXyZygcWQeiS",
"gameType": "foobar",
"state": "starting",
"hidden": {
"correctAnswer": "secret",
"someOtherStuff": "foobar"
},
"personal": {
"Y73uBhuDq2Bhk4d8W": {
"givenAnswer": "another secret",
},
"hQphob8s92gbEMXbY": {
"givenAnswer": "i have no clue"
}
}
}
What I am trying to do now is:
don't return the values behind "hidden"
from the "personal" embedded document only return the values for the asking user
In code it would look something like this:
Meteor.publish('game', function() {
this.related(function(user) {
var fields = {};
fields.hidden = 0;
fields.personal = 0;
fields['personal.' + this.userId] = 1;
return Games.find({}, {fields: fields});
}, Meteor.users.find(this.userId, {fields: {'profile.gameId': 1}}));
}
Obviously this won't work, because MongoDB won't allow mixed includes and excludes. On the other hand, I cannot switch to "specify only the included fields", because they can vary from gameType to gameType and it would become a large list.
I really hope that you can help me out of this. What can I do to solve the problem?
Typical example of where to use the directly controlled publication features (the this.added/removed/changed methods).
See the second example block a bit down the page at http://docs.meteor.com/api/pubsub.html#Meteor-publish.
With this pattern you get complete control of when and what to publish.
I've got the following document named "clients" which includes id, name and list of projects (array of objects):
{
"_id": {
"$oid": "572225d997bb651819f379f7"
},
"name": "ppg",
"projects": [
{
"name": "aaa",
"job_description": "",
"projectID": 20
},
{
"name": "bbbb",
"job_description": "",
"projectID": 21
}
]
}
I would like to update "job_description" of project with given "projectID" like this:
module.exports.saveJobDesc = function(client, idOfProject, textProvided) {
db.clients.update({ name: client},
{ $set: {'projects.0.job_description': textProvided }});
};
But instead of hardcoded index "0" of array I want to find specific project using "projectID". Is there a way to achieve this without changing the structure of collection and/or document?
If you want to update the "job_description" where name="ppg" and project_id=20 then you can use below mongo query:-
db.clients.update({ "name":"ppg","projects.projectID":20 },{$set: {"projects.$.job_description": "abcd"}})
Please let me know if any thing else is required
You cannot update multiple array elements in single update operation, instead you can update one by one which takes time depends upon number of elements in array and number of such documents in collection. see New operator to update all matching items in an array
db.test2.find().forEach( function(doc) {
var projects = doc.projects;
for(var i=0;i<projects.length;i++){
var project = projects[i];
if(project.projectID == 20){
var field = "projects."+i+".job_description";
var query = {};
query[field] = "textasdsd";
db.test2.update({ _id: doc._id},{ $set:query});
}
}
})
i'm not sure if the title of the questions fits, if you know a better one, let me know ;)
I just named it like this, because i'm thinking if i could solve my problem with a custom reduce function.
I have two types of objects:
Vehicles:
{
"id": "1G1JC5444R7252367",
"type": "Vehicle"
}
Users:
{
"company": "companyname",
"type": "User",
"parts": [
{
"company": "companyname",
"id": "1G1JC5444R7252367",
"active": true
},
{
"company": "companyname",
"id": "1G1135644R7252367",
"active": false
}
]
}
What i want is a View which returns me all vehicles of a certain company. But the company is only stored in the User object.
This is how far I got in the mapfunction:
function (doc, meta) {
if(doc.type == 'User'){
if(doc.parts){
Array.prototype.contains = function ( needle ) {
for (var i in this) {
if (this[i] == needle) return true;
}
return false;
};
var ids = new Array(doc.parts.length);
for(var k in doc.parts){
if(doc.parts[k].active) {
if(!vins.contains(doc.parts[k].id)) {
if (doc.parts[k].company && doc.parts[k].id ) {
ids.push(doc.parts[k].id);
emit(doc.parts[k].company, doc.parts[k].id);
}
}
}
}
}
}
}
But this only returns me the company as key and the id of the vehicle as value. So i get a User document. Can I somehow loop through the documents again in the map function and get all vehicles according to the ids in my ids array?
Saving the company in the vehicle itself also is not desired, because the company is not the vehicles company itself but the company of the parts.
Thanks for any help in forward.
A Couchbase view can only operate on the document presented to it. As you discovered, it can only partially do what you want.
The real problem isn't the view though but is your data model. You appear to have designed your data model as if you were using a relational database. The calculation you are attempting is a kind of join.
A fundamental concept with document databases is that a document should represent all of the information pertinent to some kind of event. This concept is what allows document databases to horizontally scale. You should not worry about data duplication. Locality of access is the key to an appropriate map-reduce data model.
I would redesign your data model.
I am trying to visualize team collaboration data, in a way like this:
Different colors in the chart are different collaboration artifact types.
The data from the source looks like this:
var json = [
{
"teamLabel": "Team 1",
"created_date": "2013-01-09",
"typeLabel": "Email"
"count": "5"
},
{
"teamLabel": "Team 1",
"created_date": "2013-01-10",
"typeLabel": "Email"
"count": "7"
},
/* and of course, a lot more data of this kind */
]
Note that the data is given for single days. So for the above visualization, I need to aggregate the data based on the week of year first. The team name and the artifact type need to be preserved though and are used as grouping attributes. Here's the code:
// "2013-01-09"
var dateFormat = d3.time.format.utc("%Y-%m-%d");
// "2013-02" for the 2nd week of the year
var yearWeek = d3.time.format.utc("%Y-%W");
var data = d3.nest().key(function(d) {
return d.teamLabel;
}).key(function(d) {
var created_date = dateFormat.parse(d.created_date);
return yearWeek(created_date);
})
.key(function(d) {
return d.typeLabel;
}).rollup(function(leaves) {
return d3.sum(leaves, function(d) {
return parseInt(d.count); // parse the integer
});
}
)
.map(json);
This results in an Object hierarchy based on the nesting keys. I do not see how to create the above chart from this, so I am rather looking for a way to convert data into the following structure:
[
// This list contains an element for each donut
{
"teamLabel": "Team 1",
"createdWeek": "2013-02",
"values": [
// This list contains one element for each type we found
{
"typeLabel": "Email",
"count": 12
},
{
...
}
]
},
{
...
}
]
This way, I can use createdWeek and teamLabel for the positioning on x- and y-Axis respectively, and the information under values can be passed to d3.layout.pie().
Is there a clean way to do this data transformation? If you need any clarification or further details, please let me know.
That's how you do it:
var flat = data.entries().map(function(d){
return d.value.entries().map(function(e){
return {
"teamLabel": d.key,
"createdWeek": e.key,
"values": e.value.entries().map(function(f){
return {"typeLabel": f.key, "count": f.value}
})
}
})
}).reduce(function(d1,d2){ return d1.concat(d2) },[]);
Note that I'm using d3.map instead of the standard javascript object in order to use the map.entries() helper function. I imagine that's what you tried judging by the fact that you're using:
.map(json); // whereas I used .map(json, d3.map)
instead of
.entries(json);
jsFiddle link here:
http://jsfiddle.net/RFontana/KhX2n/