Агрегация данных массива по заданному размеру
Простите вопрос 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 игнорирует стиль и качество кода. Это не мой код.