简体   繁体   中英

Group items inside a Javascript array

I have the following data structure, which I have tried to group the sections inside by their parent level.

  const arr = [
    {
      level: '1.1',
      name: 'group title 1',
    },
    {
      level: '1.1.1',
      name: 'group section 1.1.1',
    },
    {
      level: '1.1.2',
      name: 'group section 1.1.2',
    },
    {
      level: '1.1.3',
      name: 'group section 1.1.3',
    },
    {
      level: '1.2',
      name: 'group title 2',
    },
    {
      level: '1.2.1',
      name: 'group section 1.1.1',
    },
    {
      level: '1.3',
      name: 'group title 3',
    },
    {
      level: '1.3.1',
      name: 'group section 1.1.1',
    },
    {
      level: '1.3.2',
      name: 'group section 1.1.2',
    },
    {
      level: '1.4',
      name: 'group title 4',
    },
    {
      level: '1.4.1',
      name: 'group section 1.4.1',
    },
    {
      level: '1.4.2',
      name: 'group section 1.4.2',
    },
    {
      level: '1.4.3',
      name: 'group section 1.4.3',
    },
  ];

I have been trying to change the structure to the following object (group the sections). The object has the parent level's value as property and an array of the sections.

  const obj = {
    '1.1': [
      {
        level: '1.1.1',
        name: 'group section 1.1.1',
      },
      {
        level: '1.1.2',
        name: 'group section 1.1.2',
      },
      {
        level: '1.1.3',
        name: 'group section 1.1.3',
      },
    ],
    '1.2': [
      {
        level: '1.2',
        name: 'group title 2',
      },
      {
        level: '1.2.1',
        name: 'group section 1.1.1',
      },
    ],
    '1.3': [
      {
        level: '1.3',
        name: 'group title 3',
      },
      {
        level: '1.3.1',
        name: 'group section 1.1.1',
      },
      {
        level: '1.3.2',
        name: 'group section 1.1.2',
      },
    ],
    '1.4': [
      {
        level: '1.4.1',
        name: 'group section 1.4.1',
      },
      {
        level: '1.4.2',
        name: 'group section 1.4.2',
      },
      {
        level: '1.4.3',
        name: 'group section 1.4.3',
      },
    ],
  };

My attempts were not successful. I would appreciate any help on how to change the structure to the above.

Array.prototype.reduce() can be used to construct objects from arrays .

String.prototype.match() can be used to detect patterns in strings .

/\\d+\\.\\d+/ matches the first occurrence of one or more numbers followed by one full stop followed by one or more numbers

 // Input. const input = [{level: '1.1',name: 'group title 1',},{level: '1.1.1',name: 'group section 1.1.1',},{level: '1.1.2',name: 'group section 1.1.2',},{level: '1.1.3',name: 'group section 1.1.3',},{level: '1.2',name: 'group title 2',},{level: '1.2.1',name: 'group section 1.1.1',},{level: '1.3',name: 'group title 3',},{level: '1.3.1',name: 'group section 1.1.1',},{level: '1.3.2',name: 'group section 1.1.2',},{level: '1.4',name: 'group title 4',},{level: '1.4.1',name: 'group section 1.4.1',},{level: '1.4.2',name: 'group section 1.4.2',},{level: '1.4.3',name: 'group section 1.4.3',},] // Group. const group = (array) => array.reduce((o, x) => { const key = x.level.match(/\\d+\\.\\d+/) if (o[key]) o[key].push(x) else o[key] = [x] return o }, {}) // Output. const output = group(input) // Proof. console.log(output) 

In my opinion, and following the DRY principle, a more elegant way would be to use Lodash that already has this function.

const group = arr =>_.groupBy(arr, x => x.level.match(/\d+\.\d+/));

_.groupBy https://lodash.com/docs#groupBy

Creates an object composed of keys generated from the results of running each element of collection thru iteratee. The order of grouped values is determined by the order they occur in collection. The corresponding value of each key is an array of elements responsible for generating the key.

But of course that is if you are allowed to use an external library.

Resulting snippet

 const arr = [ { level: '1.1', name: 'group title 1', }, { level: '1.1.1', name: 'group section 1.1.1', }, { level: '1.1.2', name: 'group section 1.1.2', }, { level: '1.1.3', name: 'group section 1.1.3', }, { level: '1.2', name: 'group title 2', }, { level: '1.2.1', name: 'group section 1.1.1', }, { level: '1.3', name: 'group title 3', }, { level: '1.3.1', name: 'group section 1.1.1', }, { level: '1.3.2', name: 'group section 1.1.2', }, { level: '1.4', name: 'group title 4', }, { level: '1.4.1', name: 'group section 1.4.1', }, { level: '1.4.2', name: 'group section 1.4.2', }, { level: '1.4.3', name: 'group section 1.4.3', }, ]; const group = arr =>_.groupBy(arr, x => x.level.match(/\\d+\\.\\d+/)); const result = group(arr); console.log(result); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script> 

In your question it wasn't clear if you need the headers as well (1.1, 1.2, etc), the above version includes them. But if you don't want them, you first need to erase them from the array:

arr.filter(x => !/^\d+\.\d+$/.test(x.level))

And all together

const group = arr => {
  const noheaders = arr.filter(x => !/^\d+\.\d+$/.test(x.level));
  return _.groupBy(noheaders, x => x.level.match(/\d+\.\d+/));
}

 const arr = [ { level: '1.1', name: 'group title 1', }, { level: '1.1.1', name: 'group section 1.1.1', }, { level: '1.1.2', name: 'group section 1.1.2', }, { level: '1.1.3', name: 'group section 1.1.3', }, { level: '1.2', name: 'group title 2', }, { level: '1.2.1', name: 'group section 1.1.1', }, { level: '1.3', name: 'group title 3', }, { level: '1.3.1', name: 'group section 1.1.1', }, { level: '1.3.2', name: 'group section 1.1.2', }, { level: '1.4', name: 'group title 4', }, { level: '1.4.1', name: 'group section 1.4.1', }, { level: '1.4.2', name: 'group section 1.4.2', }, { level: '1.4.3', name: 'group section 1.4.3', }, ]; const group = arr => { const noheaders = arr.filter(x => !/^\\d+\\.\\d+$/.test(x.level)); return _.groupBy(noheaders, x => x.level.match(/\\d+\\.\\d+/)); } const result = group(arr); console.log(result); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script> 

  const obj = {};

  for(const {level, name} of array) {
    const key = level.split(".").slice(0, 2).join(".");
    if(obj[key]) {
      obj[key].push({ level, name });
    } else {
      obj[key] = [ { level, name } ];
    }
  }

To understand whats going on, the following pages might help:

for ... of

object destructuring

Array#push

This is a simplified version of the expected output. Please check the output in browser console, hence there is an issue with the snippet. Thank you.

 var parent = []; var child =[]; var result =[]; const arr = [ { level: '1.1', name: 'group title 1', }, { level: '1.1.1', name: 'group section 1.1.1', }, { level: '1.1.2', name: 'group section 1.1.2', }, { level: '1.1.3', name: 'group section 1.1.3', }, { level: '1.2', name: 'group title 2', }, { level: '1.2.1', name: 'group section 1.1.1', }, { level: '1.3', name: 'group title 3', }, { level: '1.3.1', name: 'group section 1.1.1', }, { level: '1.3.2', name: 'group section 1.1.2', }, { level: '1.4', name: 'group title 4', }, { level: '1.4.1', name: 'group section 1.4.1', }, { level: '1.4.2', name: 'group section 1.4.2', }, { level: '1.4.3', name: 'group section 1.4.3', }, ]; for(var i=0;i<arr.length;i++){ if(arr[i].level.length == 3){ parent.push(arr[i]); }else{ child.push(arr[i]); } } for(var j=0;j<parent.length;j++){ result[parent[j].level]=[]; for(var k=0;k<child.length;k++){ if(parent[j].level.substring(0, 3)== child[k].level.substring(0, 3)){ result[parent[j].level].push(child[k]); } } } console.log(result); 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM