On this script I look up properties in an array of objects. There is an original array of strings list
to get the objects that match the name
property, then I look up their respective line managers, reports
recursively but only up to the rank
2, finally I merge their entries with the manager ones.
I was wondering if there is a more efficient way to achieve this result as I am having to make an object to index the managers that have already been checked, otherwise I get duplicates when two people have the same manager. Also having to specify the rank
limit twice.
const data = [
{
name: 'rebecca',
reports: '',
rank: 1
},
{
name: 'will',
reports: 'rebecca',
rank: 2
},
{
name: 'jose',
reports: 'will',
rank: 3
},
{
name: 'tomas',
reports: 'jose',
rank: 4,
},
{
name: 'matt',
reports: 'jose',
rank: 5
},{
name: 'alison',
reports: 'john',
rank: 5
}
]
// Initial list of names
const list = ['tomas', 'matt']
// Get the people on the list first
const filterList = data.filter(({ name }) => list.includes(name))
// Helper object to account for managers already checked and avoid duplicates
const managers = {}
// Find missing managers recursively
const findManager = (manager) => {
const next = data.find(({ name }) => name === manager)
managers[next.name] = true
return next.rank > 2 ? [next, ...findManager(next.reports)] : [next]
}
// Get the line managers for the filterList array
const missingManagers = []
for (const { reports, rank } of filterList) {
if (!list.includes(reports) && rank > 2 && !managers[reports] ) {
missingManagers.push(...findManager(reports))
}
}
const result = [...missingManagers, ...filterList]
console.log(result)
.as-console-wrapper { max-height: 100% !important; top: 0; }
2 Answers 2
From a medium review;
- This code belongs in a well named function
- I always forget about destructuring in a
filter
, very nice - Mapping to an object automatically removes dupes, something I (ab)use in my counter
const findManager = (manager)
should probably beconst findManager = (employee)
const findManager = (employee)
should probably beconst findLineManagers = (employee)
- From there,
next
should probably bemanager
, it is definitely not 'next' but more 'actual' ;) - You are not using semicolons, unless you understand all nuances, you should use them
- It's more idiomatic in recursive programming to first check for the exit condition than then to check at the end if you should recurse or not
I struggled with this counter-proposal. I imagine in a codebase I maintain I would want my data structure to be linked. So this function gets a linked version of the data and then gets what is needed in what I think is an easier manner. YMMV.
// Initial list of names
function indexPeopleData(peopleData){
//Index by name
const indexedPeople = peopleData.reduce((out,person) => (out[person.name] = person, out), {});
//Make reports point to a people object
peopleData.forEach(person => person.reports = indexedPeople[person.reports]);
return indexedPeople;
}
function getLineManagers(peopleData, names){
const indexedPeople = indexPeopleData(peopleData);
//Return list of employees and their managers
let out = {}, name;
while(name = names.pop()){
out[name] = indexedPeople[name];
if(out[name].reports.rank > 1){
names.push(out[name].reports.name);
}
}
return Object.values(out);
}
var data = [
{
name: 'rebecca',
reports: '',
rank: 1
},
{
name: 'will',
reports: 'rebecca',
rank: 2
},
{
name: 'jose',
reports: 'will',
rank: 3
},
{
name: 'tomas',
reports: 'jose',
rank: 4,
},
{
name: 'matt',
reports: 'jose',
rank: 5
},{
name: 'alison',
reports: 'john',
rank: 5
}
];
console.log(getLineManagers(data, ['tomas', 'matt']));
-
\$\begingroup\$ Thanks for your response, it is an interesting approach, however the output is incorrect, it should be a flat collection of objects in an array with no deeper levels. \$\endgroup\$Álvaro– Álvaro2021年03月15日 10:25:58 +00:00Commented Mar 15, 2021 at 10:25
V poor naming!
I have to say that you must improve your naming because as presented the code is unreadable. Not only is the naming very poor, the comments made it even worse, being misplaced or directly counter the name of the function (I presume) the comments describe.
The only way I could workout what your code should do was to run it and look at the result. I then guessed based on one example.
My guess is...
Given a list of manager names list all managers they report to up to rank 2, including managers in the given list.
Maps
When you need to search for items in a list by a given field you use a Map. Convert the list to a Map indexed by the field you want to search by. To Find an item in a map the complexity is \$O(1)\$
Sets
When you need to create a list without duplicates use a Set. You add items to the set and it automatically ensures only one copy of each item.
Rewrite
Using a map and set to create the array of managers in the chain up from given manager names, including the given manager name.
There is no indication as to the order of the resulting array so no code was added to ensure that the order matched your example.
"use strict";
const manager = (name, reports, rank) => ({name, reports, rank});
const data = [ manager("rebecca", "", 1), manager("will", "rebecca", 2), manager("jose", "will", 3), manager("tomas", "jose", 4), manager("matt", "jose", 5), manager("alison", "john", 5)];
function managersByField(field, managers) {
return new Map(managers.map(manager => [manager[field], manager]));
}
function managmentChain(name, maxRank, managers, chain) {
var next = managers.get(name);
while (next?.rank >= maxRank) {
chain.add(next);
next = managers.get(next.reports);
}
}
function createReport(managers, maxRank, ...names) {
const res = new Set();
while (names.length) {
managmentChain(names.pop(), maxRank, managers, res);
}
return [...res];
}
console.log(createReport(managersByField("name", data), 2, 'tomas', 'matt'));
NOTE! The data MUST NOT contain cyclic references. Example of cyclic reference. "A" reports to "B" and "B" reports to "A".
-
\$\begingroup\$ Thanks for your response, it does work very well. And a nice usage of
Map
andSet
. I don't understand why you re-wrote the original array with themanager
function, you could use the originaldata
array and would work fine. Also there is no need to passname
field as a parameter. And the intention was to send thetomas
andmatt
as an array not as string parameters in a function. \$\endgroup\$Álvaro– Álvaro2021年03月16日 15:44:44 +00:00Commented Mar 16, 2021 at 15:44 -
\$\begingroup\$ @Álvaro The data provided did not give enough of a test. I use the spread operator for arrays as you can pass either individual items or an array using the spread operator eg
createReport(managersByField("name", data), 2, ...list)
\$\endgroup\$Blindman67– Blindman672021年03月17日 08:59:11 +00:00Commented Mar 17, 2021 at 8:59
Explore related questions
See similar questions with these tags.
tomas
tomatt
, or the management chain from each of these persons as thereports
person? How isrank
related to the given (person) object? Is rank is where that person falls with a given management chain, say, starting withtomas
and ending withmatt
? I'm just seeing this as a sorting issue so far. \$\endgroup\$