1
\$\begingroup\$

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; }

\$\endgroup\$
6
  • \$\begingroup\$ I get duplicates when two people have the same manager. Sounds like a job for StackOverflow, not CodeReview which is intended for reviewing (not debugging) working code. \$\endgroup\$
    – radarbob
    Mar 15 at 17:05
  • \$\begingroup\$ Well not really, because I did fix that on my code, so my code is a working code. Was just asking for a better approach if any. You know if I take this to stackoverflow someone just like you would tell me it belongs here. \$\endgroup\$
    – Álvaro
    Mar 15 at 17:20
  • \$\begingroup\$ You may be right. At times it seems like a slight change of wording can change interpretation. I can see how this: "... having to make an object ... otherwise I get duplicates." means the code works. My thought is that duplicates in a recursive search means either bad data - someone is (eventually) his own manager - or a code defect. \$\endgroup\$
    – radarbob
    Mar 16 at 0:54
  • \$\begingroup\$ If you care to actually read and test the code you will know the reason I get duplicates is because some people on the data array have the same manager, the data is not wrong, the code could be better, sure, but that's why it is here. \$\endgroup\$
    – Álvaro
    Mar 16 at 1:45
  • \$\begingroup\$ I apologize for not understanding what is going on. What is the goal? Find the management chain from, say, from tomas to matt, or the management chain from each of these persons as the reports person? How is rank related to the given (person) object? Is rank is where that person falls with a given management chain, say, starting with tomas and ending with matt? I'm just seeing this as a sorting issue so far. \$\endgroup\$
    – radarbob
    Mar 16 at 2:10
1
\$\begingroup\$

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 be const findManager = (employee)
  • const findManager = (employee) should probably be const findLineManagers = (employee)
  • From there, next should probably be manager , 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']));

\$\endgroup\$
1
  • \$\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
    Mar 15 at 10:25
1
\$\begingroup\$

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".

\$\endgroup\$
2
  • \$\begingroup\$ Thanks for your response, it does work very well. And a nice usage of Map and Set. I don't understand why you re-wrote the original array with the manager function, you could use the original data array and would work fine. Also there is no need to pass name field as a parameter. And the intention was to send the tomas and matt as an array not as string parameters in a function. \$\endgroup\$
    – Álvaro
    Mar 16 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
    Mar 17 at 8:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.