I have an array of nested regions that look like this:
Egypt
Zone 1
Tagamo3
Giza
Helwan
Fayoum
Zone 2
Gesr ElSuis
test
Delta
Mohandeseen
Down Town
The array itself:
[
{
"key": 10,
"title": "Egypt",
"parent_key": null,
"children": [
{
"key": 1,
"title": "Zone 1",
"parent_key": 10,
"children": [
{
"key": 3,
"title": "Tagamo3",
"parent_key": 1,
"children": []
},
{
"key": 7,
"title": "Giza",
"parent_key": 1,
"children": []
},
{
"key": 8,
"title": "Helwan",
"parent_key": 1,
"children": []
},
{
"key": 11,
"title": "Fayoum",
"parent_key": 1,
"children": []
}
]
},
{
"key": 2,
"title": "Zone 2",
"parent_key": 10,
"children": [
{
"key": 4,
"title": "Gesr ElSuis",
"parent_key": 2,
"children": [
{
"key": 12,
"title": "test",
"parent_key": 4,
"children": []
}
]
},
{
"key": 5,
"title": "Delta",
"parent_key": 2,
"children": []
},
{
"key": 6,
"title": "Mohandeseen",
"parent_key": 2,
"children": []
},
{
"key": 9,
"title": "Down Town",
"parent_key": 2,
"children": []
}
]
}
]
}
]
I want to return to the highest region in a given input
Examples:
input [7, 1, 10] should return [10] since 10 is Egypt parent of 1 and 7
input [1, 2] should return both [1, 2] since they are on the same level both Zone 1 and zone 2 located under Egypt
input [2, 3, 1] should return [2, 1] since they are on the same level and 3 removed because it's a child of 1
input [1, 4] should return [1, 4] since they are on different levels and no one parent to the other
First it helps to turn your tree structure into a map of descendant ids, recursively:
const descendantsMap = new Map<number, Set<number>>();
function walk(tree: Tree) {
const s: Set<number> = new Set();
descendantsMap.set(tree.key, s);
for (const t of tree.children) {
walk(t);
s.add(t.key);
descendantsMap.get(t.key)?.forEach(v => s.add(v));
}
}
arr.forEach(walk);
We are building up a Map from each key in your tree structure to a Set of the keys of its descendants. The walk() function is recursive, and we merge the descendants for the children of each node into the descendants for the current node.
Let's make sure it looks right:
console.log(descendantsMap);
/* Map (12) {
10 => Set (11) {1, 3, 7, 8, 11, 2, 4, 12, 5, 6, 9},
1 => Set (4) {3, 7, 8, 11},
3 => Set (0) {},
7 => Set (0) {},
8 => Set (0) {},
11 => Set (0) {},
2 => Set (5) {4, 12, 5, 6, 9},
4 => Set (1) {12},
12 => Set (0) {},
5 => Set (0) {},
6 => Set (0) {},
9 => Set (0) {}
} */
Yes. You can see how now we have a quick mapping from each key to the set of keys in its descendant subtree.
Now to get the "highest" entries in an array (I would call these the "shallowest" since they are closest to the root), we find all the descendants of all the elements in the array and then filter these out of the array:
const shallowest = (x: number[]): number[] => {
const descendants = new Set<number>();
for (const v of x) {
descendantsMap.get(v)?.forEach(i => descendants.add(i));
}
console.log(descendants); // just to understand what's happening
return x.filter(v => !descendants.has(v));
}
Let's test it:
console.log(shallowest([7, 1, 10]));
// descendants are {3, 7, 8, 11, 1, 2, 4, 12, 5, 6, 9}
// output [10]
console.log(shallowest([1, 2]));
// descendants are {3, 7, 8, 11, 4, 12, 5, 6, 9};
// output [1, 2]
console.log(shallowest([2, 3, 1]));
// descendants are {4, 12, 5, 6, 9, 3, 7, 8, 11};
// output [2, 1]
console.log(shallowest([1, 4]));
// descendants are {3, 7, 8, 11, 12};
// output [1, 4]
Looks good. You can see that shallowest([7, 1, 10]) first finds all the descendants of 7, 1, and 10, which is {3, 7, 8, 11, 1, 2, 4, 12, 5, 6, 9}, or everything except 10. So when we filter those out of [7, 1, 10] we are left with just 10. Similarly, shallowest([1, 2]) and shallowest([1, 4]) produce sets of descendants that don't overlap at all with the input, so the output is identical to the input. And with shallowest([2, 3, 1]), the list of descendants contains 3 but not 2 or 1, so the output is [2, 1].
Playground link to code
This is my 2nd attempt, thanks to jcalz for pointing out the error and his solution is neater than mine.
The function buildArray builds an array of objects in to the variable keyArray, the key is the element in the array to be searched and another array that's the path to that element (so key 7 will have a path of [10, 1, 7]).
We then filter keyArray to remove any elements that have a parent in the original search array.
Anyway, reading jcalz's solution, I've learnt about maps so my time's not been entirely wasted. Hope this helps in some way though.
console.log(search2([7, 1, 10], obj)); //returns [10]
console.log(search2([1,2], obj)); //returns [1,2]
console.log(search2([2,3,1], obj)); //returns [1,2]
console.log(search2([1,4], obj)); //returns [1,4]
function search2(search, obj) {
keyArray=[];
buildArray(obj);
return keyArray.filter((element)=> !element.path.some(e => search.includes(e))).map((e)=> e.key);
function buildArray(obj, path=[]) {
obj.forEach((element) =>{
if(search.includes(element.key)) {
keyArray.push({"key":element.key,"path":path});
}
buildArray(element.children,[...path,element.key]);
});
}
}
Related
i have a json and need to extract data to array.
const data = [{
"week": 1,
"lost": 10,
"recovery_timespan": [{
"week": 2,
"count": 1
}, {
"week": 3,
"count": 0
}],
"netLost": 10,
"netReturned": 20
}, {
"week": 2,
"lost": 7,
"recovery_timespan": [{
"week": 3,
"count": 1
}],
"netLost": 30,
"netReturned": 200
}, {
"week": 3,
"lost": 8,
"recovery_timespan":"",
"netLost": 50,
"netReturned": 40
}];
Expected output: lost,count in recovery_timespan,netLost , netReturned.
[ [ 10, 1, 0, 10, 20 ], [ 7, 1, 30, 200 ], [ 8, 50, 40 ] ]
As you can see expected output, last recovery_timespan does not contain any data and it just shows as "".so i need to ignore it.
My approach:
const result = data.map(({lost, recovery_timespan,netLost,netReturned}) => [
lost,
...recovery_timespan.map(({count}) => count),
netLost,netReturned
]);
My code breaks when "recovery_timespan" is "". How can i add a filter along with map to filter that part and make my code work?
It's just a matter of checking if it's string or not, but you can short circuit
const result = data.map(({lost, recovery_timespan,netLost,netReturned}) => [
lost,
...(recovery_timespan || []).map(({count}) => count),
netLost,netReturned
]);
I have an array as below
var a = [ [ 5, 5, 1, -4 ], [ 3, 7, 3, -1 ], [ 7, 3, 4, 1 ], [ 5, 5, 5, 0 ] ]
Every nested array index:2 element indicates its distance from 0(Zero) and every index:3 element indicate how near it is to its next poll point.
I am trying to sort this array so I can get nested array which is near to index:0 with reference to index:2 element and its next poll point is very near.
For example, my answer here is [ 3, 7, 3, -1 ] because
though [ 5, 5, 1, -4 ] , index:2 is very near to 0 its next point is located at after/before 4 positions. But for [ 3, 7, 3, -1 ] next poll point is at one position.
I tried with sorting like below
js
inDiff = inDiff.sort( function(a,b ){
if ( a[2] < b[2]) {
if ( Math.abs(a[3]) < Math.abs(b(3)) ){
return Math.abs(b[3]) - Math.abs(a[3]);
}
}
});
Update 1
As asked in the comment I am adding explanation to each element of the nested array. For example in nested array [ 5, 5, 1, -4 ]
Index:0: Value 5 Represents 1st Number that I am looking for
Index:1 Value 5 Represents 2nd Number ( next poll point number)
By Adding these two numbers I will achieve my requirement of finding two numbers which can sum up for 10.
Index 2 : Value 1 : Indicates index of 1st number nothing but 5 in the source array
Index 3 : Value -4 : Indicates difference between indexes of Index:0 and Index:1 number of nested array from source array.
But nothing happens with my array.
Any help, much appreciated.
Assuming the input always follows requirements this demo uses .reduce()
const AA = [
[5, 5, 1, -4],
[3, 7, 3, -1],
[7, 3, 4, 1],
[5, 5, 5, 0]
];
let result = AA.reduce((min, now, idx, AoA) => {
let distTo0 = array => Math.abs(Math.floor(array[2]) + Math.floor(array[3]));
min = distTo0(now) < distTo0(min) ? now : min;
return min;
});
console.log(result);
The following demo includes all of the rules as I understood them:
const crazyLogic = arrayOfArrays => {
let AAClone = JSON.parse(JSON.stringify(arrayOfArrays));
const shared = AAClone[0][0] + AAClone[0][1];
const rules = [`Must be an array of number arrays`, `The sum of index 0 and 1 of each sub-array must be identical`, `Mismatched sub-array lengths`];
let message = !AAClone.every(sub => Array.isArray(sub)) ? rules[0] : !AAClone.every(sub => sub.every(num => num + 0 === num)) ? rules[0] : !AAClone.every(sub => sub[0] + sub[1] === shared) ? rules[1] : !AAClone.every(sub => sub.length === 4) ? rules[2] : null;
if (message !== null) {
return message;
}
return AAClone.reduce((min, now, idx, AoA) => {
let distTo0 = array => Math.abs(Math.floor(array[2]) + Math.floor(array[3]));
min = distTo0(now) < distTo0(min) ? now : min;
return min;
});
};
/* Input rules:
1. Must be an array of arrays (only numbers)
2. Each sum of subArray[0] + subArray[1] must be identical
3. Each subArray.length = 4
*/
// AAa returns [ 3, 7, 3, -1 ]
const AAa = [
[5, 5, 1, -4],
[3, 7, 3, -1],
[7, 3, 4, 1],
[5, 5, 5, 0]
];
// AA1 breaks rule 1
const AA1 = [
[5, 5, 1, -4],
[3, 7, 3, -1],
[7, 3, ' X', 1],
[5, 5, 5, 0]
];
// AAO breaks rule 1
const AAO = [
[5, 5, 1, -4],
[3, 7, 3, -1],
[7, 3, 4, 1],
[5, 5, 5, 0], {}
];
// AA2 breaks rule 2
const AA2 = [
[5, 5, 1, -4],
[3, 17, 3, -1],
[7, 3, 4, 1],
[5, 5, 5, 0]
];
// AA3 breaks rule 3
const AA3 = [
[5, 5, 1, -4],
[3, 7, 3, -1],
[7, 3, 4, 1],
[5, 5, 5]
];
console.log(crazyLogic(AAa));
console.log(crazyLogic(AA1));
console.log(crazyLogic(AAO));
console.log(crazyLogic(AA2));
console.log(crazyLogic(AA3));
Im looking to merge/combine objects in an array each with a series of nested arrays. I want to merge the objects based on a specific key (here label[1]). I can use Lodash and unionBy to filter out dublicates by label[1], but how do i keep the values from the filtered items?
The array can look like this:
var arr = [{
"label": ['item', 'private'],
"values": [1, 2, 3]
},
{
"label": ['item', 'private'],
"values": [1, 2, 3, 6]
},
{
"label": ['item', 'work'],
"values": [1, 2, 8, 9]
},
{
"label": ['item', 'private'],
"values": [1, 2, 4, 5]
},
{
"label": ['item', 'school'],
"values": [1, 2, 7]
}
];
And the desired output is:
var arr = [{
"label": ["item", "private"],
"values": [1, 2, 3, 4, 5, 6 ]
}, {
"label": ["item", "work"],
"values": [1, 2, 8, 9]
}, {
"label": ["item", "school"],
"values": [1, 2, 7]
}]
Here's a sample which is only half way there.
var arr = [
{ label: ['item','private'], values: [1,2,3] },
{ label: ['item','private'], values: [1,2,3,6] },
{ label: ['item','work'], values: [1,2,8,9] },
{ label: ['item','private'], values: [1,2,4,5] },
{ label: ['item','school'], values: [1,2,7] }
];
var result = _.unionBy(arr, "label[1]");
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
Any ideas?
Thanks
Lasse
I'd write:
const arr2 = _(arr)
.groupBy(obj => obj.label[1])
.values()
.map(objs => ({
label: objs[0].label,
values: _(objs).flatMap("values").uniq().value(),
}))
.value()
Not sure how to do this with lodash but I don't think unionBy is the method to do this anyway.
Here is how you can group by label using lodash and then reduce the groups into one value to merge the items of a group.
const arr = [{"label":["item","private"],"values":[1,2,3]},{"label":["item","private"],"values":[1,2,3,6]},{"label":["item","work"],"values":[1,2,8,9]},{"label":["item","private"],"values":[1,2,4,5]},{"label":["item","school"],"values":[1,2,7]}];
console.log(
Object.values(
_.groupBy(arr, (item) => item.label.join()),//use lodash group by
).map((
group, //now we have array of array of groups
) =>
group
.reduce((result, item) => ({
//reduce a group to one object
label: result.label, //set label
values: [
//set values with unique values of all items
...new Set(
(result.values || []).concat(item.values || []),
),
],
})),
),
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
You could also do something like this via lodash:
var arr = [{ "label": ['item', 'private'], "values": [1, 2, 3] }, { "label": ['item', 'private'], "values": [1, 2, 3, 6] }, { "label": ['item', 'work'], "values": [1, 2, 8, 9] }, { "label": ['item', 'private'], "values": [1, 2, 4, 5] }, { "label": ['item', 'school'], "values": [1, 2, 7] } ]
const merge = arr => _.reduce(arr, (r,c) => _.union(r, c.values), [])
const result = _(arr).groupBy('label')
.entries()
.reduce((r,[k,v]) => (r.push({ label: k.split(','), values: merge(v) }), r), [])
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
The idea is first to group by via _.groupBy and then get the entries (via _.entries) so you can form the desired output via _.reduce. _.union we use to merge the values arrays as part of the final reduce.
Here is the ES6 implementation:
var arr = [{ "label": ['item', 'private'], "values": [1, 2, 3] }, { "label": ['item', 'private'], "values": [1, 2, 3, 6] }, { "label": ['item', 'work'], "values": [1, 2, 8, 9] }, { "label": ['item', 'private'], "values": [1, 2, 4, 5] }, { "label": ['item', 'school'], "values": [1, 2, 7] } ]
const mrg = arr => Array.from(new Set(arr.reduce((r,c) => [...r, ...c.values], [])))
const grp = (arr, k) => arr.reduce((r,c) => (r[c[k]] = [...r[c[k]] || [], c], r), {})
const result = Object.entries(grp(arr, 'label'))
.reduce((r,[k,v]) => (r.push({ label: k.split(','), values: mrg(v) }), r), [])
console.log(result)
Let's say I have 3 items in a collection:
[
{
id: 'a',
items: [1, 2, 3]
}, {
id: 'b',
items: [4, 5, 6]
}, {
id: 'c',
items: [7, 8, 9]
}
]
On the JavaScript code side, all I have is an array [5, 2, 6, 4, 7, 8]. How would I compose my query to select only the 2nd object from the collection since my array has all the elements (4, 5 and 6) of its items array?
Using mongoDB Aggregation Set Operator you can filter your array. First find out intersection of given array with actual database array and after that used set equals method. check below query :
db.collectionName.aggregate({
"$project": {
"checkAllElem": {
"$setEquals": [{
"$setIntersection": ["$items", [5, 2, 6, 4, 7, 8]]
}, "$items"]
},
"items": 1
}
}, {
"$match": {
"checkAllElem": true
}
})
Array:
5, 5, 5, 9, 4, 2, 2, 2, 2, 2, 3, 3, 3, 3
Ideal Output:
2, 3, 5, 9, 4
PHP made this easy with array_count_values() and arsort(), but javascript is proving a little tougher. Any help?
Also, what about returning it containing the counts as well? For future needs
Count unique entries, create an array of uniques, then sort based upon counts
function count(arr) { // count occurances
var o = {}, i;
for (i = 0; i < arr.length; ++i) {
if (o[arr[i]]) ++o[arr[i]];
else o[arr[i]] = 1;
}
return o;
}
function weight(arr_in) { // unique sorted by num occurances
var o = count(arr_in),
arr = [], i;
for (i in o) arr.push(+i); // fast unique only
arr.sort(function (a, b) {
return o[a] < o[b];
});
return arr;
}
weight([1, 3, 3, 5, 5, 5, 2, 2, 2, 2]);
// one 1, two 3s, three 5s, four 2s
// [2, 5, 3, 1]
You example has both one 9 and one 4, so if you want the order defined, more work would be necessary. Otherwise;
weight([5, 5, 5, 9, 4, 2, 2, 2, 2, 2, 3, 3, 3, 3]);
// [2, 3, 5, 4, 9]
To produce an Array of Objects
function weight(arr_in) { // unique sorted by num occurances
var o = count(arr_in),
arr = [], i;
for (i in o) arr.push({value: +i, weight: o[i]}); // fast unique only
arr.sort(function (a, b) {
return a.weight < b.weight;
});
return arr;
}
var result = weight([5, 5, 5, 9, 4, 2, 2, 2, 2, 2, 3, 3, 3, 3]);
/* [
{"value": 2, "weight": 5},
{"value": 3, "weight": 4},
{"value": 5, "weight": 3},
{"value": 4, "weight": 1},
{"value": 9, "weight": 1}
] */
Now, to get the value at index i, you do result[i].value, and for it's weighting result[i].weight.