sorting when keys are not present javascript using localecompare - javascript

I have json like below
[
{name:'aa',age:'1Y',address:'Alaska'},
{name:'cc',age:'4Years',address:'California'},
{name:'mm',address:'Texas'}
]
Whenever I sort with name and address it work but it will throw runtime error if I try to sort with age as it is missing on last entry.
This is my attempt
let obj=[
{name:'aa',age:'2y',address:'Alaska'},
{name:'cc',age:'4y',address:'California'},
{name:'bb',address:'Texas'}
]
let field='age'; //work for name and address;
let mode='string';
if(mode!='number'){
console.log (obj.sort((a, b) => a[field].toString().localeCompare(b[field].toString())));
}
else{
console.log(obj.sort((a, b) => a[field] -b[field]))
}
What is the best way to ignore the entry when keys are not present , do I need to have seperate loop to check keys before sorting . Entry with missing keys will be on the bottom.
Ps: Ages are never >10 years from the business logic and they can come in any format like 1,1Y so it is treated as string

Just make sure you either have the value of the object, or return an empty string.
The shortest code path would be
(a[field] || "")
Where you indicate that if a doesn't have the property, it will treat it as an empty string.
It won't cover for a being null though, so if that can happen, you have to check it more carefully still
let obj = [{
name: 'aa',
age: '25',
address: 'Alaska'
},
{
name: 'cc',
age: '25',
address: 'California'
},
{
name: 'bb',
address: 'Texas'
}
]
let field = 'age'; //work for name and address
console.log(obj.sort((a, b) => (a[field] || "").toString().localeCompare((b[field] || "").toString())));
Another way to do this, would be to simply compare the values (note, again, if a or b would be null, there might be a problem)
let obj = [{
name: 'aa',
age: 25,
address: 'Alaska'
},
{
name: 'cc',
age: 3,
address: 'California'
},
{
name: 'bb',
address: 'Texas'
}
]
function sortAndPrint( obj, field ) {
console.log(`Sorting by ${field}`);
console.log(obj.sort((a, b) => a[field] > b[field] ) );
}
sortAndPrint(obj, 'name');
sortAndPrint(obj, 'address');
sortAndPrint(obj, 'age');

Entry with missing keys will be on the bottom
Ask for the current value to decide what will be compared or what will be at the bottom.
let obj=[{name:'aa',age:'2y',address:'Alaska'},{name:'cc',age:'4y',address:'California'},{name:'bb',address:'Texas'}],
field = 'age';
console.log(obj.sort((a, b) => a[field] ? b[field] ? a[field].toString().localeCompare(b[field].toString()) : -1 : 1));
.as-console-wrapper { max-height: 100% !important; top: 0; }
If you want to compare the numbers within this string 10years or this string 5y, and so on, use a regex to compare the numbers.
let obj=[{name:'aa',age:'24y',address:'Alaska'},{name:'cc',age:'4years',address:'California'},{name:'bb',address:'Texas'}],
field = 'age';
console.log(obj.sort((a, b) => {
let evaluate = () => {
let aval = a[field].replace(/[^\d]/g, '').trim();
let bval = b[field].replace(/[^\d]/g, '').trim();
return aval !== '' && bval !== '' ? Number(aval) - Number(bval) : a[field].toString().localeCompare(b[field].toString());
};
return a[field] ? b[field] ? evaluate() : -1 : 1
}));
.as-console-wrapper { max-height: 100% !important; top: 0; }

let obj=[
{name:'aa',age:'25',address:'Alaska'},
{name:'cc',age:'25',address:'California'},
{name:'bb',address:'Texas'}
]
let field='age'; //work for name and address
const sortFunc = (a, b) => a[field].toString().localeCompare(b[field].toString())
// If you can discard entries without the field
console.log(obj.filter(e => field in e).sort(sortFunc))
// If you cannot
console.log(obj.filter(e => field in e).sort(sortFunc).concat(obj.filter(e => !(field in e))))

This happens because on the last element you don't have the property age when you're trying to access it with property toString.(it is null with the age key)

Just in case you're looking for an overkill solution :)
let obj=[
{name: 'aa', age: 25, address: 'Alaska'},
{name: 'cc', age: 24, address: 'California'},
{name: 'mm', address: 'Texas'}
];
let field = 'age';
console.log (obj.sort((a, b) => {
a = a[field];
b = b[field];
let defaultValue = '';
if (typeof a === 'number' || typeof b === 'number') {
defaultValue = 0;
}
a = a || defaultValue;
b = b || defaultValue;
if (typeof a === 'number') {
return a - b;
}
else {
return a.localeCompare(b);
}
}));
This automatically handles either strings or numbers, sorting correctly for each. If you want the no-age entries to sort higher rather than lower than everything else, just change what defaultValue is set to for numbers to a large number.

Related

Javascript : Add Key & Value at specific index in array of Object

I have array of Object like this.
let arr = [{name:"abc",age:26},{name:"xyz",age:23},{name:"pqr",age:10}]
let newVal = arr.map(function(el) {
if(el.age > 25){
var o = Object.assign({}, el);
o.gender = 'male';
return o;
}
})
console.log("New Val : " , newVal)
I would like to add {gender:'male'} to object where age is > 25
It says undefined to other objects.
Any help would be great.
Thank You.
You need to return the value if the object doesn't match the condition. Since you haven't retrned anything from from inside map if the condition is not fulfilled, you get undefined for the other objects
let arr = [{
name: "abc",
age: 26
}, {
name: "xyz",
age: 23
}, {
name: "pqr",
age: 10
}]
let newVal = arr.map(function(el) {
if (el.age > 25) {
var o = Object.assign({}, el);
o.gender = 'male';
return o;
}
return el; // return value here
})
console.log("New Val : ", newVal)
issue with your code is already solved in other answer by Shubham, ie when if clause is not executed you are not returning anything.
but i think forEach might be cleaner here
if you want to keep the original array you can copy it using copyArr = [...arr]
let arr = [{name:"abc",age:26},{name:"xyz",age:23},{name:"pqr",age:10}]
arr.forEach(function(el) {
if(el.age > 25)
el.gender = 'male';
})
console.log("New Val : " , arr)
It's missing the return statement when the condition is false.
You can do this in one line using an arrow function as follow:
let arr = [{name:"abc",age:26},{name:"xyz",age:23},{name:"pqr",age:10}],
newVal = arr.map((el) => Object.assign({}, el, el.age > 25 ? {gender: "male"} : {}));
console.log("New Val:", newVal);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You get a new array with map. Inside, you need to take either an copy of the object with a new property or the original object.
let array = [{ name: "abc", age: 26 }, { name: "xyz", age: 23 }, { name: "pqr", age: 10 }],
result = array.map(object => object.age > 25
? { ... object, gender: 'male' }
: object
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Filter data based on Object with filter values

I have in my app some filters. Whenever I click search, the data should be filtered based on the values of the search. I don't know if there's a better way to do what I'm trying to do in a cooler way or fancy way. The values of the search are being stored the following way:
FORM_VALUES =
{
title: '',
name: 'John',
Age: 56
}
If the value of the objects are empty '' then I shouldn't considered them when i filter the array of objects.
My array of Objects looks like this:
[
{
title:'CEO',
name: 'John',
Age: 56
},
{
title: null,
name: 'George',
Age: 56
},
]
In this example, it should return the first element of the array.
My code:
data.filter(d => {
if ((d.title === FORM_VALUES.title || FORM_VALUES.title === '')
&& (d.name === FORM_VALUES.name || FORM_VALUES.name === '')
&& (d.age === FORM_VALUES.age || FORM_VALUES.age === '')) {
return d;
}
});
Use filter to get the people with the values, and use Object.keys and delete to remove any empty entries, and every with Object.entries to find the actual objects:
const FORM_VALUES = {
title: '',
name: 'John',
Age: 56
}
const data = [{
title: 'CEO',
name: 'John',
Age: 56
},
{
title: null,
name: 'George',
Age: 56
}
];
let searchObj = { ...FORM_VALUES };
Object.keys(searchObj).forEach(key => searchObj[key] == "" ? delete searchObj[key] : key);
const found = data.filter(d => Object.entries(searchObj).every(([k, v]) => d[k] == v));
console.log(found);
Get an array of key/value pairs from the FORM_VALUES object with Object.entries(). Iterate the array of pairs with Array.every() for each pair, return true if the value '' (empty string) or value of the filter matches with that of the current object.
const FORM_VALUES = {"title":"","name":"John","Age":56}
const data = [{"title":"CEO","name":"John","Age":56},{"title":null,"name":"George","Age":56}]
const result = data.filter(o =>
Object.entries(FORM_VALUES)
.every(([k, v]) => v === '' || o[k] === v)
)
console.log(result)

Javascript: Remove duplicates in an array by compare function

I have an array with objects like this:
const array = [
{name:'obj1', address: 987, id: '123', location: 'zyx' },
{name:'obj2', address: 654, id: '456', location: 'wvu'},
{name:'obj3', address: 321, id: '123', location: 'zyx'}
];
and I want to remove the duplicates with a function to compare them:
const compareObjects = (a, b) => {
return a.id === b.id && a.location === b.location;
}
The function only compares the relevant properties of the objects.
How can I remove the duplicates from the array with this function?
Edit: To clarify, I want to use a function to compare some properties of the object and not the whole object.
You could reduce the array by checking the object of the temporary result set.
const
array = [{ name:'obj1', address: 987, id: '123', location: 'zyx' }, { name:'obj2', address: 654, id: '456', location: 'wvu' }, { name:'obj3', address: 321, id: '123', location: 'zyx' }],
compareObjects = (a, b) => a.id === b.id && a.location === b.location,
result = array.reduce((r, o) => {
if (!r.some(compareObjects.bind(null, o))) {
r.push(o);
}
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
The obvious solution is to compare each element against each other element (except for itself):
const result = array.filter((el, i) => !arr.some((el2, i2) => i < i2 && compareObjects(el, el2));
However that is O(n²) which will get very slow for large datasets, in that case hashtables help you:
const dupes = new Set;
const key = el => el.id + "|" + el.location;
const result = array.filter(it => !dupes.has(key(el)) && dupes.add(key(el)));
That is O(n) (aka super fast, but consumes more memory).
You can try a function like below
function unique(array , compareObjects){
array.sort(compareObjects);
for(var i = 1; i < array.length; ){
if( compareObjects(array[i-1], array[i]) === 0){
array.splice(i, 1);
} else {
i++;
}
}
return array;
}

Sort a list by property and add an object before each first letter changes in JavaScript

So I am trying to make a UI like this:
And I have an array of users
[{name: 'Julia'}, {name: 'Ismeh'}, {name: 'Alison'}, {name: 'Andrea'}, {name: 'Betty'}]
What I am trying to do is to sort the array by first letter of the name property, and add a header object before each. For example in the picture, you can see the letter A, B, I, and J as the headers.
For now, I got it working like this:
let final = []
// sort by first letter
const sortedUsers = state.test_list.sort((a, b) => a.name.localeCompare(b.name))
for (let x = 0; x < sortedUsers.length; x++) {
const user = sortedUsers[x].name
if (user.charAt(0) === 'A') {
const checkIfExists = final.findIndex((f) => f.header === 'A')
// add the header A if it doesn't exist
if (checkIfExists < 0) final.push({header: 'A'})
}
else if (user.charAt(0) === 'B') {
const checkIfExists = final.findIndex((f) => f.header === 'B')
// add the header B if it doesn't exist
if (checkIfExists < 0) final.push({header: 'B'})
}
// else if up to the letter Z
final.push(user)
}
and if I log the final array, I get:
which is correct.
My concern is that the code is very long, and I have no idea if it can be optimized or make the code smaller.
Is there any other option to do something like this? Any help would be much appreciated.
Why don't you create a collection of names, which is grouped by the first letter? You can then loop on it, and create your list. Use Array#reduce to create the grouped collection.
And then use Object#keys to iterate over the grouped collection and render your results:
let data = [{
name: 'Julia'
}, {
name: 'Ismeh'
}, {
name: 'Alison'
}, {
name: 'Andrea'
}, {
name: 'Betty'
}];
let combined = data.reduce((result, item) => {
let letter = item.name[0].toUpperCase();
if (!result[letter]) {
result[letter] = [];
}
result[letter].push(item);
return result;
}, {});
console.log(combined);
// Iterate over the result
Object.keys(combined).forEach(key => {
// key will be the first letter of the user names and
// combined[key] will be an array of user objects
console.log(key, combined[key]);
});
One thing still to do is to sort the user arrays by user name, which you can do easily using Array#sort.
Simple enough, try sorting them and then using .reduce:
const unsortedPeople = [{name: 'Julia'}, {name: 'Ismeh'}, {name: 'Alison'}, {name: 'Andrea'}, {name: 'Betty'}];
const sortedUsers = unsortedPeople.sort((a, b) => a.name.localeCompare(b.name))
const final = sortedUsers.reduce((finalSoFar, user) => {
const thisUserFirstChar = user.name[0];
if (finalSoFar.length === 0) addHeader();
else {
const lastUserFirstChar = finalSoFar[finalSoFar.length - 1].name[0];
if (lastUserFirstChar !== thisUserFirstChar) addHeader();
}
finalSoFar.push(user);
return finalSoFar;
function addHeader() {
finalSoFar.push({ header: thisUserFirstChar });
}
}, []);
console.log(final);
Why don't you just keep track of the current abbreviation as you loop. Then you can add a head when it changes:
var users = [{name: 'Julia'}, {name: 'Ismeh'}, {name: 'Alison'}, {name: 'Andrea'}, {name: 'Betty'}]
const sortedUsers = users.sort((a, b) => a.name.localeCompare(b.name))
var currentHeader
let final = sortedUsers.reduce((a, user) => {
if (currentHeader !== user.name[0]) {
currentHeader = user.name[0]
a.push({header: currentHeader})
}
a.push(user)
return a
},[])
console.log(final)
Here's one way to do it:
const users = [{name: 'Julia'}, {name: 'Ismeh'}, {name: 'Alison'}, {name: 'Andrea'}, {name: 'Betty'}];
let lastIndex;
let result = [];
users.sort((a, b) => {
return a.name > b.name;
}).forEach((user) => {
const index = user.name.charAt(0);
if (index !== lastIndex) {
result.push({
header: index
});
}
lastIndex = index;
result.push(user.name);
}, []);
console.log(result);
You can use _.orderBy(collection, [iteratees=[_.identity]], [orders]) and _.groupBy(collection, [iteratee=_.identity]) method of lodash.
This orderBy is like _.sortBy except that it allows specifying the sort orders of the iteratees to sort by. If orders is unspecified, all values are sorted in ascending order. Otherwise, specify an order of "desc" for descending or "asc" for ascending sort order of corresponding values.
groupBy will creates an object composed of keys generated from the results of running each element of collection thru iteratee. The order of grouped values is determined by the order they occur in collection. The corresponding value of each key is an array of elements responsible for generating the key. The iteratee is invoked with one argument: (value).
example
// The `_.property` iteratee shorthand.
_.groupBy(['one', 'two', 'three'], 'length');
// => { '3': ['one', 'two'], '5': ['three'] }
// Sort by `user` in ascending order and by `age` in descending order.
_.orderBy(users, ['user', 'age'], ['asc', 'desc']);
With lodash
let myArr = [{
name: 'Julia'
}, {
name: 'Ismeh'
}, {
name: 'Andrea'
}, {
name: 'Alison'
}, {
name: 'Betty'
}];
myArr = _.orderBy(myArr, ['name'], ['asc']);
let r = _.groupBy(myArr, o => {
return o.name.charAt(0).toUpperCase();
})
console.log(r);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
IN ES5
var arr = [{
name: 'Julia'
}, {
name: 'Ismeh'
}, {
name: 'Andrea'
}, {
name: 'Alison'
}, {
name: 'Betty'
}],
fChar = '';
arr = arr.sort(function(a, b) {
a = a.name.toUpperCase(); // ignore upper and lowercase
b = b.name.toUpperCase(); // ignore upper and lowercase
return a < b ? -1 : (a > b ? 1 : 0);
}).reduce(function(r, o) {
fChar = o.name.charAt(0).toUpperCase();
if (!r[fChar]) {
r[fChar] = [];
}
r[fChar].push({
name: o.name
});
return r;
}, {});
console.log(arr);
IN ES6
const arr = [{
name: 'Julia'
}, {
name: 'Ismeh'
}, {
name: 'Andrea'
}, {
name: 'Alison'
}, {
name: 'Betty'
}];
let result = arr.sort((a, b) => {
a = a.name.toUpperCase(); // ignore upper and lowercase
b = b.name.toUpperCase(); // ignore upper and lowercase
return a < b ? -1 : (a > b ? 1 : 0);
}).reduce((r, o) => {
let fChar = o.name.charAt(0).toUpperCase();
if (!r[fChar]) {
r[fChar] = [];
}
r[fChar].push({
name: o.name
});
return r;
}, {});
console.log(result);

How to get Property value from a Javascript object

I have a JavaScript object.
var obj = { Id: "100", Name: "John", Address: {Id:1,Name:"Bangalore"} }
var dataToRetrieve= "Name";
function GetPropertyValue(object,dataToRetrieve){
return obj[dataToRetrieve]
}
var retval = GetPropertyValue(obj,dataToRetrieve)
This works fine. But if I try to get the value of property value of "Address.Name" ,
Like : var dataToRetrieve = "Address.Name";
it shows undefined.
Note : The property variable is set by user from HTML And it can be changed according to user requirement(which property value he wants).
What I want to achieve :
1) If dataToRetrieve = "Name" , it should give me "John",
2) If dataToRetrieve = "Id" , it should give me "100",
3) If dataToRetrieve = "Address.Name" , it should give me "Bangalore",
4) If dataToRetrieve = "Address.Id" , it should give me 1
Plunkr Here : PLUNKR
Use reduce() method
var obj = {
Id: "100",
Name: "John",
Address: {
Id: 1,
Name: "Bangalore"
}
}
function GetPropertyValue(obj1, dataToRetrieve) {
return dataToRetrieve
.split('.') // split string based on `.`
.reduce(function(o, k) {
return o && o[k]; // get inner property if `o` is defined else get `o` and return
}, obj1) // set initial value as object
}
console.log(
GetPropertyValue(obj, "Name"),
GetPropertyValue(obj, "Id"),
GetPropertyValue(obj, "Address.Name"),
GetPropertyValue(obj, "Address.Id"),
GetPropertyValue(obj, "Address.Idsd"),
GetPropertyValue(obj, "Addre.Idsd")
)
For older browser check polyfill option of reduce method.
Use following function:
var obj = { Id: "100", Name: "John",
Address: [{ Id:1, Name:"Bangalore" }, { Id:2, Name: "Mysore" } ] };
function GetPropertyValue(object, dataToRetrieve) {
dataToRetrieve.split('.').forEach(function(token) {
if (object) object = object[token];
});
return object;
}
console.log(
GetPropertyValue(obj, "Address.0.Name"),
GetPropertyValue(obj, "Address.1.Id"),
GetPropertyValue(obj, "Name"),
GetPropertyValue(obj, "Id"),
GetPropertyValue(obj, "Unknown"),
GetPropertyValue(obj, "Some.Unknown.Property")
);
function GetPropertyValue(object,dataToRetrieve){
var valueArray = dataToRetrieve.split(".");
if (valueArray.length <= 1) {
return object[valueArray];
} else {
var res;
function browseObj(obj, valueArray, i) {
if (i == valueArray.length)
res = obj;
else
browseObj(obj[valueArray[i]], valueArray, i+1);
}
browseObj(object, valueArray, 0);
return res;
}
}
I had written a standard reusable Object method to access nested properties dynamically. It's like
Object.prototype.getNestedValue = function(...a) {
return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};
It will take dynamic arguments for the nested properties. If they are string type they are object properties if number type then they are array indices. Once you have this, your job becomes very easy. Let's see..
Object.prototype.getNestedValue = function(...a) {
return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};
var props = ["Address","Name"],
obj = { Id: "100", Name: "John", Address: {Id:1,Name:"Bangalore"} },
val = obj.getNestedValue(...props);
console.log(val);
// or you can of course do statically like
val = obj.getNestedValue("Address","Name");
console.log(val);
You can see getNestedValue() and it's twin setNestedValue() working at https://stackoverflow.com/a/37331868/4543207

Categories