Reputation: 95
What is most efficient / elegant way to achieve sql-like filtering effect. I want to filter them and get only that objects which are max value in some group.
This is my code, it works but probably it's not best way:
uniqueValues = (arr) => [...new Set(arr)];
getMaxTimeOf = (arr) => Math.max(...arr.map(o => o.timeStamp), 0);
selectorName = (name) => (obj) => obj.name === name;
selectorTime = (time) => (obj) => obj.timeStamp === time;
getGroup = (obj, selector) => obj.filter(selector)
onlyLastChangedFrom = (history) => {
const uniqueNames = uniqueValues(history.map(o => o.name))
let filtered = []
uniqueNames.forEach(name => {
const group = getGroup(history, selectorName(name))
const groupLastTime = getMaxTimeOf(group)
const lastChange = getGroup(group, selectorTime(groupLastTime))
filtered.push(lastChange[0])
});
return filtered
}
onlyLastChangedFrom(history)
// Input:
[ { name: 'bathroom',
value: 54,
timeStamp: 1562318089713 },
{ name: 'bathroom',
value: 55,
timeStamp: 1562318090807 },
{ name: 'bedroom',
value: 48,
timeStamp: 1562318092084 },
{ name: 'bedroom',
value: 49,
timeStamp: 1562318092223 },
{ name: 'room',
value: 41,
timeStamp: 1562318093467 } ]
// Output:
[ { name: 'bathroom',
value: 55,
timeStamp: 1562318090807 },
{ name: 'bedroom',
value: 49,
timeStamp: 1562318092223 },
{ name: 'room',
value: 41,
timeStamp: 1562318093467 } ]
Upvotes: 8
Views: 6617
Reputation: 386560
What is most efficient / elegant way to achieve sql-like filtering effect.
You could take functions for every step and pipe all functions for a single result.
For example in SQL, you would have the following query:
SELECT name, value, MAX(timeStamp)
FROM data
GROUP BY name;
With an SQL like approach, you could group first and take the max object out of the result sets.
result = pipe(
groupBy('name'),
select(max('timeStamp'))
)(data);
UPDATE 2024
Now with Object.groupBy
const
// functions
pipe = (...functions) => input => functions.reduce((acc, fn) => fn(acc), input),
groupBy = key => array => Object.values(Object.groupBy(
array,
object => object[key]
)),
max = key => array => array.reduce((a, b) => a[key] > b[key] ? a : b),
select = fn => array => array.map(fn),
// data and result
data = [{ name: 'bathroom', value: 54, timeStamp: 1562318089713 }, { name: 'bathroom', value: 55, timeStamp: 1562318090807 }, { name: 'bedroom', value: 48, timeStamp: 1562318092084 }, { name: 'bedroom', value: 49, timeStamp: 1562318092223 }, { name: 'room', value: 41, timeStamp: 1562318093467 }],
result = pipe(
groupBy('name'),
select(max('timeStamp'))
)(data);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Upvotes: 2
Reputation: 5947
You can use sort and filter methods
const arr = [{"name":"bathroom","value":54,"timeStamp":1562318089713},{"name":"bathroom","value":55,"timeStamp":1562318090807},{"name":"bedroom","value":48,"timeStamp":1562318092084},{"name":"bedroom","value":49,"timeStamp":1562318092223},{"name":"room","value":41,"timeStamp":1562318093467}]
arr.sort(function (a, b) {
return b.value - a.value;
}).filter((v, i, a) => a.findIndex((v2) => v2.name === v.name) === i);
console.log(arr);
Upvotes: 0
Reputation: 191976
Reduce the array to an object, using the name
property as the key. For each item, check if the item that exists in the accumulator has a higher value than the current item, and if not replace it with the current item. Convert back to an array with Object.values()
:
const arr = [{"name":"bathroom","value":54,"timeStamp":1562318089713},{"name":"bathroom","value":55,"timeStamp":1562318090807},{"name":"bedroom","value":48,"timeStamp":1562318092084},{"name":"bedroom","value":49,"timeStamp":1562318092223},{"name":"room","value":41,"timeStamp":1562318093467}]
const result = Object.values(arr.reduce((r, o) => {
r[o.name] = (r[o.name] && r[o.name].value > o.value) ? r[o.name] : o
return r
}, {}))
console.log(result)
Upvotes: 13
Reputation: 71
Use map()
and foreach()
to get desired output
const arr=[{name:"bathroom",value:54,timeStamp:1562318089713},
{name:"bathroom",value:55,timeStamp:1562318090807},
{name:"bedroom",value:48,timeStamp:1562318092084},
{name:"bedroom",value:49,timeStamp:1562318092223},
{name:"room",value:41,timeStamp:1562318093467}];
let res = new Map();
arr.forEach((obj) => {
let values = res.get(obj.name);
if(!(values && values.value > obj.value)){
res.set(obj.name, obj)
}
})
console.log(res);
console.log([...res])
Upvotes: 0
Reputation: 22876
Here is another reduce alternative :
var arr = [{"name":"bathroom","value":54,"timeStamp":1562318089713},{"name":"bathroom","value":55,"timeStamp":1562318090807},{"name":"bedroom","value":48,"timeStamp":1562318092084},{"name":"bedroom","value":49,"timeStamp":1562318092223},{"name":"room","value":41,"timeStamp":1562318093467}];
var obj = arr.reduce((r, o) => (o.value < (r[o.name] || {}).value || (r[o.name] = o), r), {});
console.log( Object.values(obj) );
Upvotes: 1
Reputation: 24181
Doing this in stages.
Below is an example.
const input = [{"name":"bathroom","value":54,"timeStamp":1562318089713},{"name":"bathroom","value":55,"timeStamp":1562318090807},{"name":"bedroom","value":48,"timeStamp":1562318092084},{"name":"bedroom","value":49,"timeStamp":1562318092223},{"name":"room","value":41,"timeStamp":1562318093467}];
const output = [...new Set(input.map(m => m.name))].
map(m => [...input].sort(
(a,b) => b.value - a.value).
find(x => m === x.name));
console.log(output);
Upvotes: 0
Reputation: 911
I love to use lodash for stuff like this. It's very functional and therefore very clear and straightforward.
Take a look at the following code:
const DATA = [
{
name: "bathroom",
value: 54,
timeStamp: 1562318089713
},
{
name: "bathroom",
value: 55,
timeStamp: 1562318090807
},
{
name: "bedroom",
value: 48,
timeStamp: 1562318092084
},
{
name: "bedroom",
value: 49,
timeStamp: 1562318092223
},
{
name: "room",
value: 41,
timeStamp: 1562318093467
}
];
let max = _
.chain(DATA)
.groupBy('name')
.sortBy('value')
.map(o => _(o).reverse().first())
.flatten()
.value();
console.log(max); // returns [{"name":"bathroom","value":55,"timeStamp":1562318090807},{"name":"bedroom","value":49,"timeStamp":1562318092223},{"name":"room","value":41,"timeStamp":1562318093467}]
Upvotes: 2
Reputation: 50684
You can use .reduce()
by keeping an accumulated object which keeps the max group currently found and then use Object.values()
to get an array of those objects (instead of an key-value pair object relationship).
See example below:
const arr=[{name:"bathroom",value:54,timeStamp:1562318089713},{name:"bathroom",value:55,timeStamp:1562318090807},{name:"bedroom",value:48,timeStamp:1562318092084},{name:"bedroom",value:49,timeStamp:1562318092223},{name:"room",value:41,timeStamp:1562318093467}];
const res = Object.values(arr.reduce((acc, o) => {
acc[o.name] = acc[o.name] || o;
if (o.value > acc[o.name].value)
acc[o.name] = o;
return acc;
}, {}));
console.log(res);
Upvotes: 0