How to write recursion where items depend on each other? - javascript

I want to write a function, which returns a list of people a person relies on. However, the people relied upon, could in turn rely on the person themselves. For example:
const people = {
'james': {
reliesOn: [
'gemma',
'jessica'
]
},
jessica: {
reliesOn: [
'gemma',
'peter'
]
},
peter: {
reliesOn: [
'james',
'gemma',
'jessica',
'ivon',
'jamie'
]
},
jamie: {
reliesOn: [
'ivon'
]
}
}
I am attempting to get the following result, in the simplest possible code:
james relies on:
gemma, jessica, peter, ivon, jamie
jessica relies on:
gemma, peter, james, ivon, jamie
peter relies on:
james, gemma, jessica, ivon, jamie
jamie relies on:
ivon
Apologies if this has already been asked. I simplified this example from real world, so I hope it makes sense

If you really want to do this with reduce then:
const getReliedOn = (people, name) =>
[...(people[name]?.reliesOn || []).reduce(function recur(acc, name) {
return acc.has(name) ? acc
: (people[name]?.reliesOn || []).reduce(recur, acc.add(name));
}, new Set([name]))].slice(1);
// Sample input
const people = {'james': { reliesOn: ['gemma','jessica']}, jessica: {reliesOn: ['gemma','peter']}, peter: {reliesOn: ['james','gemma','jessica','ivon','jamie']}, jamie: { reliesOn: ['ivon']}};
// Produce output for each person:
for (let name in people) console.log(name, "=>", ...getReliedOn(people, name));
Alternative without reduce, without recursion
This is a breadth-first search (BFS). It relies on the fact that when you iterate over a set, and during that iteration you add values to that set, the loop will also visit those added values in later iterations. So the set acts like the queue that is typical for a BFS:
function getReliedOn(people, name) {
let names = new Set([name]); // a set with only one entry
for (let name of names) { // this set will extend while we loop
if (!people[name]) continue;
for (let other of people[name].reliesOn) names.add(other);
}
// get normal array from set, without original name
return Array.from(names).slice(1);
}
// Sample input
const people = {'james': { reliesOn: ['gemma','jessica']}, jessica: {reliesOn: ['gemma','peter']}, peter: {reliesOn: ['james','gemma','jessica','ivon','jamie']}, jamie: { reliesOn: ['ivon']}};
// Produce output for each person:
for (let name in people) console.log(name, "=>", ...getReliedOn(people, name));

I find it useful to pass around a "memo" object when recursing that keeps track of where we've been already to avoid circular loops:
function *getDependenciesRecursive(people, person, alreadyVisited = new Set()) {
if (alreadyVisited.has(person)) {
return;
}
alreadyVisited.add(person);
if (!(person in people)) {
return;
}
for (const dependency of people[person].reliesOn) {
yield dependency;
yield* getDependenciesRecursive(people, dependency, alreadyVisited);
}
}
Essentially, in an approach like this the Set tells us what to skip.
const reliances = Object
.entries(people)
.reduce((results, [person, { reliesOn }]) => Object.assign(results, {
[person]: [...getDependenciesRecursive(people, person)]
}), {})

Related

Alternative to eval for converting a string to an object

I have a function that is using eval to convert a string with an expression to an object based on the parameter.
let indexType = ["Mac", "User", "Line", "Mask", "Ip", "Location"]
const filterIndex = (item) => {
filteredIndexSearch = []
eval(`search${item}`).forEach((e) => filteredIndexSearch.push(searchData[e.key]))
}
filterIndex(indexType[searchTotal.indexOf(Math.max(...searchTotal))])
searchData is an array that returns values based on the user input.
searchTotal is an array with the length of each search{item} array.
The filterIndex function takes the highest value from the searchData array and corresponds it to the indexType array, then use eval to convert the string to an object to pass the value to the filteredIndexSearch array.
What would be a better alternative to eval?
EDIT
To add more information on what this does:
searchData = [
[
{
key: 1,
data: "0123456789101"
},
{
key: 1,
data: "John Smith"
}
],
[
{
key: 2,
data: "0123456789102"
},
{
key: 2,
data: "Jane Smith"
},
]
]
const search = (data, key, container) => {
if (!data) data = "";
if (data.toLowerCase().includes(string)) {
container = container[container.length] = {
key: key,
data: data
}
}
}
const returnSearch = () => {
for (let i = 0; i < searchData.length; i++) {
search(searchData[i][0].data, searchData[i][0].key, searchMac)
search(searchData[i][1].data, searchData[i][1].key, searchUser)
}
}
returnSearch()
The data is incomplete, but hopefully conveys what I'm trying to do.
search will take the user input, and store the information in the corresponding array. If I input "Jo", it will return the searchUser array with only the "John Smith" value and all the other values with the same key. Inputting "102" returns the searchMac with the "0123456789102" value and all other values with the same key.
At the end of the day. I just want to convert search${parameter} to an object without using eval.
Move your global arrays into an object.
Somewhere it appears that you're defining the arrays, something like:
searchMac = [...];
searchUser = [...];
...
Instead of defining them as individual arrays, I'd define them as properties in an object:
searchIndices.Mac = [...];
searchIndices.User = [...];
...
Then, instead of using eval, your can replace your eval().forEach with searchIndices[item].forEach.
If the order of your search isn't important, your can instead loop through the keys of searchIndices:
Object.keys(searchIndices).forEach(item => {
searchIndices[item].forEach(...);
});
This ensures that if you ever add or drop an entry in searchIndices, you won't miss it or accidentally error out on an undefined search index.
Any time you have a situation with variables named x0, x1 etc, that should be a red flag to tell you you should be using an array instead. Variable names should never be semantically meaningful - that is code should never rely on the name of a variable to determine how the code behaves. Convert search0 etc into an array of search terms. Then use:
const filterIndex = (item) => search[item].map(i => searchData[i.key]);
filteredIndexSearch = filterIndex(indexType[searchTotal.indexOf(Math.max(...searchTotal))]);
(simplifying your code). Note that in your code, filteredIndexSearch is modified inside the arrow function. Better to have it return the result as above.

Create an object or associative array with elements of an existing array and the result of a callout for each element

This is in the context of a node express route. I receive a get request with a query param that is a list of IDs. Now I need to make a call-out for each ID and store the result of the callout in an array or object. Each element of the first array (containing the IDs) need to be mapped to its corresponding result from the call-out. I don't have a way to modify the endpoint that I'm hitting from this route so I have to make single calls for each ID. I've done some research and so far I have a mixture of code and sudo code like this:
const ids = req.query.ids;
const idMembers = Promise.all(ids.map(async id => {
// here I'd like to create a single object or associative array
[ id: await callout(id); ]
}));
When all promises resolved I need the final result of idMembers to be like: (The response will be an object with nested arrays and objects I've just simplified it for this post but I need to grab that from the res.payload)
{
'211405': { name: 'name1', email: 'email1#test.co' },
'441120': { name: 'name2', email: 'email2#test.co' },
'105020': { name: 'name3', email: 'email4#test.co' }
}
Oh and of course I need to handle the callout and the promise failures and that's when my lack of experience with javascript becomes a real issue. I appreciate your help in advance!!
Some extra thought I'm having is that I'd have to map the results of the resolved promises to their id and then in a separate iteration I can then create my final array/object that maps the ids to the actual payloads that contain the object. This is still not answering any of my questions though. I'm just trying to provide as much information as I've gathered and thought of.
Promise.all returns an array of results (one item per each promise).
Having this temporary structure it is possible to build the needed object.
const arrayOfMembers = Promise.all(ids.map(async id => {
// ...
return { id, value: await callout(id) } // short syntax for { id: id, value: ... } (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer)
}));
// arrayOfMembers = [
// { id: 211405, value: { name: 'name1', email: 'email1#test.co' } },
// ...
// ]
In pure JS it can be done with for loop or .forEach() call to iterate:
const res = {};
arrayOfMembers.forEach(el => {
const { id, value } = el;
res[el] = value;
});
or by using a single reduce() call
const res = arrayOfMembers.reduce((accumulator, el) => {
const { id, value } = el;
return { ...accumulator, [id]: value };
}, {});
in both cases res will be:
// res = {
// '211405': { name: 'name1', email: 'email1#test.co' },
// ...
// }
P.S.
There is a handy library called lodash. It has tons of small methods for data manipulation.
For example, _.fromPairs() can build an object from [[key1, value1], [key2, value2]] pairs.
As you mentioned you have lodash, so I think the following should work:
const arrayOfKeyValuePairs = Promise.all(ids.map(async id => {
// ...
return [ id, await callout(id) ] // array here so it matches what fromPairs needs
}));
const res = _.fromPairs(arrayOfKeyValuePairs);

Is there a way to traverse a possibly-self-containing object in JavaScript?

I want to descend an object in Javascript looking for a specific string. Unfortunately, this object is built in such a way that it'd be impossible to simply use the source and Ctrl-F for that string, and it's also built in such a way that recursive functions trying to descend it risk getting trapped inside of it forever.
Basically, this object contains itself. Not just once, but in very many areas. I cannot simply say "exclude these keys", as the object is obfuscated and therefore we'd be here all day listing keys, and once we were done we wouldn't have looked at all the data.
As well, I need to be able to descend __proto__ and prototype, as useful strings are hidden in there too. (But only for functions and objects.)
While I'd prefer something along the lines of findStuff(object, /string/ig), that may be hard, so any function that simply has areas clearly marked that the control flow falls to once it's found specific objects (function, string, etc.)
Thank you, and sorry for such a pain in the butt question.
Edit: In case it helps, I'm trying to traverse a compiled Construct2 runtime object. I'm not going to post the full thing here as it's not going to fit in any pastebin no matter how forgiving, and also I don't want to accidentally post resources I don't have the permission to provide. (Don't worry though, I'm not trying to pirate it myself, I'm simply trying to figure out some user-facing functionality)
You could use a WeakSet to keep track of the objects that were already traversed:
function traverseOnce(obj, cb) {
const visited = new WeakSet();
(function traverse(obj) {
for(const [key, value] of Object.entries(obj)) {
if(typeof value === "object" && value !== null) {
if(visited.has(value)) continue;
visited.add(value);
cb(value);
traverse(value);
}
}
})(obj);
}
Through the WeakSet you got O(1) lookup time, and are also sure that this will never leak.
Usable as:
const nested = { other: { a: 1 } };
nested.self = nested;
traverseOnce(nested, console.log);
// nested: { other, self }
// other: { a: 1 }
You could also use a Symbol to flag traversed objects, for that replace new WeakSet() with Symbol(), visited.has(value) with value[visited] and visuted.add(value) with value[visited] = true;
Any time you're traversing a potentially cyclical object, keeping a memo of already traversed objects and breaking if you've seen the current object before is a standard technique. You can use Set to do so.
Keep a list of objects you have recursed into, and then check each new object against that list.
const data = {
foo: {
bar: 1
},
one: 1,
jaz: {
hello: {
x: 1
}
}
};
data.bar = data.foo;
data.foo.foo = data.foo;
data.jaz.hello.foo = data;
function search_for_1() {
const seen = [];
search(data);
function search(object) {
Object.values(object).forEach(value => {
if (typeof value === "object") {
if (seen.includes(value)) {
console.log("Seen this already");
} else {
seen.push(value);
search(value);
}
} else {
if (value === 1) {
console.log("Found 1");
}
}
});
}
}
search_for_1();
Don't reinvent the wheel There are libraries for this kind of stuff.
We use object-scan for all our data processing. It's very powerful once you wrap your head around it. Here is how it would work for your questions
// const objectScan = require('object-scan');
const traverse = (data) => objectScan(['**'], {
filterFn: ({ key, value, parent }) => {
// do something here
},
breakFn: ({ isCircular }) => isCircular === true
})(data);
const circular = { name: 'Max', age: 5, sex: undefined, details: { color: 'black', breed: undefined } };
circular.sex = circular;
circular.details.breed = circular;
console.log(traverse(circular));
/* =>
[ [ 'details', 'breed' ],
[ 'details', 'color' ],
[ 'details' ],
[ 'sex' ],
[ 'age' ],
[ 'name' ] ]
*/
.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

How do I iterate through an array that is nested inside an object that is in turn nested in an array?

What I want to accomplish:
To retrieve all licenseTypes (digger and shovler) so that I can insert them in a header div.
To retrieve the respective licenseTypes' subLicenses to be used in a subheading (or maybe body) depending on the view.
My array looks like this:
const licenseType = [
{
diggerLisense: [
"Shallow Digger",
"Deep Digger"
]
},
shoverlerLicense: [
"Poop shovler",
"Grass shovler"
]
}
]
So basically, I have this structure:
licenseType > subLicenseType > subSubLicenseType
I specifically decided on this nested order for three reasons:
To map over the licenseTypes (digger and shovler) so that it can be used in a card Heading
To map over the respective subLicenseTypes (Shallow Digger, Deep Digger, Poop shover, Grass shovler) to be used in the same cards subheading. For some reason I think it's easier to handle state this way.
To keep all license types in a neat object structure that can be exported/imported throughout my project.
Any help or accusations is greatly appreciated here. BTW, I have searched other questions and nothing exactly like the structure I have described here.
Edit: I want to thank the overwhelming and swift response that you've all offered. I'm amazed at how quickly you people help. This is my first question on here and I can't believe the speed of the response.
I am currently working the suggestions that were offered including the one that suggests a different format. I will post a fiddle of the end result later when I'm done. Thanks all you members of the stackoverflow community
You can iterate the licenseType with Array.map(). For each object get the first entry, and return whatever you want to display:
const licenseType = [{"diggerLisense":["Shallow Digger","Deep Digger"]},{"shoverlerLicense":["Poop shovler","Grass shovler"]}];
const result = licenseType.map((o) => {
const [license, subLicenses] = Object.entries(o)[0];
return `
<h1>${license}</h1>
<ul>
${subLicenses.map((s) => `<li>${s}</li>`).join('\n')}
</ul>
`;
});
demo.innerHTML = result.join('\n');
<div id="demo"></div>
However, having a changing key in an object, complicates it needlessly. I suggest a slightly different format:
{
license: 'digger',
sub: ['Shallow Digger', 'Deep Digger']
}
const licenseType = [{ "license": "digger", "sub": ["Shallow Digger","Deep Digger"]},{ "license": "shoverler", sub: ["Poop shovler","Grass shovler"]}];
const result = licenseType.map(({ license, sub }) => `
<h1>${license}</h1>
<ul>
${sub.map((s) => `<li>${s}</li>`).join('\n')}
</ul>
`);
demo.innerHTML = result.join('\n');
<div id="demo"></div>
Try this:
licenseType.forEach(element => {
element[Object.keys(element)[0]].forEach(subTypes => {
console.log(subTypes);
})
});
Given your current data set, you can do something like this.
However, it would be easier if your top level data structure was an object, not an array.
const licenseType = [
{
diggerLisense: [
"Shallow Digger",
"Deep Digger"
]
},
{
shoverlerLicense: [
"Poop shovler",
"Grass shovler"
]
}
];
const getLicenses = () => licenseType.map(license => Object.keys(license)[0]);
const getData = (toFind) => licenseType.find(license => Object.keys(license)[0] === toFind);
const getSubtypes = (license) => {
const data = getData(license);
if (data) {
subTypes = data[license];
}
return subTypes;
};
console.log(getLicenses());
console.log(getSubtypes('shoverlerLicense'));
LicenseType is an array So you can access the license type like licenseType[i] where it represents the element of the array. for sub license type use
sublicensetype = []
for (i = 0; i < licenseType.length; i++) {
for ( var k in licenseType[i]) sublicenseType.push(k)
}
Try it like this fiddle:
licenseType.forEach(function (type) {
console.log('type', type);
Object.keys(type).forEach(function (typeKey) {
console.log('typeKey',typeKey);
console.log('typeValues',type[typeKey]);
});
});
Also, you have a syntax error in your initial array:
const licenseType = [
{
diggerLisense: [
"Shallow Digger",
"Deep Digger"
]
},
{ //this bracket was missing
shoverlerLicense: [
"Poop shovler",
"Grass shovler"
]
}
]

Parsing structured data in a functional way (e.g. without mutating)

Imagine you have the following data in a file:
Group1
Thing1
Thing2
Group2
Thing1
Thing2
Thing3
Group3
Group4
Thing1
It's easy to write a "parser" which loops through the file line-by-line, remembering the current Group (in a variable) and then writing all the Things to an object, neatly grouped by their respective group:
// Very naive implementation for illustrative purposes only
let groups = {}
let currentGroup = null
data
.split(/\n/)
.forEach(entry => {
const matches = entry.match(/^(Group\d+)$/)
if (matches) {
currentGroup = matches[1]
groups[currentGroup] = []
} else {
groups[currentGroup].push(entry.trim())
}
})
which gives me:
{
Group1: [
'Thing1', 'Thing2'
],
Group2: [
'Thing1', 'Thing2', 'Thing3'
],
...
}
What's the best way to achieve this without mutating groups and currentGroup, in a purely functional way? Do I need to take a harder look at Array.reduce, because I've seen some (IMHO rather mind-boggling) use-cases to transform an Array into an Object, or is that not going to help here?
Yes, you'd want to use reduce here:
data
.split(/\n/)
.reduce(({groups, currentGroup}, entry) => {
const matches = entry.match(/^(Group\d+)$/)
if (matches) {
groups[matches[1]] = []
return {currentGroup: matches[1], groups};
} else {
groups[currentGroup] = groups[currentGroup].concat([entry.trim()]);
return {currentGroup, groups};
}
}, {groups: {}, currentGroup: null})
.groups
However, there is no reasonable way in JS to create a map object without mutation. As long as you keep your property assignments local, there's nothing wrong with that.

Categories

Resources