Remove duplicate elements based on date field in javascript - javascript

I want a function that takes an array and filters out old duplicates.
Specifically, if duplicate ids exist in myList, keep only the object with the newest date. Given the following array
let myList = [{
id: "e9519e95-5a10-4274-ac24-de72ad60ffd7",
date: "2018-02-21 21:04:13"
},
{
id: "026e7ecf-d236-4aff-b26d-7546ac85b7d5",
date: "2018-02-22 21:04:13"
},
{
id: "e9519e95-5a10-4274-ac24-de72ad60ffd7",
date: "2018-02-23 21:04:13"
}]
the function should return:
[{
id: "026e7ecf-d236-4aff-b26d-7546ac85b7d5",
date: "2018-02-22 21:04:13"
},
{
id: "e9519e95-5a10-4274-ac24-de72ad60ffd7",
date: "2018-02-23 21:04:13"
}]

You can use the function reduce to build the desired output.
let myList = [{ id: "e9519e95-5a10-4274-ac24-de72ad60ffd7", date: "2018-02-21 21:04:13"},{ id: "026e7ecf-d236-4aff-b26d-7546ac85b7d5", date: "2018-02-22 21:04:13"},{ id: "e9519e95-5a10-4274-ac24-de72ad60ffd7", date: "2018-02-23 21:04:13"}];
let result = Object.values(myList.reduce((a, {id, date}) => {
if (a[id]) {
if (a[id].date < date) a[id] = {id, date};
} else a[id] = {id, date};
return a;
}, {}));
console.log(result);

Put the entries into a hash table keyed by id. Each time you add an entry, look up the id and either keep the existing entry or replace it with the new one, based on whichever has a more recent date.

Map and Array.prototype.map() can be combined to functionally filter key based duplicates from arrays.
Array.prototype.sort() can be leveraged to guarantee order.
See below for a practical example.
// Input.
const input = [
{id: "e9519e95-5a10-4274-ac24-de72ad60ffd7", date: "2018-02-21 21:04:13"},
{id: "026e7ecf-d236-4aff-b26d-7546ac85b7d5", date: "2018-02-22 21:04:13"},
{id: "e9519e95-5a10-4274-ac24-de72ad60ffd7", date: "2018-02-23 21:04:13"}
]
// Sort By Date.
const sortDate = array => array.sort((A, B) => new Date(A.date)*1 - new Date(B.date)*1)
// Filter Duplicates.
const filter = array => [...new Map(array.map(x => [x.id, x])).values()]
// Output.
const outputRaw = filter(input) // No guaranteed order.
const outputSorted = sortDate(filter(sortDate(input))) // Guaranteed latest.
// Proof.
console.log('Raw', outputRaw)
console.log('Sorted', outputSorted)

This isn't the best answer, just another take on #Ele's solution offered for completeness. Instead of plucking the values after the unique set is found, it works on the returned array for each iteration. The find during each iteration should be less efficient than a key lookup, which is one of the reasons it's not the best answer.
let myList = [{
id: "e9519e95-5a10-4274-ac24-de72ad60ffd7",
date: "2018-02-21 21:04:13"
}, {
id: "026e7ecf-d236-4aff-b26d-7546ac85b7d5",
date: "2018-02-22 21:04:13"
}, {
id: "e9519e95-5a10-4274-ac24-de72ad60ffd7",
date: "2018-02-23 21:04:13"
}]
let result = myList.reduce((arr, { id, date }) => {
let found = arr.find(v=>v.id==id)
if (found) {
if (found.date < date)
found.date = date
}
else
arr.push({ id, date });
return arr;
}, []);
console.log(result);

Related

Object Array with dates, tallied together for each month

Overview
I need to make a chart in my react project.
Using data from a json (Object Array).
Example json:
[
{recruiter_id: 1, datetime_created: "1/01/2021", name: "Aaron"},
{recruiter_id: 2, datetime_created: "9/01/2021", name: "Bob"},
{recruiter_id: 1, datetime_created: "9/01/2021", name: "Aaron"},
{recruiter_id: 3, datetime_created: "20/01/2021", name: "Jane"}
]
Result object array structure required:
[
{name: name,
recruiter_id: recruiter_id,
week_qty: [0,2,1,0,2,0,0,0,0,0,0,0,0,1,0,0,0,...] },
...]
// week_qty will be an array of 52 to represent each week of the year. It will be a 0 if there was no dates for that week.
Goal
This is what the new object array should look like, if we used the example json.
[
{name: "Aaron", recruiter_id:1, week_qty: [1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...]},
{name: "Bob", recruiter_id:2, week_qty: [0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...]},
{name: "Jane", recruiter_id:3, week_qty: [0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...]}
]
What I have
I dont have any working code yet. I am currently working on object[0] to attempt to put the dates into the 52 array. And then after that I will then turn it into a loop to work on each object. Once I have it semi working, I will post it for example.
--- Edit ---
var array = result
var flags = [], output = [], l = array.length, i;
for (i = 0; i < l; i++) {
if (flags[array[i].recruiter_id]) continue;
flags[array[i].recruiter_id] = true;
var temp = {}
temp.Recruiter_id = array[i].recruiter_id
temp.Name = array[i].name
temp.QTY = []
output.push(temp);
}
console.log("output : ", output)
This produces the new object array structure with the id and name filled out.
[
{name: name,
recruiter_id: recruiter_id,
week_qty: [] },
...]
It only has 1 object for each id
Now I need to work on getting the week numbers for the dates and put them into each of those objects.
Question
Any code suggestions on how to get this result?
Side Note
If your curious to know how I then plan on using the new object array to use with my chart.
I will let the user select the week. Lets say week 1.
I will then map through the object array and get the week_qty for index 1 and the name value of the object.
I will store that week_qty and the name in a new new object array.
That new new object array will then look like this
[{name: "Aaron",QTY: 2},{name: "Bob",QTY: 1,]
That will then be passed as the x and y value to the chart.
You can use reduce and increase the week counter after parsing each date and getting the week (using moment.js for that part here)
But you can see Get week of year in JavaScript like in PHP for more details on how to calculate it yourself
const data = [
{recruiter_id: 1, datetime_created: "1/01/2021", name: "Aaron"},
{recruiter_id: 2, datetime_created: "9/01/2021", name: "Bob"},
{recruiter_id: 1, datetime_created: "9/01/2021", name: "Aaron"},
{recruiter_id: 3, datetime_created: "20/01/2021", name: "Jane"}
];
const weekly = data.reduce((acc, item, index, array) => {
const {
recruiter_id,
datetime_created,
name
} = item;
let existing = acc.find(({
recruiter_id: id
}) => id === recruiter_id);
if (!existing) {
existing = {recruiter_id, name, week_qty:Array(52).fill(0)};
acc.push(existing);
}
const week = moment(datetime_created,'D/M/YYYY').week()-1;
existing.week_qty[week]++;
return acc;
}, []);
console.log(JSON.stringify(weekly))
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js" integrity="sha512-qTXRIMyZIFb8iQcfjXWCO8+M5Tbc38Qi5WzdPOYZHIlZpzBHG3L3by84BBBOiRGiEb7KKtAOAs5qYdUiZiQNNQ==" crossorigin="anonymous"></script>

Javascript array difference

I have two arrays like so
data = [{id: 1, name: apple},
{id: 2, name: mango},
{id: 3, name: grapes},
{id: 4, name: banana}]
data2 =[{id: 1, name: apple},
{id: 3, name grapes}]
My Expected result would be:
[{ id: 2, name: mango},
{id:4, name: banana}]
My code is
let finalData =[];
data.forEach(result => {
data2.find(datum => {
if(datum['id'] === result['id]{
finalData.push(result);
}
})
})
I am getting wrong result. What is the simplest code or library that I can use?
Your sample data doesn't make sense, but assuming you mean that all data items that have matching IDs also have matching names and also assuming you want a set of all items where the IDs are the same in the two sets of data, you could use a Set to keep track of which IDs are present in one array then filter the second array by those that have their IDs in the set:
const idsInFirst = new Set(data.map(d => d.id));
const intersection = data2.filter(d => idsInFirst.has(d.id));
The reason why an intermediate Set structure is used is because it allows O(1) lookups after a one-time scan, which is more efficient than repeatedly scanning the first array over and over.
If you meant to say you wanted a difference between data sets (items excluded from data that are in data2), you'd want to negate/inverse things a bit:
const idsToExclude = new Set(data2.map(d => d.id));
const difference = data.filter(d => !idsToExclude.has(d.id));
Edit
After your clarifying edit, it's that second block of code that you'll want.
I would say a good way to do that is filtering your longest array using a function that will validate if the object id is present in both arrays. Check this example:
const data = [
{id: 1, name: 'apple'},
{id: 2, name: 'mango'},
{id: 3, name: 'grapes'},
{id: 4, name: 'banana'}
]
const data2 =[
{id: 1, name: 'apple' },
{id: 3, name: 'grapes' }
]
const longest = data.length > data2.length ? data : data2;
const shortest = data.length <= data2.length ? data : data2;
const finalData = longest.filter( obj => !shortest.find( o => o.id === obj.id ) )
console.log(finalData)
Good luck!

Reduce an Array of Objects With Duplicate IDs by a Second Date Property

I have an array of objects and all of them have at least 1 other object with the same ID. I am trying to reduce this array to only return the object with the most recent date. An example array of what I'm dealing with:
var arrObj = [
{id: 1, startDate: 2019-10-09},
{id: 1, startDate: 2019-10-05},
{id: 1, startDate: 2019-09-30},
{id: 2, startDate: 2018-08-05},
{id: 2, startDate: 2018-09-05}
]
Output I am trying to achieve:
var newArrObj = [
{id: 1, startDate: 2019-10-09},
{id: 2, startDate: 2018-09-05}
]
I have found a lot of examples of reducing or removing duplicate objects by ID completely, but I can't seem to find an example of first identifying the objects with the same ID, then reducing by a second property. Unfortunately I keep finding myself creating a lot of nested for loops. Is there an elegant way to do this? I need to be able to do it in ES5 or earlier.
var uniqueIDs = [];
for (a in arrObj) {
if(uniqueIDs.indexOf(arrObj[a].id) != -1) {
uniqueIDs.push(arrObj[a].id);
}
}
var newArrObj = [];
for(b in uniqueIDs) {
var uniqueId = uniqueIDs[b];
var uniqueIdObjs = arrObj.filter(function(x) {
return x.id === uniqueId;
});
for(c in uniqueIdObjs) {
var objDate = uniqueIdObjs[c].startDate;
//This is where I get stuck
}
}
I'd go with your style of first filtering the IDs to uniques, because thats the easy part.
Then you can simply chain array function so extract the last date:
map over the unique IDs so that your returns create a new array at the end.
filter the original array to include only object with the ID we workin on now
sort the temporary array created with all objects of the same ID so that the mostrecent one would be first, and return the first element.
let finalResult = uniqueIDs.map( id => {
return arrayObj.filter( obj => obj.id === id )
.sort((a, b) => new Date(b.startDate) - new Date(a.startDate))[0];
Tada! finalResult is now and array containing only one object with the most recent date for every ID :)
You can sort the array by date, get an array of ids and dedupe it, then map the deduped array to replace them with actual objects from the sorted array, Array.find will return the first matching result :
var arrObj = [
{ id: 1, startDate: "2019-10-09" },
{ id: 1, startDate: "2019-10-05" },
{ id: 1, startDate: "2019-09-30" },
{ id: 2, startDate: "2018-08-05" },
{ id: 2, startDate: "2018-09-05" }
];
// Sort by date
var sorted = arrObj.sort((a, b) => new Date(b.startDate) - new Date(a.startDate));
// dedupe the array of ids
var ids = [...new Set(sorted.map(o => o.id))];
// replace the ids with original objects
var result = ids.map(id => sorted.find(o => o.id === id));
console.log(result);

How can i display more than one array elements that satisfy a condition?

How can I display multiple values of an array to the console that match the condition (e.g: === "McDonalds")?
I only managed to display one item. But I don't know how i can display all the value of my array.
public products: product[] = [
{ id: 1, name: "McFlurry", price: 2, enseigne:"McDonalds" },
{ id: 2, name: "Potatoes", price: 3, enseigne:"McDonalds" },
{ id: 3, name: "BigMac", price: 4, enseigne:"KFC" },
{ id: 4, name: "Nuggets", price: 3, enseigne:"KFC" }
];
searchEnseigne(){
let server = this.products.find(x => x.enseigne === "McDonalds");
console.log(server);
}
let server = this.products.filter(x => x.enseigne === "McDonalds");
console.log(server);
Use filter instead of find:
The filter() method creates a new array with all elements that pass the test. While The find() method returns the value of the first element
searchEnseigne(){
let server = this.products.filter(x => x.enseigne === "McDonalds");
console.log(server);
}

Consolidating array and adding amounts of similar objects

I have an array of objects that contain a date and an amount (among other things).
There is an object for each date with a specific amount, but there can also be multiple objects for the same date containing different amounts.
I'd like to consolidate the objects so I only have one object in the array for each date ... and have the amount corresponding to that date be the total sum of all previous amounts in those objects.
Examples will probably help here:
What my array looks like now:
[
{
date: "2019-1-1", // this is a dupe date
amount: 20,
...
},
{
date: "2019-1-1", // this is a dupe date
amount: 40,
...
},
{
date: "2019-1-2",
amount: 40,
...
},
{
date: "2019-1-3",
amount: 40,
...
}
]
What I would like my array to look like:
[
{
date: "2019-1-1", // this is now a unique date
amount: 60, // and the amount was totaled
...
},
{
date: "2019-1-2",
amount: 40,
...
},
{
date: "2019-1-3",
amount: 40,
...
}
]
Use .reduce to reduce an array into an object (or into anything else) by iterating over its properties. You just need to test to see if an object with a matching date already exists in the accumulator first:
const input = [
{
date: "2019-1-1", // this is a dupe date
amount: 20,
foo: 'bar',
},
{
date: "2019-1-1", // this is a dupe date
amount: 40,
foo: 'bar',
},
{
date: "2019-1-2",
amount: 40,
foo: 'bar',
},
{
date: "2019-1-3",
amount: 40,
foo: 'bar',
}
];
const output = input.reduce((accum, item) => {
const { date, amount } = item;
const foundObj = accum.find(({ date: findDate }) => findDate === date);
if (foundObj) {
foundObj.amount += amount;
return accum;
}
accum.push(item);
return accum;
}, []);
console.log(output);
You may do as follows;
var data = [ { date: "2019-1-1", // this is a dupe date
amount: 20},
{ date: "2019-1-1", // this is a dupe date
amount: 40},
{ date: "2019-1-2",
amount: 40},
{ date: "2019-1-3",
amount: 40}
],
result = Object.values(data.reduce((r,d) => r[d.date] ? (r[d.date].amount += d.amount, r)
: (r[d.date] = d, r), {}));
console.log(result);
Regarding a comment i guess i have to explain this a little for those who may not be familiar with some ES6 functionalities.
Object.values() is a Object method which returns all own property values in an array.
So we are reducing our objects into an hash object of which we collect the properties by Object.values() later. While reducing we check if the currently examined object's date value exists as key in our map. If not we create that key and insert the examined object at that key position, if yes then we increment the previously inserted objects amount property by the value of currently examined objects amount value.
If you don't want to mutate the original data then please change r[d.date] = d into r[d.date] = Object.assign({},d).
The way I would do it is to create an object with the dates as the key, then you can iterate over the array and create a new date property if it doesn't exist or increase the amount if it does, then convert it back into an array:
const items = data.reduce((acc, curr) => {
if (!acc[curr.date]) { // basically creating a property with the date as the key and the value is the current object
acc[curr.date] = { ...curr };
} else { // if it exists already, then just increment the amount
acc[curr.date].amount += curr.amount;
}
return acc;
}, {});
const newArray = Object.values(items); // grab all the values from the object above

Categories