3

I would like to create an array of object from another array of object to plot a graph.

This is the array I use to place the position of object inside the expected object.

let uniqueSkills = ['Using', 'Analyzing', 'Summarizing', 'Inferring', 'Predicting', 'Activating']

This is the object I have which needs to be modified.

let data = {
      data0:[
        {count: 1, length: 1, skill: "Activating"},
        {count: 4, length: 1, skill: "Using"},
        {count: 2, length: 1, skill: "Analyzing"}
      ],
      data1: [
        {count: 2, length: 1, skill: "Summarizing"}
      ],
      data2: [
        {count: 1, length: 1, skill: "Predicting"},
        {count: 4, length: 1, skill: "Analyzing"}
      ]
    }

The final result object should look like this.

data = {
      data0:[
        {count: 4, length: 1, skill: "Using"},
        {count: 2, length: 1, skill: "Analyzing"},
        {skill: "Summarizing"},
        {skill: "Inferring"},
        {skill: "Predicting"},
        {count: 4, length: 1, skill: "Activating"}

      ],
      data1: [
        {skill: "Using"},
        {skill: "Analyzing"},
        {count: 2, length: 1, skill: "Summarizing"},
        {skill: "Inferring"},
        {skill: "Predicting"},
        {skill: "Activating"}
      ],
      data2: [
        {skill: "Using"},
        {count: 4, length: 1, skill: "Analyzing"},
        {skill: "Summarizing"},
        {skill: "Inferring"},
        {count: 1, length: 1, skill: "Predicting"},
        {skill: "Activating"}
      ]
    }

The algorithm that I wrote works fine in some cases but breaks in some. Here it is,

Object.keys(data).forEach(key => {      
      for (let i = 0; i < uniqueSkills.length; i++) {       
        if (typeof data[key][i] == 'object') {      
          if (data[key][i].skill !== uniqueSkills[i]) {     
            let index = uniqueSkills.indexOf(data[key][i].skill)
            if (typeof data[key][index] == 'object') {
              let anotherIndex = uniqueSkills.indexOf(data[key][index].skill)
              let elementAtIndex = data[key][index]
              let elementAtAnotherIndex = data[key][anotherIndex]
              data[key][i] = elementAtIndex
              data[key][index] = elementAtAnotherIndex
            }
            else {
              data[key][index] = data[key][i]
              data[key][i] = {skill: uniqueSkills[i]}
            }
          }
        } else {        
          data[key][i] = {skill: uniqueSkills[i]}       
        }       
      }     
    })

2 Answers 2

4

You could use a Map to first create like an empty template, with only skill properties in the objects, and then populate that Map with the actual data you have. Object.entries and Object.fromEntries can be used to make conversions from plain object to array and vice versa.

As a Map retains insertion order, the output order will be guaranteed.

let uniqueSkills = ['Using', 'Analyzing', 'Summarizing', 'Inferring', 'Predicting', 'Activating']
let data = {data0:[{count: 1, length: 1, skill: "Activating"},{count: 4, length: 1, skill: "Using"},{count: 2, length: 1, skill: "Analyzing"}],data1: [{count: 2, length: 1, skill: "Summarizing"}],data2: [{count: 1, length: 1, skill: "Predicting"},{count: 4, length: 1, skill: "Analyzing"}]};

let newData = Object.fromEntries(Object.entries(data).map(([k, arr]) =>
    [k, Array.from(arr.reduce(
        (map, o) => map.set(o.skill, o),
        new Map(uniqueSkills.map(skill => [skill, { skill }]))
    ).values())]
));

console.log(newData);

Note that this solution has a worst case O(n²) time complexity (in terms of the length of uniqueSkills). Your own solution has a time complexity of O(n³), with the two outer loops (forEach and for) and nested call of indexOf, which also represents a loop. Other solutions that, instead of indexOf, have a nested find or findIndex, also have a time complexity of O(n³). Using Map#get instead of one of these array methods, brings the complexity down to O(n²).

This solution does not mutate the original data object. It produces a new object. The new object will however still include the original objects found in the original data arrays.

The problem with your solution is that you walk a path of i, index, anotherIndex, trying to move an object that stands in the way for the object you want to insert. But this path can be even longer than that. This "walk" of creating gaps, moving to the next position, creating a gap there, ...etc, is a priori not limited in length.

Sign up to request clarification or add additional context in comments.

1 Comment

Indeed, algorithm that I wrote has bad time complexity. Thank you very much for this solution. It is simple to understand and read.
2

For each object in the data, call Array.prototype.map for uniqueSkills and find an object. return the object if found, return a new one if not.

And replace the array with the origin.

let uniqueSkills = ['Using', 'Analyzing', 'Summarizing', 'Inferring', 'Predicting', 'Activating']
let data = {data0:[{count: 1, length: 1, skill: "Activating"},{count: 4, length: 1, skill: "Using"},{count: 2, length: 1, skill: "Analyzing"}],data1: [{count: 2, length: 1, skill: "Summarizing"}],data2: [{count: 1, length: 1, skill: "Predicting"},{count: 4, length: 1, skill: "Analyzing"}]};

Object.keys(data).forEach(key => {
  data[key] =
    uniqueSkills.map(skill => data[key].find(e => e.skill === skill) || { skill });
});

console.log(data);

2 Comments

Perfect usage of ES6 which makes it so simple. Thank you very much.
It is a nice solution, but I would not call it perfect, because of the non-optimal O(n³) time complexity.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.