Home > Software engineering >  How to group and sort object array based on string index
How to group and sort object array based on string index

Time:03-14

I want to group & re-arrange my flat array into an array of objects where each object contains its direct descendent in its children's properties.

My input Data -:

const data = [
  { lineNo: '1' },
  { lineNo: '1-1' },
  { lineNo: '1-2' },
  { lineNo: '1-3' },
  { lineNo: '1-4' },
  { lineNo: '1-11' },
  { lineNo: '1-2-1' },
  { lineNo: '1-2-3' },
  { lineNo: '1-2-2' },
  { lineNo: '1-2-4' },
  { lineNo: '1-3-1' },
  { lineNo: '1-3-2' },
  { lineNo: '1-3-3' },
  { lineNo: '1-11-1' },
  { lineNo: '1-11-2' },
  { lineNo: '1-11-3' },
  { lineNo: '2' },
  { lineNo: '2-1' },
  { lineNo: '2-2' },
  { lineNo: '2-3' },
  { lineNo: '2-2-1' },
  { lineNo: '2-2-2' },
];

The output I want -:

const newData = [
  {
    lineNo: '1',
    children: [{ lineNo: '1-1' }, { lineNo: '1-2' }, { lineNo: '1-3' }, { lineNo: '1-4' }, { lineNo: '1-11' }],
  },
  {
    lineNo: '1-2',
    children: [{ lineNo: '1-2-1' }, { lineNo: '1-2-2' }, { lineNo: '1-2-3' }, { lineNo: '1-2-4' }],
  },
  {
    lineNo: '1-3',
    children: [{ lineNo: '1-3-1' }, { lineNo: '1-3-2' }, { lineNo: '1-3-3' }],
  },
  {
    lineNo: '1-11',
    children: [{ lineNo: '1-11-1' }, { lineNo: '1-11-2' }, { lineNo: '1-11-3' }],
  },
  {
    lineNo: '2',
    children: [{ lineNo: '2-1' }, { lineNo: '2-2' }, { lineNo: '2-3' }],
  },
  {
    lineNo: '2-2',
    children: [{ lineNo: '2-2-1' }, { lineNo: '2-2-2' }],
  },
];

Note: The generated data must have to follow the numerical order mentioned in the output.

What i have done so far -:

const data = [
  { lineNo: '1' },
  { lineNo: '1-1' },
  { lineNo: '1-2' },
  { lineNo: '1-3' },
  { lineNo: '1-4' },
  { lineNo: '1-11' },
  { lineNo: '1-11-1' },
  { lineNo: '1-11-2' },
  { lineNo: '1-11-3' },
  { lineNo: '1-2-1' },
  { lineNo: '1-2-3' },
  { lineNo: '1-2-2' },
  { lineNo: '1-2-4' },
  { lineNo: '1-2-1-1' },
  { lineNo: '1-2-1-2' },
  { lineNo: '1-2-1-3' },
  { lineNo: '1-3-1' },
  { lineNo: '1-3-2' },
  { lineNo: '1-3-3' },
];

function createTree(data: any) {
  const tree: any[] = [];
  data.reduce(
    (r: any, o: any) => {
      o.lineNo
        .split('-')
        .map((_: any, i: any, a: any) => a.slice(0, i   1).join('-'))
        .reduce((q: any, lineNo: any, i: any, { length }: any) => {
          let temp = (q.children = q.children || []).find((p: any) => p.lineNo === lineNo);
          if (!temp) {
            q.children.push((temp = { lineNo }));
          }
          if (i   1 === length) {
            Object.assign(temp, o);
          }
          return temp;
        }, r);
      return r;
    },
    { children: tree }
  );

  return tree;
}

const createFlat = (data: any) => {
  const flat: any[] = [];
  const flatData = (data: any) => {
    data.forEach((item: any) => {
      if (item.children) {
        flat.push(item);
        flatData(item.children);
      }
    });
  };
  flatData(data);
  return JSON.parse(JSON.stringify(flat));
};

const getCleanData = (data: any) => {
  const cleanData: any[] = [];
  function delChildren(obj: any) {
    if (obj.children) {
      obj.children.forEach((item: any) => {
        if (item.children) {
          delete item.children;
        }
      });
    }
    return obj;
  }

  data.forEach(function (item: any) {
    cleanData.push(delChildren(item));
  });

  return cleanData;
};

const treeData = createTree(data);
const flatData = createFlat(treeData);
const cleanData = getCleanData(flatData);
console.log(JSON.stringify(cleanData, null, 2));

I am looking for a bit simple and clean approach.

Thank You!

CodePudding user response:

You could first sort the data using a "natural" sort. JavaScript localeCompare has an option for that.

Then create a Map keyed by lineNo and with as corresponding values the objects with the same lineNo property and an empty children array property.

Then iterate the data again to populate those children arrays.

Extract the Map values and remove those objects that have an empty children array, unless they are top-level nodes (a boundary case).

Here is an implementation:

function createTree(data) {
    // Apply natural ordering
    data = [...data].sort((a, b) => 
        a.lineNo.localeCompare(b.lineNo, "en", { numeric: true })
    );
    // Create key/value pairs for all lineNo in a Map
    let map = new Map(data.map(({lineNo}) => [lineNo, { lineNo, children: [] }]));
    // Populate the children arrays
    for (let {lineNo} of data) {
        map.get(lineNo.replace(/-?\d $/, ""))?.children?.push({lineNo});
    }
    // Exclude the nodes that have no children, except if they are top-level
    return [...map.values()].filter(({lineNo, children}) => 
        !lineNo.includes("-") || children.length
    );
}

// Demo
const data = [{ lineNo: '1-1' },{ lineNo: '1-2' },{ lineNo: '1-3-2' },{ lineNo: '1-11-2' },{ lineNo: '1-3' },{ lineNo: '1-11' },{ lineNo: '1-2-3' },{ lineNo: '1-2-2' },{ lineNo: '1' },{ lineNo: '1-2-1' },{ lineNo: '1-2-4' },{ lineNo: '1-11-1' },{ lineNo: '1-3-1' },{ lineNo: '1-4' },{ lineNo: '1-3-3' },{ lineNo: '2-1' },{ lineNo: '2-2' },{ lineNo: '2-2-1' },{ lineNo: '1-11-3' },{ lineNo: '2' },{ lineNo: '2-3' },{ lineNo: '2-2-2' },];

console.log(createTree(data));

  • Related