Related
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]);
});
}
}
im trying to execute multiple pormises in chunks using promise all => this is just a code example - throwing every time getting the number 72.
i have a simple catch in the function that calls executePromises but it does not catch as it should.
the problem is that even if the first promise chunk fails the second one is also executed.
so, all the promises execute and it the fail happens in 2 chunks i get an unhandled exception. if only 1 chunk fails then the catch is correct and it works fine.
in debug, even if a promise is rejected it continue to run the chunks - i think its because they run sort of in "parallel", but if there are 2 errors in the same chunk it does not throw twice so makes sense.
i tried also using a .catch for the promise all but it didnt help either , the second chunk does not get there.
also tried throwing an error inside the same catch also didnt help.
class testClass{
async test(num: number): Promise<{ failed_skus: { id: string }[] }> {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (num === 72) {
reject(new Error(`index : ${index}, num : ${num}`));
index++;
} else {
resolve({ failed_skus: [{ id: 'str' }] });
index++;
}
}, 1300);
});
}
async executePromises(): Promise<void> {
index = 0;
const productUpdateRequests = [
72,
900,
3,
4,
5,
6,
72,
800,
9,
1,
2,
3,
4,
5,
6,
7,
8,
9,
1,
2,
3,
4,
5,
6,
72,
100,
9,
1,
2,
3,
4,
5,
6,
7,
8,
9,
1,
2,
3,
4,
5,
6,
7,
8,
9,
1,
2,
3,
4,
5,
6,
7,
8,
9,
];
const productsUpdatesRequestsPromises = productUpdateRequests.map(this.test.bind(this));
const requestChunks = chunk(
productsUpdatesRequestsPromises,
20,
);
for (const requestChunk of requestChunks) {
const updateStockResponses = await Promise.all(requestChunk);
}
}
}
The binding and setup of the promises was confusing me. Here is a working version of your code.
You probably needed this.index in your bound test() function (not just 'index'). I did not make it a class variable as, due to NodeJS timing, I'm not sure you could have guaranteed the order would match the array. My version just uses the array index.
Also, I found that mapping all the test functions at once started running them all at once, essentially finishing the program after 1.3s. In my code I only started the test function for the chunk I was on, so each chunk takes 1.3s.
Oh, and I didn't see a chunk() function, so I made one.
I hope you find this helpful. Happy coding!
function chunk(array, chunkSize) {
let chunks = []
for (let i = 0; i < array.length; i += chunkSize) {
chunks.push(array.slice(i, i + chunkSize))
}
return chunks
}
class testClass {
async test(num, idx) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (num === 72) {
reject(new Error(`index : ${idx}, num : ${num}`))
} else {
resolve({ failed_skus: [{ id: 'str' }] })
}
}, 1300)
})
}
async executePromises() {
let processedChunks = 1
const productUpdateRequests = [
72, 900, 3, 4, 5, 6, 72, 800, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 72, 100, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3,
4, 5, 6, 7, 8, 9,
]
const requestChunks = chunk(productUpdateRequests, 20)
for (const requestChunk of requestChunks) {
const updateStockResponses = await Promise.all(requestChunk.map((num, idx) => this.test(num, idx)))
.then(() => {
console.log('Finished processing chunk ' + processedChunks)
})
.catch((error) => {
//console.log(error)
console.log('Error processing chunk ' + processedChunks)
})
processedChunks++
}
}
}
let c = new testClass()
c.executePromises()
I'd like to get output from input.
It should be pushed element like chasing tails and it couldn't be duplicated.
The rule is very simple.
Firstly, if there is the same element with input[0][1] in input[i][0],
you can put the first value of the element in a result.
The element is [1,5] so result becomes [[0,1]].
Now you can just repeat. The first element of [5,29] is the same element with [1,5][1] and result becomes [[0,1,5]]
I've been really frustrating for few weeks to solve this issue. Please help. Any comments would be appreciated.
input =
[
[ 0, 1 ], [ 0, 2 ], [ 0, 3 ],
[ 0, 4 ], [ 1, 5 ], [ 2, 6 ],
[ 3, 7 ], [ 4, 10 ], [ 4, 11 ],
[ 4, 12 ], [ 4, 13 ], [ 5, 29 ],
[ 6, 29 ], [ 7, 8 ], [ 8, 29 ],
[ 9, 29 ], [ 12, 18 ], [ 13, 19 ],
[ 17, 29 ], [ 18, 29 ], [ 19, 29 ],
[ 21, 29 ], [ 24, 29 ], [ 26, 29 ],
[ 28, 29 ]
]
output = [
[0,1,5,29],[0,2,6,29],[0,3,7,8,29],[0,4,10],[0,4,11],
[0,4,12,18,29],[0,4,13,19,29]
]
You can write solver as a generator with an input of t and a starting query of q. Other answers suggest reshaping your input or other functional techniques that iterate over the input multiple times. This simple imperative-style technique uses only one pass (per recursive call). The use of a generator allows you to find all solutions, but can be paused/stopped at any time, for any other reason -
function* solver (t, q) {
let atLeastOnce = false
for (const [parent, child] of t) {
if (parent == q) {
atLeastOnce = true
for (const sln of solver(t, child))
yield [parent, ...sln]
}
}
if (!atLeastOnce) {
yield [q]
}
}
const input =
[[0,1],[0,2],[0,3],[0,4],[1,5],[2,6],[3,7],[4,10],[4,11],[4,12],[4,13],[5,29],[6,29],[7,8],[8,29],[9,29],[12,18],[13,19],[17,29],[18,29],[19,29],[21,29],[24,29],[26,29],[28,29]]
for (const sln of solver(input, 0))
console.log(JSON.stringify(sln))
[0,1,5,29]
[0,2,6,29]
[0,3,7,8,29]
[0,4,10]
[0,4,11]
[0,4,12,18,29]
[0,4,13,19,29]
Generators are iterable and so you can collect all results in an array using Array.from -
const all = Array.from(solver(input, 0))
console.log(all)
[
[0,1,5,29],
[0,2,6,29],
[0,3,7,8,29],
[0,4,10],
[0,4,11],
[0,4,12,18,29],
[0,4,13,19,29],
]
Combine generators with an optimized input type for even better results.
Based on how I understood your output, what you want is kind of like a domino-chain. You seed the output with arrays start contain 0 in the first element, e.g. [0,1] and [0,2]..., and then you simply find the next array to join to the chain based on the last item in the array vs the first item in the next entry.
This process is recursive in nature, since you start with the seeds and have no idea how far/deep you will need to join. To break this down, we can do this:
Separate your input into seeds and non-seeds (nodes).
Start with seeds
Iterate through the seeds and invoke a function, that will recursively add nodes to your seed. The trick is that you want to duplicate the seed array, when multiple paths can be found
The function should invoke itself recursively, and here's a quick logic:
/**
* #method
* #params arr The seed array (which will grow in length)
* #params nodes The collection of non-seeds
*/
function chain(arr, nodes) {
// We first find whatever nodes left that we can domino-chain to the current seed
const nextNodes = nodes.filter(node => node[0] === arr[arr.length - 1]);
// If nothing is to be found, return the array
if (!nextNodes.length) return [arr];
// Otherwise we go through all the next nodes and create a copy of the current seed
// And then append the next node to it
return nextNodes.map(nextNode => {
return chain([...arr, nextNode[1]], nodes);
}).flat();
}
See proof-of-concept below:
const input = [
[0, 1],
[0, 2],
[0, 3],
[0, 4],
[1, 5],
[2, 6],
[3, 7],
[4, 10],
[4, 11],
[4, 12],
[4, 13],
[5, 29],
[6, 29],
[7, 8],
[8, 29],
[9, 29],
[12, 18],
[13, 19],
[17, 29],
[18, 29],
[19, 29],
[21, 29],
[24, 29],
[26, 29],
[28, 29]
];
const seeds = input.filter(entry => entry[0] === 0);
const nodes = input.filter(entry => entry[0] !== 0);
function chain(arr, nodes) {
const nextNodes = nodes.filter(node => node[0] === arr[arr.length - 1]);
if (!nextNodes.length) return [arr];
return nextNodes.map(nextNode => {
return chain([...arr, nextNode[1]], nodes);
}).flat();
}
const output = seeds.map(seed => {
return chain(seed, nodes);
}).flat();
console.log(output);
I would split the problem in 2:
Create a tree-like structure from your list. Each node in your tree contains a reference to all possible next nodes.
Write a recursive function that walks the tree to find all possible paths
Here's an implementation of that approach:
const input =
[
[ 0, 1 ], [ 0, 2 ], [ 0, 3 ],
[ 0, 4 ], [ 1, 5 ], [ 2, 6 ],
[ 3, 7 ], [ 4, 10 ], [ 4, 11 ],
[ 4, 12 ], [ 4, 13 ], [ 5, 29 ],
[ 6, 29 ], [ 7, 8 ], [ 8, 29 ],
[ 9, 29 ], [ 12, 18 ], [ 13, 19 ],
[ 17, 29 ], [ 18, 29 ], [ 19, 29 ],
[ 21, 29 ], [ 24, 29 ], [ 26, 29 ],
[ 28, 29 ]
];
const nodes = {};
// Store all paths between nodes
for (const [ start, end ] of input) {
nodes[end] = nodes[end] || { id: end, children: [] };
nodes[start] = nodes[start] || { id: start, children: [] };
nodes[start].children.push(nodes[end]);
}
// Find all paths from 0
const getPaths = ({ children, id }) => children.length === 0
? [[ id ]]
: children.flatMap(
n => getPaths(n).map(p => [ id, ...p ])
)
console.log(getPaths(nodes[0]));
You can do that with a single loop too, and follow your rules:
if edge starts (start) with 0, just push it into the result
otherwise try finding an already existing path in result which contains start
if the existing path ends with the start, just append edge's ending location
if the existing path contains the start somewhere inside, put a copy of path up to that position into result, and append edge's ending location.
let input = [[0, 1], [0, 2], [0, 3], [0, 4], [1, 5], [2, 6], [3, 7], [4, 10], [4, 11],
[4, 12], [4, 13], [5, 29], [6, 29], [7, 8], [8, 29], [9, 29], [12, 18],
[13, 19], [17, 29], [18, 29], [19, 29], [21, 29], [24, 29], [26, 29],
[28, 29]];
let result = [];
for (let edge of input) {
let start = edge[0];
if (start === 0) { // <--- 1. (0 => push for sure)
result.push(edge);
} else {
for (let path of result) {
let position = path.indexOf(start);
if (position > 0) { // <--- 2. (can't be 0, but >= would work too)
if (position !== path.length - 1) { // <--- 4. (need a copy)
path = path.slice(0, position + 1);
result.push(path);
}
path.push(edge[1]); // <--- 3. 4. (whatever path is)
break;
}
}
}
}
console.log(JSON.stringify(result));
(JSON.stringify() is just used for formatting)
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
I have array of objects where I want to filter and combine results based on specific id. This is example:
[
{
id: 1,
items: [
{
id: 10,
values: [11],
},
{
id: 20,
values: [13, 14, 15],
},
],
},
{
id: 2,
items: [
{
id: 10,
values: [12],
},
{
id: 20,
values: [13, 15],
},
],
},
];
And this is expected result:
[
{
id: 10,
values: [11, 12],
},
{
id: 20,
values: [13, 14, 15],
},
];
I also need to filter duplicates. Thanks
Note: What if I want this result?
[
{
// here I want value for id 10 (it will be always one number)
value: 11,
// here I want values for id 20 (array of numbers) => remove possible duplicates
values: [13, 14, 15],
},
{
// here I want value for id 10 (it will be always one number)
value: 12,
// here I want values for id 20 (array of numbers) => remove possible duplicates
values: [13, 15],
},
];
I tried the same approach with Map, but without success. Basically I want to combine values based on ids.
You could do with Array.flatMap to filter all items in single array.
Then recreate the array with Array.reduce and push the value based on id into new value object
And use Array.filter ignore the duplicate values on array
Object.values return only the value of object in array format
Older
const arr = [ { id: 1, items: [ { id: 10, values: [11], }, { id: 20, values: [13, 14, 15], }, ], }, { id: 2, items: [ { id: 10, values: [12], }, { id: 20, values: [13, 15], }, ], }, ];
const res = Object.values(arr.flatMap(({items})=> items)
.reduce((acc,{id,values})=>{
acc[id] = acc[id] ?? {id,values:[]};
//check the object exist or not
let newArr = acc[id]['values'].concat(values);
let valArr = newArr.filter((v,i)=>newArr.indexOf(v) === i)
//remove the duplicates
acc[id]['values'] = valArr
return acc
},{}))
console.log(res)
Updated
const arr = [ { id: 1, items: [ { id: 10, values: [11], }, { id: 20, values: [13, 14, 15], }, ], }, { id: 2, items: [ { id: 10, values: [12], }, { id: 20, values: [13, 15], }, ], }, ];
function filterMethod(arr,value,values){
return arr.map(({items})=> ({
value:detector(items,value)[0],
values:detector(items,values)
}))
}
function detector(items,idVal){
let ind = items.findIndex(({id})=> id === idVal);
return ind > -1 ? items[ind]['values'] : ['']
}
console.log(filterMethod(arr,10,20))
From a given data structure (json file) I basically need to render a table. Empty rows and/or columns should not render. I'm fairly new to JavaScript and tried different approaches (converting to array and using .map(), reduce(), .filter(), lodash etc.) without success. I don't even know what the best way would be to tackle the problem. (Or what possible search terms would be.)
Neither "row keys" (In example: mo, tu, we, th, fr) nor "column keys" (john, hane, doe) are known and can vary.
Complete example: https://jsbin.com/rafeyasena/edit?js,output
"groupA": {
"mo": { "john": 8, "jane": 5, "doe": null },
"tu": { "john": 8, "jane": 5, "doe": null },
"we": { "john": 5, "jane": 9, "doe": null },
"th": { "john": 6, "jane": 3, "doe": null },
"fr": { "john": null, "jane": null, "doe": null }
}
Possible resulting data structure
const header = ["John", "Jane"];
const content = [
"mo": {[ 8, 5 ]},
"tu": {[ 8, 5 ]},
"we": {[ 5, 9 ]},
"th": {[ 6, 3 ]}
]
Expected result (Front-end, React):
| John | Jane |
---|------|--------
mo | 8 | 5 |
tu | 8 | 5 |
we | 5 | 9 |
th | 6 | 3 |
What I tried so far:
I was able to delete all values of null and the corresponding key, if it doesn't contain keys/values any more (Delete null values in nested javascript objects) - leading me with the challenge to find out all the leftover keys to build the table header. (In the example below this would be only John and Jane - So basically a way to iterate over all keys and log each key that exists at least one time). So my current data looks like this (but I'm not sure if it is the best way):
"groupA": {
"mo": { "john": 8, "jane": 5, },
"tu": { "john": 8, "jane": 5, },
"we": { "john": 5, "jane": 9, },
"th": { "john": 6, "jane": 3, }
}
I would just represent the data as a 2D array (that makes rendering easier):
const columnNames = [""];
const rows = [columnNames];
for(const [rowName, values] of Object.entries(groupA)) {
const row = [rowName];
for(const [columnName, value] of Object.entries(values)) {
let position = columnNames.indexOf(columnName);
if(value === null) continue;
if(position === -1)
position = columnNames.push(columnName) - 1;
row[position] = value;
}
rows.push(row);
}
// just some debugging:
console.log( rows.map(row => row.map(it => (it || "").padStart(10)).join("|")).join("\n") );
I think creating that latter format (with the nulls removed) is a very useful first step. From there you could write something like this to get it into a variant of your target format:
const uniqKeys = (obj) => [... new Set(Object.values(obj).flatMap(Object.keys))]
const transform = (group, headers = uniqKeys(group)) => ({
headers,
content: Object.entries(group).reduce(
(a, [k, v]) => ({...a, [k]: headers.map(h => v[h])}),
{}
)
})
const groupA = {mo: {john: 8, jane: 5}, tu: {john: 8, jane: 5}, we: {john: 5, jane: 9}, th: {john: 6, jane: 3}}
console.log(transform(groupA))
Note that the target is a little different than your request, as your example content isn't legal JS ({[ 8, 5 ]} doesn't make sense) but I think it captures the spirit of it, returning something like:
{
headers: ['john', 'jane'],
content: {
mo: [8, 5],
tu: [8, 5],
we: [5, 9],
th: [6, 3]
}
}
Note that this function is a little more general than the requirements, as you could supply it a list of headers and only extract those from the data.
Take a look at object-scan. It makes this sort of this relatively easy once you wrap your head around how it works. Here is how you'd answer your questions
// const objectScan = require('object-scan');
const isNullObject = (obj) => (
obj instanceof Object
&& !Array.isArray(obj)
&& Object.values(obj).every((e) => e === null)
);
const prune = (data) => objectScan(['**'], {
rtn: 'count',
filterFn: ({ value, parent, property }) => {
if (isNullObject(value)) {
delete parent[property];
return true;
}
return false;
}
})(data);
const stats = { groupA: { mo: { john: 8, jane: 5, doe: null }, tu: { john: 8, jane: 5, doe: null }, we: { john: 5, jane: 9, doe: null }, th: { john: 6, jane: 3, doe: null }, fr: { john: null, jane: null, doe: null } } };
console.log(prune(stats)); // return number of replaces
// => 1
console.log(stats);
/* =>
{ groupA:
{ mo: { john: 8, jane: 5, doe: null },
tu: { john: 8, jane: 5, doe: null },
we: { john: 5, jane: 9, doe: null },
th: { john: 6, jane: 3, doe: null } } }
*/
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan