Najbardziej efektywna metoda groupby na tablicy obiektów
Jaki jest najbardziej efektywny sposób grupowania obiektów w tablicy?
Na przykład, biorąc pod uwagę tę tablicę obiektów:
[
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
]
Wyświetlam te informacje w tabeli. Chciałbym grupować według różnych metod, ale chcę zsumować wartości.
Używam podkreślenia.js dla swojej funkcji groupby, która jest pomocna, ale nie robi całej sztuczki, ponieważ nie chcę, aby były "podzielone", ale "scalone", bardziej jak metoda SQLgroup by
.
What I ' m looking for byłby w stanie sumować konkretne wartości (jeśli jest to wymagane).
Więc gdybym zrobił groupby Phase
, chciałbym otrzymać:
[
{ Phase: "Phase 1", Value: 50 },
{ Phase: "Phase 2", Value: 130 }
]
I gdybym zrobił groupy Phase
/ Step
, otrzymałbym:
[
{ Phase: "Phase 1", Step: "Step 1", Value: 15 },
{ Phase: "Phase 1", Step: "Step 2", Value: 35 },
{ Phase: "Phase 2", Step: "Step 1", Value: 55 },
{ Phase: "Phase 2", Step: "Step 2", Value: 75 }
]
Czy istnieje do tego pomocny skrypt, czy powinienem trzymać się podkreślenia.js, a następnie zapętlenie przez wynikowy obiekt, aby zrobić sumy samodzielnie?
30 answers
Jeśli chcesz uniknąć zewnętrznych bibliotek, możesz zwięźle zaimplementować waniliową wersję groupBy()
w następujący sposób:
var groupBy = function(xs, key) {
return xs.reduce(function(rv, x) {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
};
console.log(groupBy(['one', 'two', 'three'], 'length'));
// => {3: ["one", "two"], 5: ["three"]}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-01-11 08:07:35
Użycie obiektu mapy ES6:
/**
* @description
* Takes an Array<V>, and a grouping function,
* and returns a Map of the array grouped by the grouping function.
*
* @param list An array of type V.
* @param keyGetter A Function that takes the the Array type V as an input, and returns a value of type K.
* K is generally intended to be a property key of V.
*
* @returns Map of the array grouped by the grouping function.
*/
//export function groupBy<K, V>(list: Array<V>, keyGetter: (input: V) => K): Map<K, Array<V>> {
// const map = new Map<K, Array<V>>();
function groupBy(list, keyGetter) {
const map = new Map();
list.forEach((item) => {
const key = keyGetter(item);
const collection = map.get(key);
if (!collection) {
map.set(key, [item]);
} else {
collection.push(item);
}
});
return map;
}
// example usage
const pets = [
{type:"Dog", name:"Spot"},
{type:"Cat", name:"Tiger"},
{type:"Dog", name:"Rover"},
{type:"Cat", name:"Leo"}
];
const grouped = groupBy(pets, pet => pet.type);
console.log(grouped.get("Dog")); // -> [{type:"Dog", name:"Spot"}, {type:"Dog", name:"Rover"}]
console.log(grouped.get("Cat")); // -> [{type:"Cat", name:"Tiger"}, {type:"Cat", name:"Leo"}]
const odd = Symbol();
const even = Symbol();
const numbers = [1,2,3,4,5,6,7];
const oddEven = groupBy(numbers, x => (x % 2 === 1 ? odd : even));
console.log(oddEven.get(odd)); // -> [1,3,5,7]
console.log(oddEven.get(even)); // -> [2,4,6]
O Mapie: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-10-06 15:17:19
Z ES6:
const groupBy = (items, key) => items.reduce(
(result, item) => ({
...result,
[item[key]]: [
...(result[item[key]] || []),
item,
],
}),
{},
);
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-02 21:31:03
Chociaż odpowiedź linq jest interesująca, jest również dość ciężka. Moje podejście jest nieco inne: {]}
var DataGrouper = (function() {
var has = function(obj, target) {
return _.any(obj, function(value) {
return _.isEqual(value, target);
});
};
var keys = function(data, names) {
return _.reduce(data, function(memo, item) {
var key = _.pick(item, names);
if (!has(memo, key)) {
memo.push(key);
}
return memo;
}, []);
};
var group = function(data, names) {
var stems = keys(data, names);
return _.map(stems, function(stem) {
return {
key: stem,
vals:_.map(_.where(data, stem), function(item) {
return _.omit(item, names);
})
};
});
};
group.register = function(name, converter) {
return group[name] = function(data, names) {
return _.map(group(data, names), converter);
};
};
return group;
}());
DataGrouper.register("sum", function(item) {
return _.extend({}, item.key, {Value: _.reduce(item.vals, function(memo, node) {
return memo + Number(node.Value);
}, 0)});
});
Można go zobaczyć w akcji na JSBin.
Nie widziałem w Podkreślniku niczego, co robi to, co robi has
, chociaż może mi to umknąć. Jest tak samo jak _.contains
, ale używa _.isEqual
zamiast ===
do porównań. Poza tym, reszta jest specyficzny problem, choć z próbą być ogólne.
Teraz DataGrouper.sum(data, ["Phase"])
zwraca
[
{Phase: "Phase 1", Value: 50},
{Phase: "Phase 2", Value: 130}
]
I DataGrouper.sum(data, ["Phase", "Step"])
zwraca
[
{Phase: "Phase 1", Step: "Step 1", Value: 15},
{Phase: "Phase 1", Step: "Step 2", Value: 35},
{Phase: "Phase 2", Step: "Step 1", Value: 55},
{Phase: "Phase 2", Step: "Step 2", Value: 75}
]
Ale sum
jest tu tylko jedną potencjalną funkcją. Możesz zarejestrować innych, jak chcesz:
DataGrouper.register("max", function(item) {
return _.extend({}, item.key, {Max: _.reduce(item.vals, function(memo, node) {
return Math.max(memo, Number(node.Value));
}, Number.NEGATIVE_INFINITY)});
});
And now DataGrouper.max(data, ["Phase", "Step"])
will return
[
{Phase: "Phase 1", Step: "Step 1", Max: 10},
{Phase: "Phase 1", Step: "Step 2", Max: 20},
{Phase: "Phase 2", Step: "Step 1", Max: 30},
{Phase: "Phase 2", Step: "Step 2", Max: 40}
]
Lub jeśli zarejestrowałeś to:
DataGrouper.register("tasks", function(item) {
return _.extend({}, item.key, {Tasks: _.map(item.vals, function(item) {
return item.Task + " (" + item.Value + ")";
}).join(", ")});
});
Then calling DataGrouper.tasks(data, ["Phase", "Step"])
will get you
[
{Phase: "Phase 1", Step: "Step 1", Tasks: "Task 1 (5), Task 2 (10)"},
{Phase: "Phase 1", Step: "Step 2", Tasks: "Task 1 (15), Task 2 (20)"},
{Phase: "Phase 2", Step: "Step 1", Tasks: "Task 1 (25), Task 2 (30)"},
{Phase: "Phase 2", Step: "Step 2", Tasks: "Task 1 (35), Task 2 (40)"}
]
DataGrouper
sama w sobie jest funkcją. Możesz wywołać go ze swoimi danymi i listą właściwości, według których chcesz pogrupować. Zwraca tablicę, której elementy są obiektami o dwie właściwości: key
jest zbiorem zgrupowanych właściwości, {[19] } jest tablicą obiektów zawierających pozostałe właściwości nie w kluczu. Na przykład, DataGrouper(data, ["Phase", "Step"])
da:
[
{
"key": {Phase: "Phase 1", Step: "Step 1"},
"vals": [
{Task: "Task 1", Value: "5"},
{Task: "Task 2", Value: "10"}
]
},
{
"key": {Phase: "Phase 1", Step: "Step 2"},
"vals": [
{Task: "Task 1", Value: "15"},
{Task: "Task 2", Value: "20"}
]
},
{
"key": {Phase: "Phase 2", Step: "Step 1"},
"vals": [
{Task: "Task 1", Value: "25"},
{Task: "Task 2", Value: "30"}
]
},
{
"key": {Phase: "Phase 2", Step: "Step 2"},
"vals": [
{Task: "Task 1", Value: "35"},
{Task: "Task 2", Value: "40"}
]
}
]
DataGrouper.register
akceptuje funkcję i tworzy nową funkcję, która akceptuje początkowe dane i właściwości do grupy przez. Ta nowa funkcja następnie przyjmuje format wyjściowy jak powyżej i uruchamia funkcję przed każdym z nich po kolei, zwracając nową tablicę. Wygenerowana funkcja jest przechowywana jako właściwość DataGrouper
zgodnie z podaną nazwą, a także zwrócona, jeśli chcesz tylko lokalnego odniesienia.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-22 12:41:40
Sprawdziłbym lodash groupBy wydaje się, że robi dokładnie to, czego szukasz. Jest również dość lekki i naprawdę prosty.
Przykład: https://jsfiddle.net/r7szvt5k/
Pod warunkiem, że nazwa tablicy to arr
grupa z lodash jest po prostu:
import groupBy from 'lodash/groupBy';
// if you still use require:
// const groupBy = require('lodash/groupBy');
const a = groupBy(arr, function(n) {
return n.Phase;
});
// a is your array grouped by Phase attribute
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-22 10:58:16
Możesz zbudować ES6 Map
z array.reduce()
.
const groupedMap = initialArray.reduce(
(entryMap, e) => entryMap.set(e.id, [...entryMap.get(e.id)||[], e]),
new Map()
);
Ma to kilka zalet w stosunku do innych rozwiązań:]}
- nie wymaga żadnych bibliotek (w przeciwieństwie do np.
_.groupBy()
) - otrzymujesz JavaScript
Map
zamiast obiektu(np. jako zwrócony przez_.groupBy()
). Ma to wiele korzyści , w tym:- zapamiętuje kolejność, w jakiej elementy zostały dodane po raz pierwszy,
- klucze mogą być dowolnym typem, a nie tylko łańcuchami.
- A
Map
jest bardziej użytecznym wynikiem niż tablica tablic. Ale jeśli chcesz mieć tablicę tablic, możesz wywołaćArray.from(groupedMap.entries())
(dla tablicy par[key, group array]
) lubArray.from(groupedMap.values())
(dla prostej tablicy tablic). - jest dość elastyczny; często cokolwiek planowałeś zrobić dalej z tą mapą, można zrobić bezpośrednio w ramach redukcji.
Jako przykład ostatniego punktu, wyobraź sobie, że mam tablicę obiektów, na których chcę wykonać (płytkie) scalenie według id, jak to:
const objsToMerge = [{id: 1, name: "Steve"}, {id: 2, name: "Alice"}, {id: 1, age: 20}];
// The following variable should be created automatically
const mergedArray = [{id: 1, name: "Steve", age: 20}, {id: 2, name: "Alice"}]
To do Zwykle zaczynałem od grupowania według id, a następnie scalania każdej z wynikowych tablic. Zamiast tego możesz wykonać merge bezpośrednio w reduce()
:
const mergedArray = Array.from(
objsToMerge.reduce(
(entryMap, e) => entryMap.set(e.id, {...entryMap.get(e.id)||{}, ...e}),
new Map()
).values()
);
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-21 19:13:51
Jest to prawdopodobnie łatwiejsze do zrobienia z linq.js
, która ma być prawdziwą implementacją LINQ w JavaScript (DEMO):
var linq = Enumerable.From(data);
var result =
linq.GroupBy(function(x){ return x.Phase; })
.Select(function(x){
return {
Phase: x.Key(),
Value: x.Sum(function(y){ return y.Value|0; })
};
}).ToArray();
Wynik:
[
{ Phase: "Phase 1", Value: 50 },
{ Phase: "Phase 2", Value: 130 }
]
Lub, prościej używając selektorów opartych na łańcuchach znaków(DEMO):
linq.GroupBy("$.Phase", "",
"k,e => { Phase:k, Value:e.Sum('$.Value|0') }").ToArray();
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-14 03:12:35
_.groupBy([{tipo: 'A' },{tipo: 'A'}, {tipo: 'B'}], 'tipo');
>> Object {A: Array[2], B: Array[1]}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-12 13:54:30
Możesz to zrobić za pomocą Alasql JavaScript library:
var data = [ { Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" }];
var res = alasql('SELECT Phase, Step, SUM(CAST([Value] AS INT)) AS [Value] \
FROM ? GROUP BY Phase, Step',[data]);
Spróbuj tego przykładu w jsFiddle .
BTW: na dużych tablicach (100000 rekordów i więcej) alasql szybciej tham Linq. Zobacz test w jsPref .
Komentarze:
- tutaj umieszczam wartość w nawiasach kwadratowych, ponieważ wartość jest słowem kluczowym w SQL
- muszę użyć funkcji CAST() do konwersji wartości łańcuchów na typ liczby.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-23 09:09:46
Array.prototype.groupBy = function(keyFunction) {
var groups = {};
this.forEach(function(el) {
var key = keyFunction(el);
if (key in groups == false) {
groups[key] = [];
}
groups[key].push(el);
});
return Object.keys(groups).map(function(key) {
return {
key: key,
values: groups[key]
};
});
};
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-26 20:15:10
MDN ma Ten przykład w swojej dokumentacji Array.reduce()
.
// Grouping objects by a property
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#Grouping_objects_by_a_property#Grouping_objects_by_a_property
var people = [
{ name: 'Alice', age: 21 },
{ name: 'Max', age: 20 },
{ name: 'Jane', age: 20 }
];
function groupBy(objectArray, property) {
return objectArray.reduce(function (acc, obj) {
var key = obj[property];
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(obj);
return acc;
}, {});
}
var groupedPeople = groupBy(people, 'age');
// groupedPeople is:
// {
// 20: [
// { name: 'Max', age: 20 },
// { name: 'Jane', age: 20 }
// ],
// 21: [{ name: 'Alice', age: 21 }]
// }
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-13 15:14:09
Chociaż pytanie ma kilka odpowiedzi i odpowiedzi wyglądają trochę zbyt skomplikowane, proponuję użyć vanilla Javascript dla group-by z zagnieżdżonym (jeśli to konieczne) Map
.
function groupBy(array, groups, valueKey) {
var map = new Map;
groups = [].concat(groups);
return array.reduce((r, o) => {
groups.reduce((m, k, i, { length }) => {
var child;
if (m.has(o[k])) return m.get(o[k]);
if (i + 1 === length) {
child = Object
.assign(...groups.map(k => ({ [k]: o[k] })), { [valueKey]: 0 });
r.push(child);
} else {
child = new Map;
}
m.set(o[k], child);
return child;
}, map)[valueKey] += +o[valueKey];
return r;
}, [])
};
var data = [{ Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" }, { Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" }, { Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" }, { Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" }, { Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" }, { Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" }, { Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" }, { Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }];
console.log(groupBy(data, 'Phase', 'Value'));
console.log(groupBy(data, ['Phase', 'Step'], 'Value'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-10-03 21:56:30
Oto paskudne, trudne do odczytania rozwiązanie przy użyciu ES6:
export default (arr, key) =>
arr.reduce(
(r, v, _, __, k = v[key]) => ((r[k] || (r[k] = [])).push(v), r),
{}
);
Dla tych, którzy pytają, Jak to działa nawet, Oto Wyjaśnienie:
-
W obu
=>
masz wolnyreturn
-
Funkcja
Array.prototype.reduce
przyjmuje do 4 parametrów. Dlatego dodawany jest piąty parametr, dzięki czemu możemy mieć tanią deklarację zmiennej dla grupy (k) na poziomie deklaracji parametru, używając wartości domyślnej. (tak, to jest sorcery) -
Jeśli nasza bieżąca grupa nie istnieje w poprzedniej iteracji, tworzymy nową pustą tablicę
((r[k] || (r[k] = []))
, która będzie oceniać do lewego wyrażenia, innymi słowy, istniejącą tablicę lub pustą tablicę , dlatego po tym wyrażeniu jest natychmiastpush
, ponieważ tak czy inaczej otrzymasz tablicę. -
Gdy istnieje
return
, operator przecinka,
odrzuci wartość po lewej stronie, zwracając poprawioną poprzednią grupę dla tego scenariusz.
Łatwiej zrozumieć wersję, która robi to samo jest:
export default (array, key) =>
array.reduce((previous, currentItem) => {
const group = currentItem[key];
if (!previous[group]) previous[group] = [];
previous[group].push(currentItem);
return previous;
}, {});
Edit:
Wersja TS:
const groupBy = <T, K extends keyof any>(list: T[], getKey: (item: T) => K) =>
list.reduce((previous, currentItem) => {
const group = getKey(currentItem);
if (!previous[group]) previous[group] = [];
previous[group].push(currentItem);
return previous;
}, {} as Record<K, T[]>);
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-07-06 23:31:08
Sprawdzona odpowiedź - tylko płytkie zgrupowanie. Miło jest zrozumieć redukcję. Pytanie zawiera również problem dodatkowych obliczeń zbiorczych.
Oto prawdziwa grupa przez dla tablicy obiektów przez niektóre pola z 1) obliczoną nazwą klucza i 2) kompletnym rozwiązaniem do kaskadowego grupowania poprzez podanie listy żądanych kluczy i konwertowanie jego unikalnych wartości na klucze główne takie jak SQL GROUP przez tak.
const inputArray = [
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
];
var outObject = inputArray.reduce(function(a, e) {
// GROUP BY estimated key (estKey), well, may be a just plain key
// a -- Accumulator result object
// e -- sequentally checked Element, the Element that is tested just at this itaration
// new grouping name may be calculated, but must be based on real value of real field
let estKey = (e['Phase']);
(a[estKey] ? a[estKey] : (a[estKey] = null || [])).push(e);
return a;
}, {});
console.log(outObject);
Zagraj z estKey
-- możesz grupować według więcej niż jedno pole, Dodaj dodatkowe agregacje, obliczenia lub inne przetwarzanie.
Można również grupować dane rekurencyjnie. Na przykład początkowo Grupuj przez Phase
, Następnie przez Step
pole i tak dalej. Dodatkowo blow off
dane o spożyciu tłuszczu.
const inputArray = [
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
];
/**
* Small helper to get SHALLOW copy of obj WITHOUT prop
*/
const rmProp = (obj, prop) => ( (({[prop]:_, ...rest})=>rest)(obj) )
/**
* Group Array by key. Root keys of a resulting array is value
* of specified key.
*
* @param {Array} src The source array
* @param {String} key The by key to group by
* @return {Object} Object with grouped objects as values
*/
const grpBy = (src, key) => src.reduce((a, e) => (
(a[e[key]] = a[e[key]] || []).push(rmProp(e, key)), a
), {});
/**
* Collapse array of object if it consists of only object with single value.
* Replace it by the rest value.
*/
const blowObj = obj => Array.isArray(obj) && obj.length === 1 && Object.values(obj[0]).length === 1 ? Object.values(obj[0])[0] : obj;
/**
* Recursive grouping with list of keys. `keyList` may be an array
* of key names or comma separated list of key names whom UNIQUE values will
* becomes the keys of the resulting object.
*/
const grpByReal = function (src, keyList) {
const [key, ...rest] = Array.isArray(keyList) ? keyList : String(keyList).trim().split(/\s*,\s*/);
const res = key ? grpBy(src, key) : [...src];
if (rest.length) {
for (const k in res) {
res[k] = grpByReal(res[k], rest)
}
} else {
for (const k in res) {
res[k] = blowObj(res[k])
}
}
return res;
}
console.log( JSON.stringify( grpByReal(inputArray, 'Phase, Step, Task'), null, 2 ) );
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-09-17 13:39:08
To rozwiązanie przyjmuje dowolną dowolną funkcję (nie klucz), więc jest bardziej elastyczne niż powyższe rozwiązania i pozwala Funkcje strzałek, które są podobne do wyrażenia lambda używane w LINQ :
Array.prototype.groupBy = function (funcProp) {
return this.reduce(function (acc, val) {
(acc[funcProp(val)] = acc[funcProp(val)] || []).push(val);
return acc;
}, {});
};
Uwaga: To, czy chcesz rozszerzyć prototyp Array
, zależy od Ciebie.
Przykład obsługiwany w większości przeglądarek:
[{a:1,b:"b"},{a:1,c:"c"},{a:2,d:"d"}].groupBy(function(c){return c.a;})
Przykład użycia funkcji strzałek (ES6):
[{a:1,b:"b"},{a:1,c:"c"},{a:2,d:"d"}].groupBy(c=>c.a)
Oba powyższe przykłady return:
{
"1": [{"a": 1, "b": "b"}, {"a": 1, "c": "c"}],
"2": [{"a": 2, "d": "d"}]
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-19 21:01:23
Bez mutacji:
const groupBy = (xs, key) => xs.reduce((acc, x) => Object.assign({}, acc, {
[x[key]]: (acc[x[key]] || []).concat(x)
}), {})
console.log(groupBy(['one', 'two', 'three'], 'length'));
// => {3: ["one", "two"], 5: ["three"]}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-13 20:44:54
Chciałbym zasugerować moje podejście. Po pierwsze, oddzielne grupowanie i agregowanie. Pozwala zadeklarować prototypową funkcję "group by". Potrzeba innej funkcji do wytworzenia ciągu "hash" dla każdego elementu tablicy do grupowania przez.
Array.prototype.groupBy = function(hash){
var _hash = hash ? hash : function(o){return o;};
var _map = {};
var put = function(map, key, value){
if (!map[_hash(key)]) {
map[_hash(key)] = {};
map[_hash(key)].group = [];
map[_hash(key)].key = key;
}
map[_hash(key)].group.push(value);
}
this.map(function(obj){
put(_map, obj, obj);
});
return Object.keys(_map).map(function(key){
return {key: _map[key].key, group: _map[key].group};
});
}
Po zakończeniu grupowania możesz agregować dane tak, jak potrzebujesz, w Twoim przypadku
data.groupBy(function(o){return JSON.stringify({a: o.Phase, b: o.Step});})
/* aggreagating */
.map(function(el){
var sum = el.group.reduce(
function(l,c){
return l + parseInt(c.Value);
},
0
);
el.key.Value = sum;
return el.key;
});
W powszechnym to działa. testowałem ten kod w konsoli chrome. i zapraszam do poprawiania i znajdowania błędów;)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-29 08:49:32
groupByArray(xs, key) {
return xs.reduce(function (rv, x) {
let v = key instanceof Function ? key(x) : x[key];
let el = rv.find((r) => r && r.key === v);
if (el) {
el.values.push(x);
}
else {
rv.push({
key: v,
values: [x]
});
}
return rv;
}, []);
}
Ten wyprowadza tablicę.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-03 10:55:18
Na podstawie poprzednich odpowiedzi
const groupBy = (prop) => (xs) =>
xs.reduce((rv, x) =>
Object.assign(rv, {[x[prop]]: [...(rv[x[prop]] || []), x]}), {});
I jest trochę ładniej patrzeć na składnię object spread, jeśli twoje środowisko obsługuje.
const groupBy = (prop) => (xs) =>
xs.reduce((acc, x) => ({
...acc,
[ x[ prop ] ]: [...( acc[ x[ prop ] ] || []), x],
}), {});
Tutaj, nasz reduktor pobiera częściowo uformowaną wartość zwracaną (zaczynając od pustego obiektu) i zwraca obiekt składający się z rozłożonych członów poprzedniej wartości zwracanej wraz z nowym członem, którego klucz jest obliczany na podstawie bieżącej wartości iteree w prop
i którego wartość jest listą wszystkich wartości dla tego elementu wraz z aktualna wartość.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-12 12:21:12
Wyobraź sobie, że masz coś takiego:
[{id:1, cat:'sedan'},{id:2, cat:'sport'},{id:3, cat:'sport'},{id:4, cat:'sedan'}]
Robiąc to:
const categories = [...new Set(cars.map((car) => car.cat))]
Dostaniesz to:
['sedan','sport']
Explanation: 1. Najpierw tworzymy nowy zestaw, przekazując tablicę. Ponieważ Set zezwala tylko na unikalne wartości, wszystkie duplikaty zostaną usunięte.
- teraz duplikaty zniknęły, przekonwertujemy je z powrotem do tablicy za pomocą operatora spread ...
Zestaw Doc: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set / Align = "left" / https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-02-09 23:46:21
GroupBy one-liner , A ES2021 rozwiązanie
const groupBy = (x,f)=>x.reduce((a,b)=>((a[f(b)]||=[]).push(b),a),{});
Przykłady
const groupBy = (x, f) => x.reduce((a, b) => ((a[f(b)] ||= []).push(b), a), {});
// f -> should must return string/number because it will be use as key in object
// for demo
groupBy([1, 2, 3, 4, 5, 6, 7, 8, 9], v => (v % 2 ? "odd" : "even"));
// { odd: [1, 3, 5, 7, 9], even: [2, 4, 6, 8] };
const colors = [
"Apricot",
"Brown",
"Burgundy",
"Cerulean",
"Peach",
"Pear",
"Red",
];
groupBy(colors, v => v[0]); // group by colors name first letter
// {
// A: ["Apricot"],
// B: ["Brown", "Burgundy"],
// C: ["Cerulean"],
// P: ["Peach", "Pear"],
// R: ["Red"],
// };
groupBy(colors, v => v.length); // group by length of color names
// {
// 3: ["Red"],
// 4: ["Pear"],
// 5: ["Brown", "Peach"],
// 7: ["Apricot"],
// 8: ["Burgundy", "Cerulean"],
// }
const data = [
{ comment: "abc", forItem: 1, inModule: 1 },
{ comment: "pqr", forItem: 1, inModule: 1 },
{ comment: "klm", forItem: 1, inModule: 2 },
{ comment: "xyz", forItem: 1, inModule: 2 },
];
groupBy(data, v => v.inModule); // group by module
// {
// 1: [
// { comment: "abc", forItem: 1, inModule: 1 },
// { comment: "pqr", forItem: 1, inModule: 1 },
// ],
// 2: [
// { comment: "klm", forItem: 1, inModule: 2 },
// { comment: "xyz", forItem: 1, inModule: 2 },
// ],
// }
groupBy(data, x => x.forItem + "-" + x.inModule); // group by module with item
// {
// "1-1": [
// { comment: "abc", forItem: 1, inModule: 1 },
// { comment: "pqr", forItem: 1, inModule: 1 },
// ],
// "2-1": [
// { comment: "klm", forItem: 1, inModule: 2 },
// { comment: "xyz", forItem: 1, inModule: 2 },
// ],
// }
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-11-10 01:54:31
Array.prototype.groupBy = function (groupingKeyFn) {
if (typeof groupingKeyFn !== 'function') {
throw new Error("groupBy take a function as only parameter");
}
return this.reduce((result, item) => {
let key = groupingKeyFn(item);
if (!result[key])
result[key] = [];
result[key].push(item);
return result;
}, {});
}
var a = [
{type: "video", name: "a"},
{type: "image", name: "b"},
{type: "video", name: "c"},
{type: "blog", name: "d"},
{type: "video", name: "e"},
]
console.log(a.groupBy((item) => item.type));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-14 18:39:16
Pozwala wygenerować ogólne narzędzie Array.prototype.groupBy()
. Tylko dla odmiany użyjmy ES6 fanciness operator spread dla niektórych haskellesque dopasowania wzorca na podejściu rekurencyjnym. Niech nasza Array.prototype.groupBy()
zaakceptuje wywołanie zwrotne, które jako argumenty przyjmuje item (e
) index (i
) i zastosowaną tablicę (a
).
Array.prototype.groupBy = function(cb){
return function iterate([x,...xs], i = 0, r = [[],[]]){
cb(x,i,[x,...xs]) ? (r[0].push(x), r)
: (r[1].push(x), r);
return xs.length ? iterate(xs, ++i, r) : r;
}(this);
};
var arr = [0,1,2,3,4,5,6,7,8,9],
res = arr.groupBy(e => e < 5);
console.log(res);
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-05 20:25:14
Aby dodać do odpowiedzi Scotta Sauyeta , niektórzy pytali w komentarzach, jak używać jego funkcji do grupowania wartości1, wartości2 itp., zamiast grupować tylko jedną wartość.
Wystarczy edytować jego funkcję sum:
DataGrouper.register("sum", function(item) {
return _.extend({}, item.key,
{VALUE1: _.reduce(item.vals, function(memo, node) {
return memo + Number(node.VALUE1);}, 0)},
{VALUE2: _.reduce(item.vals, function(memo, node) {
return memo + Number(node.VALUE2);}, 0)}
);
});
Pozostawienie głównego (DataGrouper) bez zmian:
var DataGrouper = (function() {
var has = function(obj, target) {
return _.any(obj, function(value) {
return _.isEqual(value, target);
});
};
var keys = function(data, names) {
return _.reduce(data, function(memo, item) {
var key = _.pick(item, names);
if (!has(memo, key)) {
memo.push(key);
}
return memo;
}, []);
};
var group = function(data, names) {
var stems = keys(data, names);
return _.map(stems, function(stem) {
return {
key: stem,
vals:_.map(_.where(data, stem), function(item) {
return _.omit(item, names);
})
};
});
};
group.register = function(name, converter) {
return group[name] = function(data, names) {
return _.map(group(data, names), converter);
};
};
return group;
}());
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-17 12:40:06
ODPOWIEDŹ Ceasara jest dobra, ale działa tylko dla wewnętrznych właściwości elementów wewnątrz tablicy(długość w przypadku łańcucha).
Ta implementacja działa bardziej jak: ten link
const groupBy = function (arr, f) {
return arr.reduce((out, val) => {
let by = typeof f === 'function' ? '' + f(val) : val[f];
(out[by] = out[by] || []).push(val);
return out;
}, {});
};
Mam nadzieję, że to pomoże...Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-08 14:30:57
From @ mortb, @ jmarceli answer and from this post,
Korzystam z JSON.stringify()
, aby być tożsamością dla prymitywnej wartości wielu kolumn grupy by.
BEZ osób trzecich
function groupBy(list, keyGetter) {
const map = new Map();
list.forEach((item) => {
const key = keyGetter(item);
if (!map.has(key)) {
map.set(key, [item]);
} else {
map.get(key).push(item);
}
});
return map;
}
const pets = [
{type:"Dog", age: 3, name:"Spot"},
{type:"Cat", age: 3, name:"Tiger"},
{type:"Dog", age: 4, name:"Rover"},
{type:"Cat", age: 3, name:"Leo"}
];
const grouped = groupBy(pets,
pet => JSON.stringify({ type: pet.type, age: pet.age }));
console.log(grouped);
Z Lodash third-party
const pets = [
{type:"Dog", age: 3, name:"Spot"},
{type:"Cat", age: 3, name:"Tiger"},
{type:"Dog", age: 4, name:"Rover"},
{type:"Cat", age: 3, name:"Leo"}
];
let rslt = _.groupBy(pets, pet => JSON.stringify(
{ type: pet.type, age: pet.age }));
console.log(rslt);
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-23 10:56:41
ES6 reduce
wersja bazowa z obsługą funkcji iteratee
.
Działa zgodnie z oczekiwaniami, jeśli funkcja iteratee
nie jest podana:
const data = [{id: 1, score: 2},{id: 1, score: 3},{id: 2, score: 2},{id: 2, score: 4}]
const group = (arr, k) => arr.reduce((r, c) => (r[c[k]] = [...r[c[k]] || [], c], r), {});
const groupBy = (arr, k, fn = () => true) =>
arr.reduce((r, c) => (fn(c[k]) ? r[c[k]] = [...r[c[k]] || [], c] : null, r), {});
console.log(group(data, 'id')) // grouping via `reduce`
console.log(groupBy(data, 'id')) // same result if `fn` is omitted
console.log(groupBy(data, 'score', x => x > 2 )) // group with the iteratee
W kontekście pytania OP:
const data = [ { Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" }, { Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" }, { Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" }, { Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" }, { Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" }, { Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" }, { Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" }, { Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" } ]
const groupBy = (arr, k) => arr.reduce((r, c) => (r[c[k]] = [...r[c[k]] || [], c], r), {});
const groupWith = (arr, k, fn = () => true) =>
arr.reduce((r, c) => (fn(c[k]) ? r[c[k]] = [...r[c[k]] || [], c] : null, r), {});
console.log(groupBy(data, 'Phase'))
console.log(groupWith(data, 'Value', x => x > 30 )) // group by `Value` > 30
Kolejna ES6 wersja, która odwraca grupowanie i używa values
jako keys
i keys
jako grouped values
:
const data = [{A: "1"}, {B: "10"}, {C: "10"}]
const groupKeys = arr =>
arr.reduce((r,c) => (Object.keys(c).map(x => r[c[x]] = [...r[c[x]] || [], x]),r),{});
console.log(groupKeys(data))
Uwaga: funkcje są zamieszczane w ich krótkim forma (jedna linijka) dla zwięzłości i odniesienia tylko idei. Możesz je rozwinąć i dodać dodatkowe sprawdzanie błędów itp.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-11-20 07:52:16
Sprawdziłbym deklaratywny-js groupBy
wydaje się robić dokładnie to, czego szukasz. Jest też:
- very performant (performance benchmark)
- napisane maszynopisem, więc wszystkie typizmy są włączone.
- nie jest wymuszane używanie obiektów podobnych do tablic innych firm.
import { Reducers } from 'declarative-js';
import groupBy = Reducers.groupBy;
import Map = Reducers.Map;
const data = [
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
];
data.reduce(groupBy(element=> element.Step), Map());
data.reduce(groupBy('Step'), Map());
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-09-06 15:39:25
Postowanie ponieważ mimo że to pytanie ma 7 lat, nie widzę jeszcze odpowiedzi spełniającej pierwotne kryteria:
Nie chcę ich "rozdzielać", ale "scalać" , bardziej jak grupa SQL przez metoda.
Pierwotnie przyszedłem do tego postu, ponieważ chciałem znaleźć metodę redukcji tablicy obiektów (tj. struktury danych utworzonych podczas odczytu z pliku csv, na przykład) i agregacji przez podane indeksy do wytworzenia tej samej struktury danych. Zwracaną wartością, której szukałem, była inna tablica obiektów, a nie zagnieżdżony obiekt lub mapa, jak widziałem tutaj.
Następująca funkcja pobiera zbiór danych( tablica obiektów), listę indeksów (tablica) oraz funkcję reduktora i zwraca wynik zastosowania funkcji reduktora do indeksów jako tablicy obiektów.
function agg(data, indices, reducer) {
// helper to create unique index as an array
function getUniqueIndexHash(row, indices) {
return indices.reduce((acc, curr) => acc + row[curr], "");
}
// reduce data to single object, whose values will be each of the new rows
// structure is an object whose values are arrays
// [{}] -> {{}}
// no operation performed, simply grouping
let groupedObj = data.reduce((acc, curr) => {
let currIndex = getUniqueIndexHash(curr, indices);
// if key does not exist, create array with current row
if (!Object.keys(acc).includes(currIndex)) {
acc = {...acc, [currIndex]: [curr]}
// otherwise, extend the array at currIndex
} else {
acc = {...acc, [currIndex]: acc[currIndex].concat(curr)};
}
return acc;
}, {})
// reduce the array into a single object by applying the reducer
let reduced = Object.values(groupedObj).map(arr => {
// for each sub-array, reduce into single object using the reducer function
let reduceValues = arr.reduce(reducer, {});
// reducer returns simply the aggregates - add in the indices here
// each of the objects in "arr" has the same indices, so we take the first
let indexObj = indices.reduce((acc, curr) => {
acc = {...acc, [curr]: arr[0][curr]};
return acc;
}, {});
reduceValues = {...indexObj, ...reduceValues};
return reduceValues;
});
return reduced;
}
Stworzę reduktor, który zwróci count ( * ) i Sum (wartość):
reducer = (acc, curr) => {
acc.count = 1 + (acc.count || 0);
acc.value = +curr.Value + (acc.value|| 0);
return acc;
}
Wreszcie, zastosowanie funkcji agg z naszym reduktorem do oryginalny zbiór danych daje tablicę obiektów z zastosowanymi odpowiednimi agregacjami:
agg(tasks, ["Phase"], reducer);
// yields:
Array(2) [
0: Object {Phase: "Phase 1", count: 4, value: 50}
1: Object {Phase: "Phase 2", count: 4, value: 130}
]
agg(tasks, ["Phase", "Step"], reducer);
// yields:
Array(4) [
0: Object {Phase: "Phase 1", Step: "Step 1", count: 2, value: 15}
1: Object {Phase: "Phase 1", Step: "Step 2", count: 2, value: 35}
2: Object {Phase: "Phase 2", Step: "Step 1", count: 2, value: 55}
3: Object {Phase: "Phase 2", Step: "Step 2", count: 2, value: 75}
]
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-15 17:21:34
Nie wydaje mi się, aby podane odpowiedzi odpowiadały na pytanie, myślę, że ta poniżej powinna odpowiedzieć na pierwszą część :
const arr = [
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
]
const groupBy = (key) => arr.reduce((total, currentValue) => {
const newTotal = total;
if (
total.length &&
total[total.length - 1][key] === currentValue[key]
)
newTotal[total.length - 1] = {
...total[total.length - 1],
...currentValue,
Value: parseInt(total[total.length - 1].Value) + parseInt(currentValue.Value),
};
else newTotal[total.length] = currentValue;
return newTotal;
}, []);
console.log(groupBy('Phase'));
// => [{ Phase: "Phase 1", Value: 50 },{ Phase: "Phase 2", Value: 130 }]
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-10-16 11:54:40