Ayoub k
Ayoub k

Reputation: 8868

Get unique values from an object of arrays (complex)

var myObject = {
  word1: {
    a: ['1.json', '2.json']
    b: ['3.json', '4.json', '5.json']
  },
  word2: {
    x: ['1.json', '3.json'],
    y: ['2.json', '4.json'],
    z: ['5.json']
  }
}

So the output of this script should be:

['1.json', '4.json', '5.json']

Here is the logic, I want to be able to get the files that use the min stuff. Take for example "1.json", that one shows in:

myObject.word1.a

but also on

myObejct.word2.x

This means that '1.json' is no longer going to be used. But will be added to the output.

output so far: [1.json]

Here comes the tricky part.

Now lets go to '2.json'. It can't be used since 'myObject.word1.a' is already in use by '1.json', so just skip it.

output so far: [1.json]

Now lets go to '3.json', it appears on myobject.word1.b it apperas on myObject.word2.x // BUT 1.json already filled this position

So it can't be used

output so far: [1.json]

Lets go to 4.json it appears on myobject.word1.b it appears on myobject.word2.y <- here remember we skipped '2.json' since it did not work. So '4.json' is accepted

output so far: [1.json, 4.json]

lets go to 5.json it appears on myobject.word1.b it also appears on myobject.word2.z

this can be used since the "z" key has not been used by any other file.

Upvotes: 1

Views: 52

Answers (1)

Majed Badawi
Majed Badawi

Reputation: 28404

Let's name the combination of the word and letter as "dir", and the array elements as "files".

While Iterating over each dir you can save them in a Set (dirs). In each dir, you can loop over its files and update a Map (fileDirsMap) saving this file's dirs.

After this step, you would have a set of dirs available, and a map of each file and its dirs:

dirs: [ "word1.a", "word1.b", "word2.x", "word2.y", "word2.z" ]
fileDirsMap: [
  [ "1.json", [ "word1.a", "word2.x" ] ],
  [ "2.json", [ "word1.a", "word2.y" ] ],
  [ "3.json", [ "word1.b", "word2.x" ] ],
  [ "4.json", [ "word1.b", "word2.y" ] ],
  [ "5.json", [ "word1.b", "word2.z" ] ]
]

Now, you can iterate over fileDirsMap's entries (Array#reduce is suitable here). In each iteration, check if this file has all its dirs "not taken", if they are, add the file to the resulting array (acc) and remove all its dirs from the set (dirs) (setting them as taken).

const myObject = {
  word1: {
    a: ['1.json', '2.json'],
    b: ['3.json', '4.json', '5.json']
  },
  word2: {
    x: ['1.json', '3.json'],
    y: ['2.json', '4.json'],
    z: ['5.json']
  }
};

const fileDirsMap = new Map();
const dirs = new Set();
Object.entries(myObject).forEach(([word, letters]) => {
  Object.entries(letters).forEach(([letter, files]) => {
    const dir = `${word}.${letter}`;
    dirs.add(dir);
    files.forEach(file => 
      fileDirsMap.set(file, [...(fileDirsMap.get(file) || []), dir])
    );
  });
});

const res = [...fileDirsMap.entries()].reduce((acc,[file, fileDirs]) => {
  const allDirsNotTaken = fileDirs.every(fileDir => dirs.has(fileDir));
  if(allDirsNotTaken) {
    acc.push(file);
    fileDirs.forEach(fileDir => dirs.delete(fileDir));
  }
  return acc;
}, []);

console.log(res);

Upvotes: 1

Related Questions