How to sort by name descending in lodash? - javascript

I have an array of objects that are sorted in descending order by date:
_.sortBy
(persons.entities.alerts,
dateObj => new Date(dateObj.createdDateTime)
).reverse()
This is the array:
let persons={
"entities": {
"applicants": [
{
"lastName": "Agamemnon",
"isPrimaryApplicant": true,
"id": "16671520038"
},
{
"lastName": "Purdy",
"isPrimaryApplicant": false,
"id": "16671520039"
},
{
"lastName": "Brekky",
"isPrimaryApplicant": true,
"id": "16671520040"
},
{
"lastName": "Tabouli",
"isPrimaryApplicant": true,
"id": "16671520041"
}
],
"alerts": [
{
"createdDateTime": "2018-06-14T00:00:00.000Z",
"applicants": ["16671520038", "16671520039"],
"id": "05025fea-ec37-4767-a868-a646597365d0"
},
{
"createdDateTime": "2018-06-14T00:00:00.000Z",
"applicants": ["16671520040"],
"id": "19d0da63-dfd0-4c00-a13a-cc822fc83869"
},
{
"createdDateTime": "2018-06-14T00:00:00.000Z",
"applicants": ["16671520041"],
"id": "c5385595-2104-409d-a676-c1b57346f63e"
}
]
}
}
The sort returns the correct order by date desc. In this sample the dates are the same. Only in this case i want to sort by (applicants) lastName where isPrimaryApplicant=true? Link to codepen

You want lodash's orderBy, which allows sort directions.
You can attach asc or desc to each sort property you use.
This should get you the ordering you're looking for:
_.orderBy(persons.entities.applicants, ['lastName'], ['desc'])

Loadash sortBy doesn't provide option for comparator function(though there are other ways to achieve it)
You can use array sort method to achieve this:
persons.entities.alerts.sort(function(a1, a2) {
if(a1.createdDateTime === a2.createdDateTime) {
let applicant1 = persons.entities.applicants.find(a => a.id === a1.applicants[0]);
let applicant2 = persons.entities.applicants.find(a => a.id === a2.applicants[0]);
if (!applicant1.isPrimaryApplicant || applicant1.lastName < applicant2.lastName) {
return -1;
}
return 1;
} else {
let d1 = new Date(a1.createdDateTime);
let d2 = new Date(a2.createdDateTime);
return d2 - d1;
}
})

Would have loved to use lodash for this but the documentation does not reflect reality. The second argument to _.sortBy is an array in documentation but doesn't work if I pass an array of functions.
You can add names to your alerts and while your at it add a sortDate to use for sorting:
const persons={"entities":{"applicants":[{"lastName":"Agamemnon","isPrimaryApplicant":true,"id":"16671520038"},{"lastName":"Purdy","isPrimaryApplicant":false,"id":"16671520039"},{"lastName":"Brekky","isPrimaryApplicant":true,"id":"16671520040"},{"lastName":"Tabouli","isPrimaryApplicant":true,"id":"16671520041"}],"alerts":[{"createdDateTime":"2018-06-14T00:00:00.000Z","applicants":["16671520038","16671520039"],"id":"05025fea-ec37-4767-a868-a646597365d0"},{"createdDateTime":"2018-06-14T00:00:00.000Z","applicants":["16671520041"],"id":"19d0da63-dfd0-4c00-a13a-cc822fc83869"},{"createdDateTime":"2019-06-14T00:00:00.000Z","applicants":["16671520040"],"id":"c5385595-2104-409d-a676-c1b57346f63e"}]}}
const applicantsById = persons.entities.applicants.reduce(
(result, applicant) => result.set(applicant.id, applicant),
new Map(),
);
const alertsWithName = persons.entities.alerts.map((alert) => ({
...alert,
sortDate:new Date(alert.createdDateTime).getTime(),
name: (alert.applicants
.map((id) => applicantsById.get(id))
.filter((x) => x) //remove empty
.find((applicant)=>applicant.isPrimaryApplicant)||{lastName:''}).lastName
}));
//according to not correct lodash documentation here:
//https://lodash.com/docs/4.17.11#sortBy
//we should be able to do this:
// console.log(
// _.sortBy(alertsWithName, [
// (alert) => new Date(alert.createdDateTime),
// (alert) => alert.name,
// ])
// )
//however that's not going to work so can try array sort method
console.log(
alertsWithName.sort(
(a,b)=>b.sortDate-a.sortDate || a.name.localeCompare(b.name)
)
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.core.js"></script>

Related

Grouping elements of array on the basis of property

I have a array as follows:
data = [
{
"id":1
"name":"london"
},
{
"id":2
"name":"paris"
},
{
"id":3
"name":"london"
},
{
"id":4
"name":"paris"
},
{
"id":5
"name":"australia"
},
{
"id":6
"name":"newzearland"
}
]
At runtime this array can have n number of elements. I want to group this array with respect to name attribute. All the elements with same name should be moved to a separate array. I don't know the what value can name have in advance. This is coming at runtime. For example, from above array I want final output as follows:
output:
newArray1 = [
{
"id":1
"name":"london"
},
{
"id":3
"name":"london"
}
]
newArray2 = [
{
"id":2
"name":"paris"
},
{
"id":4
"name":"paris"
}
]
newArray3 = [
{
"id":5
"name":"australia"
}
]
newArray4 = [
{
"id":6
"name":"newzearland"
}
]
How can I do that?
As Teemu has already pointed out in a comment, creating new variables to store the data is not ideal. You would have no way of knowing how many groups you've created and using variables that you can't be sure exist is not the best way to write code. Fortunately, JavaScript has objects, which can store data like this in a much cleaner way. Here's the code I've come up with:
function groupBy(arr, key) {
let res = {}
for (let element of arr) {
if (res.hasOwnProperty(element[key])) {
res[element[key]].push(element)
} else {
res[element[key]] = [element]
}
}
return res
}
This code is not the best, most efficient code ever, but it is written to be easier to understand for someone still learning. This code loops over every element in your data and checks whether our result already contains an array for elements with that name. If there's already an array for elements with that name, the current element is added to it. If there isn't one, a new one is created with the current element inside it. To do exactly what you want, you'd call this function with groupBy(data, "name") and assign it to a new variable like groupedData (THIS DOES NOT MODIFY THE DATA, IT RETURNS A NEW OBJECT OF GROUPED DATA) .
Start by getting all the unique .names, then map them to the original array filtered by each .name:
const data = [{
"id": 1, "name": "london"
},
{
"id": 2, "name": "paris"
},
{
"id": 3, "name": "london"
},
{
"id": 4, "name": "paris"
},
{
"id": 5, "name": "australia"
},
{
"id": 6, "name": "newzearland"
}
];
const newData = [...new Set(data
//Get all names in an array
.map(({name}) => name))]
//For each name filter original array by name
.map(n => data.filter(({name}) => n === name));
console.log( newData );
//OUTPUT: [newArray1, newArray2, .....]
You can get the expected result with grouping by key approach.
const data = [{"id":1,"name":"london"},{"id":2,"name":"paris"},{"id":3,"name":"london"},{"id":4,"name":"paris"},{"id":5,"name":"australia"},{"id":6,"name":"newzearland"}];
const result = Object.values(data.reduce((acc, obj) =>
({ ...acc, [obj.name]: [...(acc[obj.name] ?? []), obj] }), {}));
console.log(result);
const [newArray1, newArray2, newArray3, newArray4, ...rest] = result;
console.log('newArray1:', newArray1);
console.log('newArray2:', newArray2);
console.log('newArray3:', newArray3);
console.log('newArray4:', newArray4);
.as-console-wrapper{min-height: 100%!important; top: 0}

Convert array object into object in Javascript

I am trying to covert an array object into object array with matching filterKey and filterValue of current object.
[
{
"filterKey": "name",
"filterValue": [
{
"value": "abc"
},
{
"value": "def"
}
]
},
{
"filterKey": "status",
"filterValue": [
{
"value": "active"
},
{
"value": "pending"
}
]
}
]
The result I am expecting as below:
{
"name": [
"abc",
"def"
],
"statuses": [
"active",
"pending"
]
}
I tried
obj.map(item => Object.values(item.filterValue))
reduce over the array of data, and create a new object, using map to create a new array of values for each new property.
const data=[{filterKey:'name',filterValue:[{value:'abc'},{value:'def'}]},{filterKey:'status',filterValue:[{value:'active'},{value:'pending'}]}];
// Iterate over the array with `reduce`
const out = data.reduce((acc, obj) => {
// Extract the key and value from each object
const { filterKey: key, filterValue: value } = obj;
// Return the accumulator object updated with the new key
// and array of values gained from `map`
return {...acc, [key]: value.map(o => o.value)};
}, {});
console.log(out);
const oldArr = [{"filterKey": "name","filterValue": [{"value": "abc"},{"value": "def"}]},{"filterKey": "status","filterValue": [{"value": "active"},{"value": "pending"}]}];
const newObj = oldArr.reduce((a, b) => { // reduce to single object
const values = b.filterValue.map(x => x.value); // map the filter values to their strings
if (a[b.filterKey]) a[b.filterKey].concat(values); // if the key already exists add the strings to it (does not apply to your example)
else a[b.filterKey] = values; // else create a new entry with the strings
return a;
}, {});
// if you really want to rename status => statuses
newObj.statuses = newObj.status;
delete newObj.status;
console.log(newObj);

Javascript - Enumerating key, value pairs in object and converting those that match ISO timestamp format

Overview: I have an array (arr) of objects. I need to convert any value in an object that looks like an ISO timestamp to an epoch. I match via regular expression.
I have an object that looks like:
{
assignee: null
color: null
description: "cool object"
id: "12234858"
last_updated: "2021-01-22T15:30:10.495000+00:00"
}
I have some code that does the following:
arr.forEach((item) => {
let regex = /^[+-]?\d{4}(-[01]\d(-[0-3]\d(T[0-2]\d:[0-5]\d:?([0-5]\d(\.\d+)?)?[+-][0-2]\d:[0-5]\dZ?)?)?)?/;
Object.entries(myobject).forEach(([key, value]) => {
if (value !== null) {
if (value.match(regex)) {
value = Date.parse(value) / 1000;
}
}
});
});
The date conversions work while iterating through the key, values of each object, but once the arr.forEach() completes, the values are reset. What is the best way to handle these conversions? Should I map to new object and duplicate?
The value itself is a primitive that doesn't retain "connection" to the object it was taken from. You need to assign the value back to the item using the key you've destructured.
Note: your regex doesn't actually work, and detects other values as dates. It's easier just to parse, and check if the value is NaN. See this answer.
const arr = [{
assignee: null,
color: null,
description: "cool object",
id: "12234858",
last_updated: "2021-01-22T15:30:10.495000+00:00",
}];
arr.forEach((item) => {
Object.entries(item).forEach(([key, value]) => {
if (!isNaN(Date.parse(value || ''))) {
// assign the new value back to the original item
item[key] = Date.parse(value) / 1000;
}
});
});
console.log(arr);
As noted by #NicholasCarey's comment, !isNaN(Date.parse()) might identify other strings as dates. So if you are experiencing false positives, you might want to use regex (taken from this answer) or a stricter library:
const arr = [{
assignee: null,
color: null,
description: "cool object",
id: "12234858",
last_updated: "2021-01-22T15:30:10.495000+00:00",
}];
const regex = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/;
arr.forEach((item) => {
Object.entries(item).forEach(([key, value]) => {
if (regex.test(value || '')) {
// assign the new value back to the original item
item[key] = Date.parse(value) / 1000;
}
});
});
console.log(arr);
I would use a library like Luxon. Like so:
const {DateTime} = require('luxon');
function transformIso8601ToSomethingUsable(arr) {
for ( const obj of arr ) {
for ( const [k,v] of Object.entries(obj) ) {
obj[k] = iso8601DateTime2epochTime(v);
}
}
}
function iso8601DateTime2epochTime(s) {
const dt = DateTime.fromISO(s).toUTC();
const epochTime = dt.isValid
? Math.round(dt.toSeconds())
: s ;
return epochTime;
}
Then you can say something like
const arr = [{
assignee: null,
color: null,
description: "cool object",
id: "12234858",
last_updated: "2021-01-22T15:30:10.495000+00:00",
}];
transformIso8601ToSomethingUsable(arr);
and get
[
{
"assignee": null,
"color": null,
"description": "cool object",
"id": "12234858",
"last_updated": 1611329410
}
]

Find difference between two arrays

I have following Plunkr which works perfectly.
https://plnkr.co/edit/WDjoEK7bAVpKSJbAmB9D?p=preview
It uses the _.differenceWith() function of lodash, in order two save all array values, which are not contained by the two arrays.
var result = _.differenceWith(data, test, _.isEqual);
Now I have two problems:
1.) In our project we use an older Lodash Version where the function differenceWith is not implemented
2.) I only need to compare one value of the array. This currently compares the complete objects. I only need to compare the id property.
This will find the objects in arr1 that are not in arr2 based on the id attribute.
var arr1 = [ { "id": "1" }, { "id": "2" }, { "id": "3" } ];
var arr2 = [ { "id": "1" }, { "id": "2" } ];
var result = arr1.filter(o1 => arr2.filter(o2 => o2.id === o1.id).length === 0);
console.log(result);
Note that this example does not require lodash.
If you want to use a different comparison instead of id, you can change the o2.id === o1.id part to a different property.
Here is a more generic solution:
var arr1 = [ { "name": "a" }, { "name": "b" }, { "name": "c" } ];
var arr2 = [ { "name": "a" }, { "name": "c" } ];
function differenceWith(a1, a2, prop) {
return a1.filter(o1 => a2.filter(o2 => o2[prop] === o1[prop]).length === 0);
}
var result = differenceWith(arr1, arr2, 'name');
console.log(result);

Filtering array of objects with arrays based on nested value

I am trying to filter an array, based on some nested object. I prepared some Fiddle
Input array looks like this:
let arrayOfElements =
[
{
"name": "a",
"subElements":
[
{"surname": 1},
{"surname": 2}
]
},
{
"name": "b",
"subElements":
[
{"surname": 3},
{"surname": 1}
]
},
{
"name": "c",
"subElements":
[
{"surname": 2},
{"surname": 5}
]
}
];
I want the output for this case, to look like this:
let filteredArray =
[
{
"name": "a",
"subElements":
[
{"surname": 1}
]
},
{
"name": "b",
"subElements":
[
{"surname": 1}
]
}
];
I am using this formula to do that:
let filteredArray = arrayOfElements.filter((element) => element.subElements.some((subElement) => subElement.surname === 1));
Output is almost good, but it returns objects with all objects with surnames (better check that fiddle :D), instead of cutting them away. How can i improve the filtering ?
This way you can go as deep as you want in an array and filter elements at any level,
arrayOfElements.map((element) => {
return {...element, subElements: element.subElements.filter((subElement) => subElement.surname === 1)}
})
Spread operator will expand element and then filtered subElements will override the subElements in element.
After you call filter, you need to pipe the results to map, like this:
let filteredArray = arrayOfElements
.filter((element) =>
element.subElements.some((subElement) => subElement.surname === 1))
.map(element => {
let newElt = Object.assign({}, element); // copies element
return newElt.subElements.filter(subElement => subElement.surname === '1');
});
I am assuming here that you don't want to manipulate the original array. So, I am using Object.assign.
let filteredArray = arrayOfElements
.filter((element) =>
element.subElements.some((subElement) => subElement.surname == 1))
.map(element => {
return Object.assign({}, element, {subElements : element.subElements.filter(subElement => subElement.surname == 1)});
});
Just improved the answers above
let elements =
[
{
"name": "a",
"subElements":
[
{"surname": 1},
{"surname": 2}
]
},
{
"name": "b",
"subElements":
[
{"surname": 3},
{"surname": 1}
]
},
{
"name": "c",
"subElements":
[
{"surname": 2},
{"surname": 5}
]
}
];
var value = 1;
var filteredArray = elements
.filter(element => element.subElements
.some(subElement => subElement.surname === value)
)
.map(element => {
let n = Object.assign({}, element, {'subElements': element.subElements.filter(
subElement => subElement.surname === value
)})
return n;
})
console.log(filteredArray)
Try this solution:
data_filter = arrayOfElements.filter(function (element) {
return element.subElements.some( function (subElement) {
return subElement.surname === surname
});
});
You can make it generic as well:
Logic
Find all distinct surnames and loop over them
Filter every object to check if surnames exists. If yes, copy object using Object.assign and set subElements value to filtered list.
Create a temp array to hold all similar objects and push copied object to it.
Push this array to final array on every iteration of distinct surname.
Sample
let arrayOfElements=[{name:"a",subElements:[{surname:1},{surname:2}]},{name:"b",subElements:[{surname:3},{surname:1}]},{name:"c",subElements:[{surname:2},{surname:5}]}];
let distinct_surnames = [];
arrayOfElements.forEach(function(el) {
el.subElements.forEach(function(s) {
if (distinct_surnames.indexOf(s.surname) < 0) distinct_surnames.push(s.surname)
});
})
let result = [];
distinct_surnames.forEach(function(sn) {
let inter = [];
arrayOfElements.forEach(function(el) {
let f = el.subElements.filter(function(sub) {
return sub.surname === sn;
});
if (f.length > 0) {
let _tmp = Object.assign({}, el);
_tmp.subElements = f;
inter.push(_tmp);
}
});
result.push(inter);
})
console.log(result)
Note: Arrow functions are used to keep the reference of this. If you are not using this inside function, you can use normal functions as well.
function display_message() {
let arrayOfElements = [{
"name": "a",
"subElements": [{
"surname": 1
}, {
"surname": 2
}]
}, {
"name": "b",
"subElements": [{
"surname": 3
}, {
"surname": 1
}]
}, {
"name": "c",
"subElements": [{
"surname": 2
}, {
"surname": 5
}]
}];
// console.log(arrayOfElements);
var surname = 1;
let filteredArray = arrayOfElements.filter((element) => element.subElements.some((subElement) => subElement.surname === surname));
for(var data in filteredArray){
filteredArray[data].subElements = {"surname": surname};
}
console.log(filteredArray);
}
<input type="button" onclick="display_message();" value="click"/>
let filteredArray = arrayOfElements
.filter((element) =>
element.subElements.some((subElement) => subElement.surname === 1))
.map(element => {
let newElt = Object.assign({}, element); // copies element
newElt.subElements = newElt.subElements.filter(subElement => subElement.surName === '1');
return newElt;
});
is more correctly

Categories