Агрегация данных массива по заданному размеру

Простите вопрос n00b-ish, но я новичок в структурах данных. Недавно меня попросили агрегировать данный массив над другим массивом и получить результат на основе дерева.

Может ли кто-нибудь дать мне несколько указаний о том, как достичь этого результата?

ВХОД

var T = [
  ['COUNTRY', 'GENDER', 'MARITAL STATUS', 'SALES'],
  ['India', 'Female', 'Single', 2400],
  ['India', 'Male', 'Single', 5200],
  ['India', 'Female', 'Married', 4300],
  ['India', 'Male', 'Married', 3200],
  ['England', 'Female', 'Single', 1600],
  ['England', 'Female', 'Married', 2000],
  ['England', 'Male', 'Single', 4800],
  ['England', 'Male', 'Married', 6400],
];

var A = ['GENDER', 'MARITAL STATUS', 'COUNTRY'];

OUTPUT: используйте 2 * пробелы для каждого листа node.

TOTAL 29900
  Female <Female Total>
    Single <Single Female Total>
      India <Single Female Total India>
      England <Single Female Total England>
    Married <Married Female Total>
      India <Married Female Total India>
      England <Married Female Total England>
  Male <Male Total>
    Single <Single Male Total>
      India <Single Male Total India>
      England <Single Male Total England>
    Married <Married Male Total>
      India <Married Male Total India>
      England <Married Male Total England>

Ответ 1

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

{
  total: 29900,
  Female: {
    total: 10300,
    Single: {
      total: 4000,
      India: {
        total: 2400
      },
      ...
    },
    ...
  },
  ...
}

Просто проведите все последующие записи и добавьте значения в соответствующие узлы поддерева.

Для вывода вы можете использовать JSON.stringify и удалить ненужный текст из него.

Предупреждение: спойлеры ниже

const T = [
  ['COUNTRY', 'GENDER', 'MARITAL STATUS', 'SALES'],
  ['India', 'Female', 'Single', 2400],
  ['India', 'Male', 'Single', 5200],
  ['India', 'Female', 'Married', 4300],
  ['India', 'Male', 'Single', 3200],
  ['England', 'Female', 'Single', 1600],
  ['England', 'Female', 'Married', 2000],
  ['England', 'Male', 'Single', 4800],
  ['England', 'Male', 'Married', 6400],
]
const A = ['GENDER', 'MARITAL STATUS', 'COUNTRY']

function aggregate(T, A) {
  const [fields, ...data] = T
  const columns = A.map(name => fields.findIndex(it => name === it))
  const count = fields.length - 1
  const result = { total: 0 }
  data.forEach(values => {
    result.total += values[count]
    //Go through the tree path, reduce is used here
    //to avoid creating extra tracking variable for current position
    columns.reduce((ref, index) => {
      const key = values[index]
      const next = ref[key] || (ref[key] = { total: 0 })
      next.total += values[count]
      return next
    }, result)
  })
  return result
}
function pack(data) {
  return Object.keys(data).reduce((result, key) => {
    if (key !== 'total') {
      const name = key + " " + data[key].total
      result[name] = pack(data[key])
    }
    return result
  }, {})
}
function format(result) {
  return JSON.stringify(pack(result), null, 2)
  .replace(/[^A-z0-9\n\s]/g, '')
  .replace(/\n?\s*\n/g, '\n')
}
function output(it) {
  const result = "TOTAL " + it.total + format(it)
  console.log(result)
}
output(aggregate(T, A))

Ответ 2

Общим подходом к работе с древовидными структурами является представление их как вложенных объектов, как показано DarkKnight answer, а затем создать строковое представление из этой структуры данных.

В случае, представленном OP, альтернативный подход состоит в том, чтобы сначала отсортировать данные, а затем создать строковое представление дерева непосредственно из отсортированных данных без необходимости какой-либо промежуточной структуры данных вложенных объектов.

Учитывая массив столбцов для агрегирования,

['GENDER', 'MARITAL STATUS', 'COUNTRY']

мы можем сортировать данные по этим столбцам:

GENDER   STATUS   COUNTRY   SALES
Female   Single   India     2400
Female   Single   England   1600
Female   Married  India     4300
Female   Married  England   2000
Male     Single   India     5200
Male     Single   England   4800
Male     Married  India     3200
Male     Married  England   6400

Цикл назад из последней строки, при агрегации, мы можем построить строковое представление дерева снизу вверх. Последняя строка отличается от предыдущей на уровне 3 (COUNTRY), которая дает следующий результат:

      England 6400

Строка before отличается на обоих уровнях 3 (COUNTRY) и 2 (MARITAL STATUS), добавленной к текущему выходу:

    Married 9600
      India 3200
      England 6400

После строки перед этим:

      England 4800
    Married 9600
      India 3200
      England 6400

Затем пятая строка отличается от предыдущей на всех трех уровнях:

  Male 19600
    Single 10000
      India 5200
      England 4800
    Married 9600
      India 3200
      England 6400

И так далее, пока не будет представлено все дерево:

Total 29900
  Female 10300
    Single 4000
      India 2400
      England 1600
    Married 6300
      India 4300
      England 2000
  Male 19600
    Single 10000
      India 5200
      England 4800
    Married 9600
      India 3200
      England 6400

Ниже приведен рабочий код (совместимый с ES3), демонстрирующий подход.

var T = [
  ['COUNTRY', 'GENDER', 'MARITAL STATUS', 'SALES'],
  ['India', 'Female', 'Single', 2400],
  ['India', 'Male', 'Single', 5200],
  ['India', 'Female', 'Married', 4300],
  ['India', 'Male', 'Married', 3200],
  ['England', 'Female', 'Single', 1600],
  ['England', 'Female', 'Married', 2000],
  ['England', 'Male', 'Single', 4800],
  ['England', 'Male', 'Married', 6400],
];

var A = ['GENDER', 'MARITAL STATUS', 'COUNTRY'];
var valueField = 'SALES';

// sortBy GENDER ascending
// MARITAL STATUS descending
// COUNTRY descending
var sortDirection = [1, -1, -1];

function find(arr, val){
  for(var i = 0 ; i < arr.length; i++){
    if(arr[i] === val) return i;
  }
  return -1;
}

function buildTreeString(
    data, aggregateBy, sortDirection, valueField
  ) {
  var i, record,
    value, level,
    groupBy = [],
    result = [],
    sums = [],
    total = 0;
  // get column index of valueField
  valueField = find(data[0], valueField);
  // get column indexes to groupBy
  for(var i = 0; i < aggregateBy.length; i++) {
    groupBy[i] = find(data[0], aggregateBy[i]);
  }
  // sort data
  data = data.slice(1)
    .sort(function(a, b) {
      var i, compare = 0, column;
      for(i = 0; i < groupBy.length && !compare; i++) {
        column = groupBy[i];
        compare = a[column] < b[column] ?
          sortDirection[i] : 
          (a[column] > b[column] ? 
            -sortDirection[i] : 0);
      }
      return compare;
    });
  // loop over data rows, output tree nodes
  for(i = 0; i < data.length; i++) {
    record = data[i];
    value = record[valueField];
    total += value;
    //loop over columns, starting from deepest level
    for(level = groupBy.length - 1; level > -1; level--) {
      sums[level] = (sums[level] || 0) + value;
      if(i == data.length - 1 || 
         record[groupBy[level]] != data[i + 1][groupBy[level]]) {
        //output tree node
        result.push(
          Array(level + 2).join('  ') + 
          record[groupBy[level]] + ' ' + 
          sums[level]);
        //reset level aggregation
        sums[level] = 0;
      }
    }
  }
  result.push('Total ' + total);
  result.reverse();
  return result.join('\n');
}

console.log(
  buildTreeString(T, A, sortDirection, valueField)
);

Ответ 3

Дерево - хороший вариант для реализации этого вопроса. Вы также можете выполнять агрегацию в одноразовый проход. Основная идея

  • сортировать данные по заданным столбцам.

  • закодируйте массив, проверьте значение столбца

    2.1 суммировать групповой счетчик, если значение столбца совпадает с предыдущей строкой

    2.2 имя группы вывода и счетчик, если значение столбца отличается от предыдущей строки

Вот пример, которым я руководил студентом CS по его заданию, который очень похож на ваш.

Метод sumaryStage3 в Здесь реализует логику шагов 2.

Pls игнорирует стиль и качество кода. Это не мой код.