Finding optimal nodes whose presence satisfies nested JSON logic - javascript

Below is the JSON, I am using to generate following UI:
{
"Text" : "1. Parent group"
"Logic" : "all of the following",
"Id":"1" //some unique id
"children": [
{
"Text' : "1.1 Level Child group"
"Logic' : "1 or more of the following",
"Id':"1.1" //some unique id,
"children" : [
{
"Text' : "1.1.1 Level Child group"
"Logic' : "all of the following",
"Id':"1.1.1" //some unique id,
"children": [
{
"Text' : "1.1.1.1 Level Child"
"Id':"1.1.1.1" //some unique id,
},
{
"Text' : "1.1.1.2 Level Child"
"Id':"1.1.1.2" //some unique id,
"Logic" : "one or more of the following",
"children" : [
{
"Text": "1.1.1.2.1 Level Child",
"Id": "1.1.1.2.1"
},
{
"Text": "1.1.1.2.2 Level Child",
"Id": "1.1.1.2.2"
},
]
}
]
},
{
"Text' : "1.1.2 Level Child group"
"Id':"1.1.2" //some unique id,
},
{
"Text' : "1.1.3 Level Child group"
"Id':"1.1.3" //some unique id,
},
{
"Text' : "1.1.4 Level Child group"
"Logic' : "one or more of the following",
"Id':"1.1.4" //some unique id,
"children": [
{
"Text' : "1.1.4.1 Level Child"
"Id':"1.1.4.1" //some unique id,
},
{
"Text' : "1.1.4.2 Level Child"
"Id':"1.1.4.2" //some unique id,
}
]
},
{
"Text' : "1.1.5 Level Child group"
"Logic' : "all of the following",
"Id':"1.1.5" //some unique id,
"children": [
{
"Text' : "1.1.5.1 Level Child"
"Id':"1.1.5.1" //some unique id,
},
{
"Text' : "1.1.5.2 Level Child"
"Id':"1.1.5.2" //some unique id,
}
]
}
]
},
{
"Text" : "1.2 Level Child"
"Id":"1.2" //some unique id
}
]
}
1. Parent group (all of the following)
1.1 Level Child group (1 or more )
1.1.1 Level Child group (all of the following)
1.1.1.1 Level Child // selectable
1.1.1.2 Level Child // selectable
1.1.2 Level Child // selectable
1.1.3 Level Child // selectable
1.1.4 Level Child group (one or more of the following)
1.1.4.1 Level Child // selectable
1.1.4.2 Level Child // selectable
1.1.5 Level Child group (all of the following)
1.1.5.1 Level Child // selectable
1.1.5.2 Level Child // selectable
1.2 Level Child // selectable
Node working logic:
only nodes with no children can be selected by the user.
Node is implicitly selected if the Logic is "all of the following" if and only if all its nested children are selected
Node is implicitly selected if the logic is "n or more of the following" if only if its "n" nested children are selected
Scenario: If the user selects 1.1.2(it implicitly satisfies 1.1) and 1.2 then "1. parent group" is "Met", so it is selected(makes the whole group met);
Scenario: If the user selects 1.1.1.1, the user has to select 1.1.1.2 so that 1.1.1 is "Met" and selecting 1.2, will make the whole group as "Met".
Actual need is to find the optimal ids when "parent group is not met":
If the selecting any nodes and the top most parent group is still not "met".
Then On a button click, I need to find and return the optimal node ids
whose presence would have made the "parent group" to be "Met".
NOTE : when finding the optimal node ids we should importance to child groups if any of its children are selected.
For eg:
If only 1.2 is selected by the user, the fastest way to make the
"parent group" to met is to select 1.1.2. But if 1.1.1 has any children
selected, then I should give precedence to 1.1.1 node, as result I
need to return following ids: [
1.1.1.2, 1.1.1, 1.1 ]
Not sure what type of algorithm, I should be using to solve this problem.

Here a short approach by having a look to the constraints, which are now in the data set metioned with q: 'some' or q: 'every'.
function select(node, selected) {
function iter(node) {
if (!node.children) return [node.Id];
var temp = node.children.map(iter);
if (node.q === 'every') return [...temp.flat(), node.Id];
var filtered = temp.filter(a => a.some(v => selected.includes(v)));
if (filtered.length) return [...filtered.flat(), node.Id];
return [...temp.reduce((a, b) => b.length < a.length ? b : a), node.Id];
}
return iter(node).filter(v => !selected.includes(v));
}
var data = { q: 'every', Text: "1. Parent group", Logic: "all of the following", Id: "1", children: [{ q: 'some', Text: "1.1 Level Child group", Logic: "1 or more of the following", Id: "1.1", children: [{ q: 'every', Text: "1.1.1 Level Child group", Logic: "all of the following", Id: "1.1.1", children: [{ Text: "1.1.1.1 Level Child", Id: "1.1.1.1" }, { Text: "1.1.1.2 Level Child", Id: "1.1.1.2" }] }, { Text: "1.1.2 Level Child group", Id: "1.1.2" }, { Text: "1.1.3 Level Child group", Id: "1.1.3" }, { q: 'some', Text: "1.1.4 Level Child group", Logic: "one or more of the following", Id: "1.1.4", children: [{ Text: "1.1.4.1 Level Child", Id: "1.1.4.1" }, { Text: "1.1.4.2 Level Child", Id: "1.1.4.2" }] }, { q: 'every', Text: "1.1.5 Level Child group", Logic: "all of the following", Id: "1.1.5", children: [{ Text: "1.1.5.1 Level Child", Id: "1.1.5.1" }, { Text: "1.1.5.2 Level Child", Id: "1.1.5.1" }] }] }, { Text: "1.2 Level Child", Id: "1.2" }] };
console.log(select(data, ["1.2"]));
console.log(select(data, ["1.2", "1.1.1.1"]));
console.log(select(data, ["1.1.1.1"]));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

Complex top level parents by param of the lowest child Saving your hierarchy and without repeating his hierarchy

Good evening. Today I am looking for a recursive function that will help me find all the greatest parents of a child of an element of a multidimensional array of objects.
Saving your hierarchy and without repeating his hierarchy
const $tables = [{
"name": "years",
"parents": []
},
{
"name": "grades",
"parents": []
},
{
"name": "groups",
"parents": ["grades"]
},
{
"name": "areas",
"parents": []
},
{
"name": "subjects",
"parents": ["areas"]
},
{
"name": "nivels",
"parents": ["subjects"]
},
{
"name": "users",
"parents": []
},
{
"name": "assessment_types",
"parents": ["years", "groups", "subjects", "nivels"]
},
{
"name": "assessments",
"parents": ["assessment_types", "users"]
}
];
function getRelativeParents($search, $exclude = []) {
$finish = false;
$relatives = [];
while ($finish == false) {
$current_table = $tables.find($table => $table.name == $search);
if ($current_table.parents.length > 0) {
$search = $current_table.parents[0]; //I know that in this line i need a recursive funtion but i do not how make
$relatives.unshift($search);
} else {
$finish = true;
}
}
return $relatives;
}
for (item in $tables) {
if ($tables.some(table => table.name == $tables[item]["name"])) {
$tables[item]["all_relatives_parents"] = getRelativeParents($tables[item]["name"]);
}
}
console.log($tables);
The result I need is the following, if you can see in the field all_relatives_parents we store the hierarchy of each parent according to the order of the parents specified in the parents attribute, the complex part is in the parents that have multiple parents , as you can see in the object called assessments, it has two parents, and those parents like assessment_types have multiple parents, so I need to store their pecking order in the all_relatives_parents attribute
$results = [
{"name" : "years" , "parents":[],"all_relatives_parents" :[]},
{"name" : "grades" , "parents":[],"all_relatives_parents" :[]},
{"name" : "groups" , "parents":["grades"],"all_relatives_parents" :["grades"]},
{"name" : "areas" , "parents":[],"all_relatives_parents" :[]},
{"name" : "subjects" , "parents":["areas"],"all_relatives_parents" :["areas"]},
{"name" : "nivels" , "parents":["subjects"],"all_relatives_parents" : ["areas","subjects"]},
{"name" : "users" , "parents":[],"all_relatives_parents" :[]},
{"name" : "assessment_types" , "parents":["years","groups","subjects","nivels"], "all_relatives_parents": ["years","grades","groups","areas","subjects","nivels"]},
{"name" : "assessments" , "parents":["assessment_types","users"],"all_relatives_parents" :["assessment_types","years","grades","groups","areas","subjects","nivels","users"]}
];
Now I am going to explain why that is the reason for the results I need according to the most complex result for this case for the object assessments which is the one with the most parents at higher levels
"assessment_types" has the next parents
[
"years" : not has parents the result is empty [] If we join it to its child and keep the hierarchy the result is ["years"]
"groups" : has a parent and the result is ["grades"] If we join it to its child and keep the hierarchy ["grades", "groups"]
"subjects": has a parent and the result is ["areas"] If we join it to its child and keep the hierarchy ["areas", "subject"]
"nivels" : has a parent and the result is ["areas", "subject"] If we join it to its child and keep the hierarchy ["areas", "subject", "nivel"]
] if the values are not repeated and all the results are joined, the hierarchy that must be stored is ["years","grades","groups","areas","subjects","nivels","users"] If we join it to its child and keep the hierarchy the result is ["assessment_types","years","grades","groups","areas","subjects","nivels"]
"users" : not has parents the result is empty [] If we join it to its child and keep the hierarchy ["users"]
joining the parents of the object with the property assessments the hierarchy that must be stored is this ["assessment_types","years","grades","groups","areas","subjects","nivels","users"]
I really appreciate any help

MongoDB find in nested array aggregation

I have nested array documents explained below:
countries: [
{
"id": "id of country",
"cities": [
{
"id": "id of city 1",
"areas": [
{
"id": "id of area 1"
},
{
"id": "id of area 2"
},
{
"id": "id of area 3"
},
{
"id": "id of area 4"
}
]
},
{
"id": "id of city 2",
"areas": [
{
"id": "id of area 1"
},
{
"id": "id of area 2"
},
{
"id": "id of area 3"
},
{
"id": "id of area 4"
}
]
}
]
}
]
My target is to add a field using $addFields to indicate if a given id matching area ID or not.
{$addFields: {
isDeliveringToArea: {
$in: [ObjectId('5db5d11cb18a2500129732a5'),'$countries.cities.areas.id']
}
}}
but apparently $in doesn't work with nested arrays.
I want something like the find method works Model.find({'countries.cities.areas.id': 'areaID'}) but to return a boolean value in the aggregation.
Since there are 3 level nested arrays, we can achieve this with $map which is used to run all/modify the objects. First $map used to go through each country object, the second $map used to go each city objects inside each country object
Update 1
Since you need over all filed, you can do it with $anyElementTrue which helps if there is any element true on our condition, it will emit true.
Working Mongo play ground for overall country
[
{
"$addFields": {
isDeliveringToArea: {
$anyElementTrue: {
$map: {
input: "$countries",
in: {
$anyElementTrue: {
$map: {
input: "$$this.cities",
in: {
$in: [
"6",
"$$this.areas.id"
]
}
}
}
}
}
}
}
}
}
]
I keep the old query for your reference.
Working Mongo playground for each country object

Lodash: filter a nested object by multiple properties

Consider the following example:
var products = {
"Products": [{
"Title": "A",
"Categories": [{
"Name": "Type",
"Properties": ["Type 1", "Type 2", "Type 3"]
}, {
"Name": "Market",
"Properties": ["Market 1", "Market 2", "Market 3", "Market 4"]
}, {
"Name": "Technology",
"Properties": ["Tech 1", "Tech 2"]
}]
}, {
"Title": "B",
"Categories": [{
"Name": "Type",
"Properties": ["Type 1", "Type 3"]
}, {
"Name": "Market",
"Properties": "Market 1"
}, {
"Name": "Technology",
"Properties": ["Tech 1", "Tech 3"]
}]
}, {
"Title": "C",
"Categories": [{
"Name": "Type",
"Properties": ["Type 1", "Type 2", "Type 3"]
}, {
"Name": "Market",
"Properties": ["Market 2", "Market 3"]
}, {
"Name": "Technology",
"Properties": ["Tech 2", "Tech 3"]
}]
}]
}
I'm trying to filter products by their properties so consider I'm using an array to keep track of my selected filters:
var filters = ['Type 3', 'Tech 1'];
With these filters I would like to return product A and product B.
I currently have this:
var flattenedArray = _.chain(products).map('Categories').flatten().value();
var result= _.some(flattenedArray , ['Properties', 'Tech 1']);
But I'm stuck on how to combine the properties for a combined search.
Use _.filter() to iterate the products. For each product combine the list of properties using _.flatMap(), and use _.intersection() and _.size() to find the amount of filters that exist in the categories. Compare that to the original number of filters, and return comparison's response.
var products = {"Products":[{"Title":"A","Categories":[{"Name":"Type","Properties":["Type 1","Type 2","Type 3"]},{"Name":"Market","Properties":["Market 1","Market 2","Market 3","Market 4"]},{"Name":"Technology","Properties":["Tech 1","Tech 2"]}]},{"Title":"B","Categories":[{"Name":"Type","Properties":["Type 1","Type 3"]},{"Name":"Market","Properties":"Market 1"},{"Name":"Technology","Properties":["Tech 1","Tech 3"]}]},{"Title":"C","Categories":[{"Name":"Type","Properties":["Type 1","Type 2","Type 3"]},{"Name":"Market","Properties":["Market 2","Market 3"]},{"Name":"Technology","Properties":["Tech 2","Tech 3"]}]}]};
var filters = ['Type 3', 'Tech 1'];
var result = _.filter(products.Products, function(product) {
return filters.length === _(product.Categories)
.flatMap('Properties')
.intersection(filters)
.size();
});
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>
If I understand you question correctly, this code may help:
_.filter(
products.Products,
product => _.difference(
filters,
_.chain(product.Categories).map(category => category.Properties).flatten().value()
).length === 0
);
It calculates a union of all properties for each product:
_.chain(product.Categories).map(category => category.Properties).flatten().value()
And then checks that it contains all filters array elements, using _.difference method.
Hope it helps.
another fancy way through _.conforms
var res = _.filter(
products.Products,
_.conforms({'Categories': function(categories) {
return _.chain(categories)
.flatMap('Properties') // flat arrays
.uniq() // remove dublicates
.keyBy() // transform to objects with Properties keys
.at(filters) // get objects values by filters
.compact() // remove undefineds
.size() // get size
.eq(filters.length) // compare to filters size
.value();
}
}))
This will work for a list of items where the givenProperty you want to filter on is either a string like 'doorColour' or an array of strings representing the path to the givenProperty like ['town', 'street', 'doorColour'] for a value nested on an item as town.street.doorColour.
It also can filter on more than one value so you could you just need pass in an array of substrings representing the string values you want to keep and it will retain items that have a string value which contains any substring in the substrings array.
The final parameter 'includes' ensures you retain these values if you set it to false it will exclude these values and retain the ones that do not have any of the values you specified in the substrings array
import { flatMap, path } from 'lodash/fp';
const filteredListForItemsIncludingSubstringsOnAGivenProperty = (items, givenProperty, substrings, including=true) => flatMap((item) =>
substrings.find((substring) => path(givenProperty)(item) && path(givenProperty)(item).includes(substring))
? including
? [item]
: []
: including
? []
: [item])(items);
E.g. fLFIISOAGP(contacts, ['person','name'], ['Joh','Pau',Pet']);
with items of structure {contact, business:null, personal:{name:'John'}}.
For the original question - this will also work - I would use this repeatedly on a list of items to filter with different keys to filter on more than one property.
const firstFilteredResult = filteredListForItemsIncludingSubstringsOnAGivenProperty(
products.Products,
["Categories", "0", "Properties"],
["Type 3"]);
const secondFilteredResult = filteredListForItemsIncludingSubstringsOnAGivenProperty(
firstFilteredResult,
["Categories", "2", "Properties"],
["Tech 1"]);
expect(secondFilteredResult[0]['Title']).to.equal( "A");
expect(secondFilteredResult[1]['Title']).to.equal( "B");
expect(secondFilteredResult.length).to.equal(2);

Make jsonSource for vtree

I wanted to use JavaScript vtree plugin. For this i made a jsonSource. But i only get first child.
I have multiple class and under a campus have multiple class and so is section. My json is like this
[
{
"campus_id": "4",
"campus_name": "Test Campus Name",
"class_id": "11",
"class_name": "STD - VIII",
"section_id": "11",
"section_name": "A"
},
{
"campus_id": "4",
"campus_name": "Test Campus Name",
"class_id": "15",
"class_name": "A' Level",
"section_id": "15",
"section_name": "A"
},
{
"campus_id": "5",
"campus_name": "Dhanmondi",
"class_id": "6",
"class_name": "STD - III",
"section_id": "6",
"section_name": "A"
}
]
Here is how i made the jsonSource for vtree.
<script type="text/javascript">
var json_data = <?php echo $json_data; ?>;
$.each(json_data, function(key,value){
alert(value.section_id);
var jsonSource = {
tree: {
id: "root",
nodes: [{
id: "master",
title: "Select Campus",
hasChildren: true,
nodes: [{
id: value.campus_id,
title: value.campus_name,
hasChildren: true,
nodes: [{
id: value.class_id,
title: value.class_name,
hasChildren: true,
nodes: [{
id: "section_id",
title: value.section_name,
hasChildren: false,
}]
}]
}]
}]
}
};
//initialisation
var tree = Vtree.create({
// mandatory
container: jQuery("#treeContainer"),
// mandatory
dataSource: jsonSource,
// mandatory
plugins:["checkbox"],
// otional. all parents will be open automatically.
initiallyOpen: ["parent_1", "parent_2", "parent_3"],
// optional. default value is true.
displayCheckbox: true,
// list of initially checked nodes
initiallyChecked: ["child_1"],
//optional. default value is 'checkParents'. Checking a node check all his parents automatically. In this case checking child_1 at initialisation checked all his parents.
checkBehaviour: "checkParents",
// optional. default value is 'checked'
checkedClass: "checked",
//optional. default value is 'uncheckChildren'. Unchecking a node uncheck all his children automatically
uncheckBehaviour: "uncheckChildren",
// a list of disabled nodes
disabledCheckboxes: ["parent_3"],
//optional. by default value is 'disableChildren'. in this case disabling parent_3 will disable automatically his children.
disableBehaviour: "disableChildren",
// optional. default value is "disabled". The class applied to disabled nodes.
disabledClass: "disabled",
});
});
</script>
How can i get all the campus and existing classes and sections as child?

MongoDB: Doing update for nested elements in document and removing specific nested object

I need to remove some nested data in a document, which looks like this:
{
"_id" : "123",
"groupdata" : [
{
"title" : "group 1",
"data" : [
{
"description" : "row 1",
"many" : 2,
"other" : 0,
"fields" : 0
},
{
"description" : "row 2",
"many" : 2,
"other" : 0,
"fields" : 0
}
]
},
{
"title" : "group 2",
"data" : [
{
"description" : "row 1",
"many" : 2,
"other" : 0,
"fields" : 0
},
{
"description" : "row 2",
"many" : 2,
"other" : 0,
"fields" : 0
}
]
}
]
}
In the frontend the data is displayed in a table. Each dataset of groupdata is a own table and each data is one row of the table. Each input name identical to the field-name in the mongodb document.
For saving changes I'm using for a change event on input:
var inputs = $(that).closest('tr')[0].getElementsByTagName('input'),
indexGroup = $(that).closest('.panel').index(),
indexDosis = $(that).closest('tr').index(),
id = 123;
for (var i = 0; i < inputs.length; i++) {
field = $(inputs[i]).context.name;
data[field] = $(inputs[i]).context.value;
}
setObject["groupdata."+ indexTable +".data"+ indexRow] = data;
Collection.update({ _id: id }, { $set: setObject }
I have one question and one problem:
Is the order of objects fixed, so that I can do that with index() or is the way how I do the update problematically? If this is not the best way, what do I have to change?
I need to remove one row, but the data-object itself don't have an unique id. So how can I remove a specific row (=data object) withour removing the other objects of the same array?
So in this example I want to remove the first row of the second table.

Categories

Resources