Как эффективно сгруппировать массив объектов по дочернему свойству, которое является массивом? - PullRequest
1 голос
/ 24 марта 2020

Я работаю над функцией, которая принимает массив объектов и возвращает объект с данными, сгруппированными по значениям свойства вложенного массива:

INPUT

[
  {
    id: 0,
    section: ['valueX']
  },
  {
    id: 1,
    section: ['valueX']
  },
  {
    id: 2,
    section: ['valueY']
  },
  {
    id: 3,
    section: ['valueY', 'valueX', 'valueZ']
  },
  {
    id: 4,
    section: []
  }
];

OUTPUT


{
  valueX: [{...}]
  valueY: [{...}]
  valueZ: [{...}],
  all: [{...}]
}

У меня есть эта работа с использованием Reduce и нескольких вложенных циклов forEach. Я хотел посмотреть, какие еще есть альтернативы. Я не хочу использовать какую-либо библиотеку или реализовывать крайне нечитаемый код.

МОЯ ПОПЫТКА

const groupBy = ({ data, property }) =>
  data.reduce((arr, current) => {
    const result = arr;
    const groupList = current[property];
    const groupHasItems = groupList.length > 0;
    const groupItemExists = result[groupList];

    if (groupHasItems && !groupItemExists) {
      groupList.forEach(id => {
        if (!result[id]) {
          result[id] = [];
        }
      });
    } else if (!result.all) {
      result.all = [];
    }

    if (groupHasItems) {
      groupList.forEach(id => {
        result[id].push(current);
      });
    } else {
      result.all.push(current);
    }

    return result;
  }, {});

1 Ответ

1 голос
/ 24 марта 2020

У меня есть рекурсивный способ, который может обрабатывать более глубокое вложение и т. Д. c., Который, кажется, не нужен вам (?), Но он есть, если вы этого хотите. Если вам нужен только 1 уровень глубины, то мы также можем упростить его.

const input = [
  {id: 0, section: ['valueX']},
  {id: 1, section: ['valueX']},
  {id: 2, section: ['valueY']},
  {id: 3, section: ['valueY', 'valueX', 'valueZ']},
  {id: 4, section: []}
];

const groupBy = (input, propertyArr) => {
  //console.log(propertyArr);
  const property = propertyArr[0];
  const grouped = input.reduce((groupedObj, item) => {
    const key = item[property];
    if (key instanceof Array && key.length > 0){
      key.forEach(k => {
        groupedObj[k] = [...(groupedObj[k] || []), item];
      })
    }else if (key instanceof Array){
      groupedObj['all'] = [...(groupedObj['all'] || []), item];
    }else{
      groupedObj[key] = [...(groupedObj[key] || []), item];
    }        
    return groupedObj;
  }, {});
  if (propertyArr.length > 1) {
    //console.log(grouped);
    return Object.keys(grouped).reduce((AggObj, key, index) => {
      const propertyArrCopy = [...propertyArr];
      propertyArrCopy.shift();
      AggObj[key] = groupBy(grouped[key], propertyArrCopy);
      return AggObj;
    }, {});
  }else {
    return grouped;
  }
};

const grouped = groupBy(input, ["section"]);
console.log(grouped);

const inputNested = [
  {id: 0, somekey: 'someValA', section: ['valueX']},
  {id: 1, somekey: 'someValB', section: ['valueX']},
  {id: 2, somekey: 'someValA', section: ['valueY']},
  {id: 3, somekey: 'someValB', section: ['valueY', 'valueX', 'valueZ']},
  {id: 4, somekey: 'someValA', section: []}
];

//Nested Example
//const nestedGrouped = groupBy(inputNested, ["somekey", "section"]);
//console.log(nestedGrouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Упрощенный код для вашей конкретной проблемы c 1-го уровня:

const input = [
  {id: 0, section: ['valueX']},
  {id: 1, section: ['valueX']},
  {id: 2, section: ['valueY']},
  {id: 3, section: ['valueY', 'valueX', 'valueZ']},
  {id: 4, section: []}
];

const groupBy = (input, propertyArr) => {
  //console.log(propertyArr);
  const property = propertyArr[0];
  const grouped = input.reduce((groupedObj, item) => {
    const key = item[property];
    if (key instanceof Array && key.length > 0){
      key.forEach(k => {
        groupedObj[k] = [...(groupedObj[k] || []), item];
      })
    }else{
      groupedObj['all'] = [...(groupedObj['all'] || []), item];
    }    
    return groupedObj;
  }, {});
  
  return grouped;
};

const nestedGrouped = groupBy(input, ["section"]);

console.log(nestedGrouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Вот ваш код для справки:

const input = [
  {id: 0, section: ['valueX']},
  {id: 1, section: ['valueX']},
  {id: 2, section: ['valueY']},
  {id: 3, section: ['valueY', 'valueX', 'valueZ']},
  {id: 4, section: []}
];


const groupBy = ({ data, property }) =>
  data.reduce((arr, current) => {
    const result = arr;
    const groupList = current[property];
    const groupHasItems = groupList.length > 0;
    const groupItemExists = result[groupList];

    if (groupHasItems && !groupItemExists) {
      groupList.forEach(id => {
        if (!result[id]) {
          result[id] = [];
        }
      });
    } else if (!result.all) {
      result.all = [];
    }

    if (groupHasItems) {
      groupList.forEach(id => {
        result[id].push(current);
      });
    } else {
      result.all.push(current);
    }

    return result;
  }, {});
  
  console.log(groupBy({ data: input, property: 'section'}))
.as-console-wrapper { max-height: 100% !important; top: 0; }
...