#javascript #arrays #algorithm #average
#javascript #массивы #алгоритм #среднее
Вопрос:
Я застрял на том, как реализовать эту относительно простую операцию в Javascript:
У меня есть список объектов, определенных таким образом:
[
{id: 1, region: "America", country:"USA", values:[1,2,3,4] },
{id: 2, region: "America", country:"Canada", values:[3,4,5,6] },
{id: 3, region: "Europe", country:"France", values:[1,2,3,4] },
{id: 4, region: "Europe", country:"Italy", values:[1,2,3,4] },
{id: 5, region: "Europe", country:"Spain", values:[5,9,1,7] },
{id: 6, region: "Europe", country:"Germany", values:[1,6,2,8] },
{id: 7, region: "Europe", country:"Ireland", values:[6,4,6,9]}
]
Я пытаюсь вычислить среднее значение чисел, содержащихся в поле значения, сгруппированных по региону.
Итак, к приведенному выше списку у меня будет еще два элемента, один для Америки и один для Европы, содержащие среднее значение значений:
[
{id: 1, region: "America", country:"USA", values:[1,2,3,4] },
{id: 2, region: "America", country:"Canada", values:[3,4,5,6] },
{id: 3, region: "Europe", country:"France", values:[1,2,3,4] },
{id: 4, region: "Europe", country:"Italy", values:[1,2,3,4] },
{id: 5, region: "Europe", country:"Spain", values:[5,9,1,7] },
{id: 6, region: "Europe", country:"Germany", values:[1,6,2,8] },
{id: 7, region: "Europe", country:"Ireland", values:[6,4,6,9]},
{id: 8, region: "America", country:"avg", values:[2,3,4,5]},
{id: 9, region: "Europe", country:"avg", values:[2.8,4.6,2.8,6.4]}
]
Есть идеи о том, как это сделать?
Пожалуйста, имейте в виду, что количество элементов для агрегирования может составлять около 10 ~ 15, а числа в полях значений могут составлять около 150 ~ 200
поле значения содержит одинаковое количество значений для всех элементов.
Некоторое значение может быть нулевым, поэтому в этом случае мне нужно вычислить средние значения соответственно, поскольку null не равен 0!
Я мог бы выполнить множество циклов, чтобы просканировать все и произвести вычисления, но мне интересно, есть ли что-то более простое и быстрое, обеспечивающее хорошую производительность.
Объяснение / Пример:
Первое среднее значение для Америки будет рассчитано следующим образом:
(sum of first value of 'values' for each country with region 'America')
-----------------------------------------------------------------------
(number of countries with region 'America')
Псевдокод:
America.avg.values[0] = (USA.values[0] Canada.values[0]) / 2 /*(1 3)/2 = 2*/;
America.avg.values[1] = (USA.values[1] Canada.values[1]) / 2 /*(2 4)/2 = 3*/;
...
Комментарии:
1. @Andreas да, это правильно
Ответ №1:
Вы могли бы использовать объект для хранения суммы и количества элементов и присваивать среднее значение для каждого цикла значений сгруппированному объекту.
var data = [{ id: 1, region: "America", country: "USA", values: [1, 2, 3, 4] }, { id: 2, region: "America", country: "Canada", values: [3, 4, 5, 6] }, { id: 3, region: "Europe", country: "France", values: [1, 2, 3, 4] }, { id: 4, region: "Europe", country: "Italy", values: [1, 2, 3, 4] }, { id: 5, region: "Europe", country: "Spain", values: [5, 9, 1, 7] }, { id: 6, region: "Europe", country: "Germany", values: [1, 6, 2, 8] }, { id: 7, region: "Europe", country: "Ireland", values: [6, 4, 6, 9] }];
data.forEach(function (a, i, aa) {
if (!this[a.region]) {
this[a.region] = { sum: [], count: [], values: [] };
aa.push({ id: aa.length 1, region: a.region, country: 'avg', values: this[a.region].values });
}
a.values.forEach(function (b, i) {
if (b !== null) {
this[a.region].sum[i] = (this[a.region].sum[i] || 0) b;
this[a.region].count[i] = (this[a.region].count[i] || 0) 1;
this[a.region].values[i] = this[a.region].sum[i] / this[a.region].count[i];
}
}, this);
}, Object.create(null));
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Комментарии:
1. Только для последнего
Europe
среднего значения:6.25 !== 6.4
; Также «средние объекты» имеютAmerica
иEurope
. Я сомневаюсь, что ваш алгоритм соответствует требованиям2. @Andreas прав, это не то, что я должен делать, мне не нужно делать среднее значение числа в поле значения, слишком просто!! смотрите ожидаемые результаты в вопросе
3. @Giox Правильно ли вычисление в моем комментарии к вашему вопросу? Если это так, вы могли бы добавить это в свой вопрос в качестве примера
4. @Andreas да, именно то, что мне нужно! но учтите, что я не могу жестко закодировать название региона, поскольку они динамические
5. @NinaScholz идеально
Ответ №2:
Уменьшите исходные данные, отслеживая промежуточные результаты в объекте:
- Объект создает новый ключ для каждой новой области, с которой он сталкивается
- Когда рассматривается первая запись данных для региона, сохраните две вещи:
- Среднее значение, которое начинается как копия значений первого элемента
- Количество, которое представляет собой среднее значение, которое начинается с 1
- Когда появится второй или n-й элемент, вычислите новое среднее значение, используя формулу скользящего среднего
- Возвращайте результирующий объект до тех пор, пока не будут обработаны все записи
- Преобразуйте объект в две точки данных и соедините их с исходным массивом
var data = [
{id: 1, region: "America", country:"USA", values:[1,2,null,4] },
{id: 2, region: "America", country:"Canada", values:[3,4,5,6] },
{id: 3, region: "Europe", country:"France", values:[1,2,3,4] },
{id: 4, region: "Europe", country:"Italy", values:[1,2,3,4] },
{id: 5, region: "Europe", country:"Spain", values:[5,9,1,7] },
{id: 6, region: "Europe", country:"Germany", values:[1,6,2,8] },
{id: 7, region: "Europe", country:"Ireland", values:[6,4,6,9]}
];
var avg = data.reduce(function(result, current) {
if (result[current.region]) {
var obj = result[current.region];
obj.avg = current.values
// Map to a moving average:
// - the current avg at pos `i` represents `count` samples
.map(function(v, i) {
if (v === null) return obj.avg[i];
return (v (obj.avg[i] * obj.count[i])) / ( obj.count[i]);
});
} else {
result[current.region] = {
count: current.values.map(function(v) {
return v !== null ? 1 : 0;
}),
avg: current.values.map(function(v) {
return v !== null ? v : 0;
})
};
}
return resu<
}, {});
// Add to array (assume sorted by id)
var extendedData = data.concat(Object.keys(avg).map(function(k, i) {
return {
id: data[data.length - 1].id 1 i,
region: k,
country: "avg",
values: avg[k].avg
};
}));
console.log(extendedData);
Комментарии:
1. Спасибо, это идеально, а также подход с уменьшением карты очень хорош. Правильным термином для этого вопроса должно быть Скользящее среднее … вы правы.
2. Я обнаружил проблему, когда есть нулевое значение, поэтому данные недоступны… вот так: {идентификатор: 1, регион: «Америка», страна: «США», значения: [1,2, null, 4] }, {идентификатор: 2, регион: «Америка», страна: «Канада», значения: [3,4,5,6] } должно получиться 2,3,5,5
3. Ах, я подумал, что вы хотели бы, чтобы
null
значения обрабатывались как0
… Не видел этого требования. Хотя это легко исправить, я внесу правку.4. Я исправил это, также создав
count
массив. Теперь у каждого индекса значения может быть свой собственный счетчик, что означает, что когдаnull
находится в наборе значений, количество этого индекса значения не будет увеличиваться.5.
map
это способ сделать две вещи одновременно: вы используете массив для создания нового массива равной длины, обрабатывая его значения с помощью переданной функции. Самое замечательное вreduce
том, что он позволяет выполнять цикл по массиву, возвращая значение на следующей итерации. Дайте мне знать, если в этом фрагменте есть определенные строки, которые нуждаются в уточнении; Я буду рад добавить комментарии или предоставить альтернативные способы записи материала, если это необходимо!
Ответ №3:
Я бы просто прошелся по всему массиву и подсчитал среднее значение значений, затем добавил avg
свойство к массиву.
var data = [
{id: 1, region: "America", country:"USA", values:[1,2,3,4] },
{id: 2, region: "America", country:"Canada", values:[3,4,5,6] },
{id: 3, region: "Europe", country:"France", values:[1,2,3,4] },
{id: 4, region: "Europe", country:"Italy", values:[1,2,3,4] },
{id: 5, region: "Europe", country:"Spain", values:[5,9,1,7] },
{id: 6, region: "Europe", country:"Germany", values:[1,6,2,8] },
{id: 7, region: "Europe", country:"Ireland", values:[6,4,6,9]}
];
data.forEach(function(element,index,array){
var sum = 0;
element.values.forEach(function(element,index,array){
sum = element;
});
element.avg = sum / element.values.length;
});
console.log(data);
Комментарии:
1. нет, это неправильно, не соответствует требованиям. Это делает среднее значение по странам, а не по регионам. Смотрите ожидаемый результат в вопросе
Ответ №4:
Вы можете сделать что-то вроде этого:
var data = [{ id: 1, region: "America", country: "USA", values: [1, 2, null, 4] }, { id: 2, region: "America", country: "Canada", values: [3, 4, 5, 6] }, { id: 3, region: "Europe", country: "France", values: [1, 2, 3, 4] }, { id: 4, region: "Europe", country: "Italy", values: [1, 2, 3, 4] }, { id: 5, region: "Europe", country: "Spain", values: [5, 9, 1, 7] }, { id: 6, region: "Europe", country: "Germany", values: [1, 6, 2, 8] }, { id: 7, region: "Europe", country: "Ireland", values: [6, 4, 6, 9] }]
// So that regions are configurable
var regions = ['America', 'Europe'];
var result = [];
// for cases when data is not sorted by id.
var lastIndex = Math.max.apply(null, data.map(function(x){ return x.id}))
regions.forEach(function(r) {
var val = [];
data.forEach(function(c,i){
if(c.region === r amp;amp; c.values amp;amp; c.values.length > 0){
c.values.forEach(function(v, i) {
if (!v amp;amp; v!== 0) return
val[i] = (val[i] || []);
val[i].push(v)
})
}
});
var avg = val.map(function(v){
return (v.reduce(function(p,c){ return p c }) / v.length);
})
// not pushing into data to prevent extra iterations
result.push({
id: lastIndex,
region: r,
country: 'avg',
values: avg
})
});
data = data.concat(result)
console.log(data)
Комментарии:
1. Вы близки, но … «когда есть значение null, мне не нужно учитывать среднее значение: (2 4 null) / 2 = 3»
2. @Andreas Вы принимаете во внимание случай, когда
values
длина является переменной?3.
values
Массив может содержатьnull
, и в этом случае это не должно рассматриваться как0
((val[i] || 0)
) и не должно увеличивать количество элементов (len
) — по крайней мере, это моя интерпретация вопроса и комментария К…4. Обновлена скрипка . И спасибо, что согласились со мной. Я обновил условие в своем ответе на
!v amp;amp; v!== 0
Ответ №5:
Ниже приведено общее решение, которое будет работать для любого количества регионов. Я знаю, что вопросу уже много лет (2 часа), но учтите простоту подхода. Для каждого уникального региона вычислите среднее значение как таковое:
1 — получить все values
массивы как один 2d массив для данной области
2 — транспонировать этот массив
3 — усреднить каждый массив
var data = // your array here
function getArr(data,reg) {
return data.filter(el => el.region === reg)
.map(el => el.values)
}
const xpose = x => x[0].map( (c,i) => x.map( r => r[i] ) )
function avgArr(a) {
a = a.filter(el => el !== null);
return a.reduce((x,y) => x y) / a.length;
}
function calcAverages(data) {
let i = 1 Math.max.apply(null, data.map( el => el.id )) // max id
let regions = [...new Set (data.map (a => a.region))] // unique regions
regions.forEach( region => {
data.push( { id: i ,
region: region,
country: "avg",
values: xpose(getArr(data,region)).map(a => avgArr(a))})})
return data
}
// > calcAverages(data)
//
// ...
// { id: 8,
// region: 'America',
// country: 'avg',
// values: [ 2, 3, 4, 5 ] },
// { id: 9,
// region: 'Europe',
// country: 'avg',
// values: [ 2.8, 4.6, 3, 6.4 ] } ]
Ответ №6:
Я хотел бы сделать следующее
var data = [
{id: 1, region: "America", country:"USA", values:[1,2,3,4] },
{id: 2, region: "America", country:"Canada", values:[3,4,5,6] },
{id: 3, region: "Europe", country:"France", values:[1,2,3,4] },
{id: 4, region: "Europe", country:"Italy", values:[1,2,3,4] },
{id: 5, region: "Europe", country:"Spain", values:[5,9,1,7] },
{id: 6, region: "Europe", country:"Germany", values:[1,6,2,8] },
{id: 7, region: "Europe", country:"Ireland", values:[6,4,6,9]}
],
newData = [...new Set(data.map(c => c.region))]
.reduce((f,c,i) => (f.push(...data.filter(o => o.region === c)
.reduce((p,q,_,a) => { p[0].values = q.values.map((v,j) => v/a.length p[0].values[j]);
p.push(q);
return p;
} ,[{ id: data.length 1 i,
region: c,
country: "avg",
values: Array(data[0].values.length).fill(0)
}])),f),[]);
console.log(newData);