JavaScript sort array by 2 values - javascript

I've an array and I want to sort it by "id" and "date" from smaller to bigger. How can I do this correctly ?
Example :
var unsorted = [
{id: 1, date: "2015-01-18T15:00:00+01:00"},
{id: 1, date: "2015-01-18T14:30:00+01:00"},
{id: 2, date: "2015-01-18T10:00:00+01:00"},
{id: 1, date: "2015-01-18T16:00:00+01:00"},
{id: 3, date: "2015-01-18T14:15:00+01:00"},
{id: 2, date: "2015-01-18T14:00:00+01:00"}
]
Should return :
var sorted = [
{id: 1, date: "2015-01-18T14:30:00+01:00"},
{id: 1, date: "2015-01-18T15:00:00+01:00"},
{id: 1, date: "2015-01-18T16:00:00+01:00"},
{id: 2, date: "2015-01-18T10:00:00+01:00"},
{id: 2, date: "2015-01-18T14:00:00+01:00"},
{id: 3, date: "2015-01-18T14:15:00+01:00"}
]

Here is an example using array.sort:
var arr = [
{id: 1, date: "2015-01-18T15:00:00+01:00"},
{id: 1, date: "2015-01-18T14:30:00+01:00"},
{id: 2, date: "2015-01-18T10:00:00+01:00"},
{id: 1, date: "2015-01-18T16:00:00+01:00"},
{id: 3, date: "2015-01-18T14:15:00+01:00"},
{id: 2, date: "2015-01-18T14:00:00+01:00"}
];
arr.sort(function(a,b){
if (a.id == b.id) return a.date.localeCompare(b.date);
return a.id-b.id;
});
// test
for (var i in arr) {
console.log(arr[i]);
}
Result being:
Object {id: 1, date: "2015-01-18T14:30:00+01:00"}
Object {id: 1, date: "2015-01-18T15:00:00+01:00"}
Object {id: 1, date: "2015-01-18T16:00:00+01:00"}
Object {id: 2, date: "2015-01-18T10:00:00+01:00"}
Object {id: 2, date: "2015-01-18T14:00:00+01:00"}
Object {id: 3, date: "2015-01-18T14:15:00+01:00"}

You can use .sort():
var unsorted = [
{id: 1, date: "2015-01-18T15:00:00+01:00"},
{id: 1, date: "2015-01-18T14:30:00+01:00"},
{id: 2, date: "2015-01-18T10:00:00+01:00"},
{id: 1, date: "2015-01-18T16:00:00+01:00"},
{id: 3, date: "2015-01-18T14:15:00+01:00"},
{id: 2, date: "2015-01-18T14:00:00+01:00"}
];
var sorted = unsorted.sort(function(a, b) {
return a.id == b.id ?
new Date(a.date) - new Date(b.date) : a.id - b.id;
});
console.log(sorted);
Output:
[ { id: 1, date: '2015-01-18T14:30:00+01:00' },
{ id: 1, date: '2015-01-18T15:00:00+01:00' },
{ id: 1, date: '2015-01-18T16:00:00+01:00' },
{ id: 2, date: '2015-01-18T10:00:00+01:00' },
{ id: 2, date: '2015-01-18T14:00:00+01:00' },
{ id: 3, date: '2015-01-18T14:15:00+01:00' } ]

Give this a shot
var sorted = unsorted.sort(function(a, b) {
return a.id === b.id ?
Date.parse(a.date) - Date.parse(b.date) :
a.id - b.id ;
});
Explanation
If the id field is equal, we want to return the comparison of the date field.
If the id field is not equal, we will return the comparison of the id field

Array.sort takes a function with two parameters to compare two elements of an array. If this function returns a negative then a is placed before b, if it returns positive then a is placed before b and if it returns 0 they stay as they are. Here I compare them by id and if their IDs are same then I compare them by date.
var unsorted = [{
id: 1,
date: "2015-01-18T15:00:00+01:00"
}, {
id: 1,
date: "2015-01-18T14:30:00+01:00"
}, {
id: 2,
date: "2015-01-18T10:00:00+01:00"
}, {
id: 1,
date: "2015-01-18T16:00:00+01:00"
}, {
id: 3,
date: "2015-01-18T14:15:00+01:00"
}, {
id: 2,
date: "2015-01-18T14:00:00+01:00"
}];
unsorted.sort(function(a, b) {
if (a.id < b.id)
return -1;
else if (a.id > b.id)
return 1;
else {
if (a.date < b.date)
return -1;
else if (a.date > b.date)
return 1;
else
return 0;
}
});

Divide and conquer!
Start by reducing the input array into a map of id => object, ie:
var dataById = unsorted.reduce(function (soFar, value) {
// Initialise the array if we haven't processed this
// id yet.
if (soFar[value.id] === undefined) {
soFar[value.id] = [];
}
// ad this object to Array.
soFar[value.id].push(value);
return soFar;
}, {});
Now you can sort each array by looping over the Object's keys, note this modifies the dataById map in place.
Object.keys(dataById).forEach(function (id) {
dataById[id] = dataById[id].sort();
});
Finally, you can combine all the data together, again by iterating over the keys in the map. Note that maps (objects) in javascript don't guarantee the order of their keys, so you may wish to dump the ids out to an Array first before iterating:
var ids = Object.keys(dataById).sort();
// Reduce the ids into an Array of data.
var ids.reduce(function (soFar, value) {
return soFar.concat(dataById[id]);
}, []);
Not the most efficient way of solving your problem, but hopefully it gives you some help with the thought process.

Related

Filter array based on other values in that same array

I have an array of data like so:
[
{id: 1, date: "2022-10-01T12:00:00.00", type: 1},
{id: 2, date: "2022-10-02T12:00:00.00", type: 1},
{id: 3, date: "2022-10-03T12:00:00.00", type: 2},
{id: 4, date: "2022-10-04T12:00:00.00", type: 2},
]
I'd like to filter this so that I get an array of objects that only includes objects with the most recent date for each type. So something like this:
[
{id: 2, dttm: "2022-10-02T12:00:00.00", type: 1},
{id: 4, dttm: "2022-10-04T12:00:00.00", type: 2},
]
I suspect there's a clever way to do this with the .reduce() function, but I haven't quite figured that out yet.
It's easy to compare dates this way just using string comparing since they are sorted by year-month-date. As for the rest, yes reduce is an option but basically it's just a loop over the array grouping by type.
var arr = [
{id: 1, date: "2022-10-01T12:00:00.00", type: 1},
{id: 2, date: "2022-10-02T12:00:00.00", type: 1},
{id: 3, date: "2022-10-03T12:00:00.00", type: 2},
{id: 4, date: "2022-10-04T12:00:00.00", type: 2},
];
var grouped = arr.reduce(function(agg, item) {
agg[item.type] = agg[item.type] || {
id: item.id,
dttm: item.date,
type: item.type
};
if (item.date > agg[item.type].dttm) {
agg[item.type].id = item.id
agg[item.type].dttm = item.date
}
return agg;
}, {})
console.log (Object.values(grouped))
We can achieve this using .reduce by keeping a running tally of the most recent items.
Note:
Dates can be compared in js as their valueOf method is equivalent to someDate.getTime() which gives a nice integer for comparison.
let items = [{
id: 1,
date: "2022-10-01T12:00:00.00",
type: 1
},
{
id: 2,
date: "2022-10-02T12:00:00.00",
type: 1
},
{
id: 3,
date: "2022-10-03T12:00:00.00",
type: 2
},
{
id: 4,
date: "2022-10-04T12:00:00.00",
type: 2
},
]
let recentItems = Object.values(items.reduce((recent, item) => {
debugger;
if (
// type has not items yet
!recent[item.type]
// the current item is more recent
||
new Date(recent[item.type].date) < new Date(item.date)
) {
recent[item.type] = item
}
return recent;
}, {}))
console.log(recentItems)
This does what you specify. (See in-code comments for more info.)
// Calls filter function on the data array and prints output
const data = getData();
console.log(recentOfEachType(data));
// Defines filter function
function recentOfEachType(arr){
// Sets up output array
let recents = [];
// Loops through data to populate output
for(const item of arr){
// Gets position of existing item with this type
const typeIndex = recents.findIndex(recent => recent.type === item.type);
// If no item with this type has been added yet
if(typeIndex < 0){
recents.push(item);
}
// If a newer item is found (ignoring items with identical timestamps)
else if(item.date > recents[typeIndex].date){
recents[typeIndex] = item;
}
// (else do nothing)
}
return recents;
}
// Creates oringal data array
function getData(){
return [
{id: 1, date: "2022-10-01T12:00:00.00", type: 1},
{id: 2, date: "2022-10-02T12:00:00.00", type: 1},
{id: 3, date: "2022-10-03T12:00:00.00", type: 2},
{id: 4, date: "2022-10-04T12:00:00.00", type: 2}
];
}

Sorting array of objects based on two attributes [duplicate]

This question already has answers here:
How to sort an array of objects by multiple fields?
(38 answers)
Closed 1 year ago.
I want to sort Below array based on name and is_closed.
this.rawDataListDup = [
{id: 1, name: 'john','is_closed':true},
{id: 2, name: 'james','is_closed':true},
{id: 3, name: 'jane','is_closed':false},
{id: 4, name: 'alex','is_closed':false},
{id: 5, name: 'david','is_closed':true},
];
As of now i can only sort using any one of the attribute using below code.
let colName = 'name'
this.rawDataListDup.sort((b, a) => a[colName] < b[colName] ? 1 : a[colName] > b[colName] ? -1 : 0)
I want array objects with is_closed = false on top of array and also it should be in Alphabetical order. like this
this.rawDataListDup = [
{id: 4, name: 'alex','is_closed':false},
{id: 3, name: 'jane','is_closed':false},
{id: 5, name: 'david','is_closed':true},
{id: 2, name: 'james','is_closed':true},
{id: 1, name: 'john','is_closed':true},
];
how to do this?
You can do this with sort with multiple conditions inside sort:
const rawDataListDup = [
{id: 1, name: 'john','is_closed':true},
{id: 2, name: 'james','is_closed':true},
{id: 3, name: 'jane','is_closed':false},
{id: 4, name: 'alex','is_closed':false},
{id: 5, name: 'david','is_closed':true},
];
const sortedList = rawDataListDup.sort((a,b)=>(a.is_closed-b.is_closed) || a.name.localeCompare(b.name));
console.log(sortedList);
It just write simple code.
const rawDataListDup = [
{id: 1, name: 'john','is_closed':true},
{id: 2, name: 'james','is_closed':true},
{id: 3, name: 'jane','is_closed':false},
{id: 4, name: 'alex','is_closed':false},
{id: 5, name: 'david','is_closed':true},
];
rawDataListDup.sort(function(a, b) {
return a.is_closed - b.is_closed || a.name - b.name;
});
//OR
var obj = rawDataListDup.sort((a, b) => a.is_closed - b.is_closed || a.name - b.name);
You could also use lodash (sortBy).
Example:
_.sortBy(rawDataListDup, ['is_closed', 'name']);

Common values in array of arrays - lodash

I have an array that looks like this:
const myArray = [
[
{id: 1, name: 'Liam'},
{id: 2, name: 'Oliver'},
{id: 3, name: 'Jake'},
],
[
{id: 1, name: 'Liam'},
{id: 2, name: 'Oliver'},
{id: 4, name: 'Joe'},
],
]
I need to find common elements by id, and return them in an array that would look something like this:
[
{id: 1, name: 'Liam'},
{id: 2, name: 'Oliver'},
]
If there isn't any way to do it with lodash, just JS could work too.
Note that I do not know how many of these arrays I will have inside, so it should work for any number.
You can use lodash's _.intersectionBy(). You'll need to spread myArray because _intersectionBy() expect arrays as arguments, and not a single array of array:
const myArray = [[{"id":1,"name":"Liam"},{"id":2,"name":"Oliver"},{"id":3,"name":"Jake"}],[{"id":1,"name":"Liam"},{"id":2,"name":"Oliver"},{"id":4,"name":"Joe"}]]
const result = _.intersectionBy(...myArray, 'id')
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js" integrity="sha512-90vH1Z83AJY9DmlWa8WkjkV79yfS2n2Oxhsi2dZbIv0nC4E6m5AbH8Nh156kkM7JePmqD6tcZsfad1ueoaovww==" crossorigin="anonymous"></script>
A vanilla solution can be as simple as a filter() call on the first element of the array checking to see that every() subsequent element contains some() elements that match.
const [srcElement, ...compArray] = [...myArray];
const intersection = srcElement.filter(o => (
compArray.every(arr => arr.some(p => p.id === o.id)))
);
console.log(intersection)
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script>
const myArray = [
[{ id: 1, name: 'Liam' }, { id: 2, name: 'Oliver' }, { id: 3, name: 'Jake' }],
[{ id: 1, name: 'Liam' }, { id: 2, name: 'Oliver' }, { id: 4, name: 'Joe' }],
[{ id: 1, name: 'Liam' }, { id: 2, name: 'Oliver' }, { id: 5, name: 'Dean' }, { id: 6, name: 'Mara' }]
]
</script>
Use nested forEach loops and Set. Go over each sub-array and find out the common items so far.
const intersection = ([firstArr, ...restArr]) => {
let common = new Set(firstArr.map(({ id }) => id));
restArr.forEach((arr) => {
const newCommon = new Set();
arr.forEach(({ id }) => common.has(id) && newCommon.add(id));
common = newCommon;
});
return firstArr.filter(({ id }) => common.has(id));
};
const myArray = [
[
{ id: 1, name: "Liam" },
{ id: 2, name: "Oliver" },
{ id: 3, name: "Jake" },
],
[
{ id: 1, name: "Liam" },
{ id: 2, name: "Oliver" },
{ id: 4, name: "Joe" },
],
[
{ id: 2, name: "Oliver" },
{ id: 4, name: "Joe" },
],
];
console.log(intersection(myArray));
Nowadays vanilla ES is pretty powerful to work with collections in a functional way even without the help of utility libraries.
You can use regular Array's methods to get a pure JS solution.
I've created two examples with pure JS.
Of course, there could be more approaches as well. And if you already use Lodash in your application, probably it would be better to just use its high-level implementation in form of _.intersectionBy() proposed above to reduce the code complexity.
const myArray = [
[
{id: 1, name: 'Liam'},
{id: 2, name: 'Oliver'},
{id: 3, name: 'Jake'},
],
[
{id: 1, name: 'Liam'},
{id: 2, name: 'Oliver'},
{id: 4, name: 'Joe'},
],
];
// Regular functional filter-reduce
const reducer = (accum, x) => {
return accum.findIndex(y => x.id == y.id) < 0
? [...accum, x]
: accum;
};
const resultFilterReduce = myArray
.flat()
.filter(x => myArray.every(y => y.findIndex(obj => obj.id === x.id) > -1))
.reduce(reducer, []);
console.log(resultFilterReduce);
// Filter-reduce with using of "HashMap" to remove duplicates
const resultWithHashMap = Object.values(
myArray
.flat()
.filter(x => myArray.every(y => y.findIndex(obj => obj.id === x.id) > -1))
.reduce((accum, x) => {
accum[x.id] = x;
return accum;
}, {})
);
console.log(resultWithHashMap);

Remove duplicate records with same status and preserve only latest

I have below object.
const data = [
{
status: 1,
date: '2020-12-01',
},
{
status: 1,
date: '2020-11-01',
},
{
status: 2,
date: '2020-12-01',
},
{
status: 4,
date: '2020-12-01',
},
{
status: 5,
date: '2020-12-01',
}
]
I need to filter out records with status 4 and 5.
Also, need to have only latest record for status 1.
So the result would be like below.
const data = [
{
status: 1,
date: '2020-12-01',
},
{
status: 2,
date: '2020-12-01',
},
]
This is what I have tried.
data.filter(obj => [1, 2, 3].includes(obj.status))
.filter(obj => obj.status === 1)
.sort((a, b) => new Date(b.date) - new Date(a.date))
But here I am losing object with other status.
I could do by storing filtered result first and then sorting and picking up the latest record, and use something like below
const result = filteredResult.push(latestRecordWithStatusOne)
But, is it possible to achieve this using the same chaining?
After filtering for status being 1,2 or 3, you can then use Array.reduce to create an object with the latest date for each status value. Since the other status values don't have multiple entries, it's safe to use this code for all of them. You can then use Object.values to create your desired output array:
const data = [{
status: 1,
date: '2020-12-01',
},
{
status: 1,
date: '2020-11-01',
},
{
status: 2,
date: '2020-12-01',
},
{
status: 4,
date: '2020-12-01',
},
{
status: 5,
date: '2020-12-01',
}
]
const out = Object.values(data
.filter(obj => [1, 2, 3].includes(obj.status))
.reduce((c, obj) => {
c[obj.status] = c[obj.status] || obj;
if (obj.date > c[obj.status].date)
c[obj.status].date = obj.date
return c;
}, {})
);
console.log(out);
you can try:
const data = [{ status: 1, date: '2020-12-01', },
{ status: 1, date: '2020-11-01', },
{ status: 2, date: '2020-12-01', },
{ status: 4, date: '2020-12-01', },
{ status: 5, date: '2020-12-01', }
]
console.log([...new Set(Object.values(data
.filter(obj => [1, 2, 3].includes(obj.status))).map(item => item.status))])
You can do this in one go with a reduce function. Ignore the 4/5 status and check for newest status 1 and replace if needed. Otherwise, add the value.
const data = [{
status: 1,
date: '2020-12-01',
},
{
status: 1,
date: '2020-11-01',
},
{
status: 2,
date: '2020-12-01',
},
{
status: 4,
date: '2020-12-01',
},
{
status: 5,
date: '2020-12-01',
}
]
const filtered = data.reduce((accumulator, currentValue, index, array) => {
if ([4, 5].includes(currentValue.status)) {
// don't add value since it's a status 4/5
return accumulator;
} else if (currentValue.status === 1) {
// currentValue status is 1 so check if there is already one in the result
const index = accumulator.findIndex(obj => obj.status === 1)
if (index === -1) {
// no other status 1 so add it
return [...accumulator, currentValue]
} else if (accumulator[index].date < currentValue.date) {
// newer status 1 so replace it
return [...accumulator.slice(0, index), currentValue, ...accumulator.slice(index + 1)]
} else {
// already the newest so just return the current accumulator
return accumulator;
}
} else {
// any other status can just be added
return [...accumulator, currentValue];
}
}, []);
console.log(filtered)

Combining Like Objects by Date in JavaScript Array

I have the following array:
var objArray = [
{ num: 1, date: '1/12/2017' },
{ num: 3, date: '1/12/2017' },
{ num: 7, date: '1/12/2017' },
{ num: 1, date: '1/13/2018' },
{ num: 3, date: '1/16/2018' },
{ num: 4, date: '1/16/2018' }
];
I want to combine those with same dates so that the output array looks like this:
var outputArr = [
{ num: 11, date: '1/12/2017' },
{ num: 1, date: '1/13/2018' },
{ num: 7, date: '1/16/2018' }
];
I'm adding all num with similar dates and creating a single new object.
I have a very large dataset of objects like this so I'm trying to reduce the amount of processing time for this.
I've got the arrays sorted by date so that it mirrors objArray.
For loops seems cumbersome since I'm taking the first date in the array and checking every other element in the array a la the following pseudo-code:
var newArr = [];
for(i = 0; i < objArray.length; i++) {
for(j = 0; j < objArray.length; j++) {
var tempArr = [];
// check every date manually
// add similar to new array
tempArr.push({ similar items });
}
newArr.push(tempArr):
}
// Do another couple loops to combine those like arrays into another array
There has to be a more elegant way to perform this than running multiple for loops.
Any suggestions would be appreciated.
Simply use Array.reduce() to create a map and group values by date, Object.values() on the map will give you the desired output value:
let arr = [ { num: 1, date: '1/12/2017' }, { num: 3, date: '1/12/2017' }, { num: 7, date: '1/12/2017' }, { num: 1, date: '1/13/2018' }, { num: 3, date: '1/16/2018' }, { num: 4, date: '1/16/2018' } ];
let result = Object.values(arr.reduce((a, {num, date})=>{
if(!a[date])
a[date] = Object.assign({},{num, date});
else
a[date].num += num;
return a;
},{}));
console.log(result);
Using lodash,
// Aggregate num from unique dates
var g = _.groupBy(objArray,'date')
Object.keys(g).map(k=>({num:g[k].reduce((a,c)=>c.num+a,0),date:k}))
var objArray = [
{ num: 1, date: '1/12/2017' },
{ num: 3, date: '1/12/2017' },
{ num: 7, date: '1/12/2017' },
{ num: 1, date: '1/13/2018' },
{ num: 3, date: '1/16/2018' },
{ num: 4, date: '1/16/2018' }
];
let outputArr = Array.from(objArray.reduce((acc, obj)=>{
acc.set(obj.date, (acc.get([obj.date]) || 0) + obj.num);
return acc;
}, new Map()))
.map(kv=>({num: kv[1], date: kv[0]}))
console.log(outputArr);
gives:
[ { num: 11, date: '1/12/2017' },
{ num: 1, date: '1/13/2018' },
{ num: 7, date: '1/16/2018' } ]
You could also remove the if statements and use a Set if you wanted to be even more declarative.
var objArray = [
{ num: 1, date: '1/12/2017' },
{ num: 3, date: '1/12/2017' },
{ num: 7, date: '1/12/2017' },
{ num: 1, date: '1/13/2018' },
{ num: 3, date: '1/16/2018' },
{ num: 4, date: '1/16/2018' }
];
var mSet = new Set(objArray.map(d => d.date));
return Array.from(mSet).map(d => {
return
{
date: d,
sum: (objArray
.filter(o => o.date === d)
.map(n => n.num)
.reduce((a, c) => a + c, 0))
}
);
This returns:
[{ date: 1/12/2017, sum: 11},
{ date: 1/13/2018, sum: 1 },
{ date: 1/16/2018, sum: 7 }]
Here's another way. It's more verbose, but if you're just starting out it might be easier to understand as opposed to using array methods like reduce().
objArray = [
{ num: 1, date: '1/12/2017' },
{ num: 3, date: '1/12/2017' },
{ num: 7, date: '1/12/2017' },
{ num: 1, date: '1/13/2018' },
{ num: 3, date: '1/16/2018' },
{ num: 4, date: '1/16/2018' }
]
function combineObj(data) {
let validator= new Set();
let combinedArr = [];
let numCount = 0;
// Create a list of unique properties to match against:
data.forEach((e) => validator.add(e.date));
// For each value in the validator, create a new object in a new array
// and add the unique values from the validator to the respective property:
validator.forEach((e) => {
combinedArr.push({
num: 0,
date: e
});
})
// Lastly, for each object in the combinedArr, use a counter to sum up the total values of each property
// as you loop through your data:
combinedArr.forEach((e) => {
numCount = 0;
data.forEach((ee) => {
if (e.date === ee.date) {
numCount += ee.num;
e.num = numCount;
}
})
})
return combinedArr;
}
Returns:
[
{ num: 11, date: '1/12/2017' },
{ num: 1, date: '1/13/2018' },
{ num: 7, date: '1/16/2018' }
]

Categories

Resources