Scalable table sorting approach - javascript

I want to sort each column in react. I can do it, but forcing the code, for example if I have this array and display it in a html table, I want when I click id it sort in ascending order, when I click name it sort in ascending order and when click again it sort descending order, i want the same with name and age. And at the same time i want an arrow that if that column is in ascendig is looking up otherwise is looking down.
const USERS = [
{ id: 1, name: "Andy", age: 32 },
{ id: 2, name: "Bob", age: 30 },
{ id: 3, name: "Tom Hulk", age: 40 },
{ id: 4, name: "Tom Hank", age: 50 },
{ id: 5, name: "Audra", age: 30 },
{ id: 6, name: "Anna", age: 68 },
{ id: 7, name: "Tom", age: 34 },
{ id: 8, name: "Tom Riddle", age: 28 },
{ id: 9, name: "Bolo", age: 23 },
];
The way that i can do it is like this, but it's ugly and not practical. And also, when i change the sort state for the arrow, it updates for all the arrow not for the arrow of the column that i clicked
const sortBy = (k) => {
if (k === "id") {
if (sort) {
const res = USERS.sort((a, b) => (a.id > b.id ? 1 : -1));
setDataFilter(res);
setSort(!sort);
console.log(res);
} else {
const res = USERS.sort((a, b) => (a.id < b.id ? 1 : -1));
setDataFilter(res);
setSort(!sort);
console.log(res);
}
} else if (k === "age") {
if (sort) {
const res = USERS.sort((a, b) => (a.age > b.age ? 1 : -1));
setDataFilter(res);
setSort(!sort);
console.log(res);
} else {
const res = USERS.sort((a, b) => (a.age < b.agek ? 1 : -1));
setDataFilter(res);
setSort(!sort);
console.log(res);
}
} else if (k === "name") {
if (sort) {
const res = USERS.sort((a, b) => a.name.localeCompare(b.name));
setDataFilter(res);
setSort(!sort);
console.log(res);
} else {
const res = USERS.sort((a, b) => b.name.localeCompare(a.name));
setDataFilter(res);
setSort(!sort);
console.log(res);
}
} else {
console.log("hmm");
}
};

Try the below code. It checks the type of the sorting key value and choose the sorting method accordingly. I re-used your logic to make it simple for you.
const USERS = [
{ id: 1, name: "Andy", age: 32 },
{ id: 2, name: "Bob", age: 30 },
{ id: 3, name: "Tom Hulk", age: 40 },
{ id: 4, name: "Tom Hank", age: 50 },
{ id: 5, name: "Audra", age: 30 },
{ id: 6, name: "Anna", age: 68 },
{ id: 7, name: "Tom", age: 34 },
{ id: 8, name: "Tom Riddle", age: 28 },
{ id: 9, name: "Bolo", age: 23 },
];
const sortBy = (k, sort) => {
var res;
if( USERS[0] && (typeof USERS[0][k] == 'string')) {
res = sort ? USERS.sort((a, b) => a[k].localeCompare(b[k])) : USERS.sort((a, b) => b[k].localeCompare(a[k]));
} else {
const f = sort ? 1 : -1;
res = USERS.sort((a, b) => (a[k] > b[k] ? f : -f));
}
// setDataFilter(res);
// setSort(!sort);
console.log(res);
}
sortBy('name', 1);
sortBy('age', 1);
sortBy('name', 0);
sortBy('age', 0);

You may want to have sorting callbacks within a mapping object (by property name or by property type).
Also, do not forget to leverage useMemo()/ useCallback() hooks to boost sorting performance through memoizing the sorting output (which may be beneficial for large number of items):
const users = [
{ id: 1, name: "Andy", age: 32 },
{ id: 2, name: "Bob", age: 30 },
{ id: 3, name: "Tom Hulk", age: 40 },
{ id: 4, name: "Tom Hank", age: 50 },
{ id: 5, name: "Audra", age: 30 },
{ id: 6, name: "Anna", age: 68 },
{ id: 7, name: "Tom", age: 34 },
{ id: 8, name: "Tom Riddle", age: 28 },
{ id: 9, name: "Bolo", age: 23 },
],
sortDirection = {
'asc': 1,
'desc': -1
},
numericSorter = propName =>
sortOrder =>
({[propName]:a}, {[propName]:b}) =>
(a-b)*sortDirection[sortOrder],
stringicSorter = propName =>
sortOrder =>
({[propName]:a}, {[propName]:b}) =>
a.localeCompare(b)*sortDirection[sortOrder],
sortMap = {
id: numericSorter('id'),
name: stringicSorter('name'),
age: numericSorter('age')
},
sortUsers = (users, byProp, sortOrder) =>
users.sort(sortMap[byProp](sortOrder))
console.log(sortUsers(users, 'name', 'desc'))
console.log(sortUsers(users, 'age', 'asc'))
.as-console-wrapper{min-height: 100%;}

Related

Find the difference between two complex objects containing array of objects

let baseObj = {
place: {city: 'Bangalore', pin: 123456},
office: [
{ name: 'Tom', age: 22, salutation: { title: 'Mr'}},
{ name: 'John', age: 31, salutation: { title: 'Mr'}}
]
}
let updatedObj = {
place: {city: 'Bangalore', pin: 99999},
office: [
{ name: 'Tom', age: 22, salutation: { title: 'Mr'}},
{ name: 'Peter', age: 16, salutation: { title: 'Mr'}},
{ name: 'John', age: 31, salutation: { title: 'Mr'}}
]
}
expected result = {
place: {city: 'Bangalore', pin: 99999},
office: [
{ name: 'Peter', age: 16, salutation: { title: 'Mr'}}
]
}
Note: comparison can be done by finding the properties of object and values but no comparison should be done hardcoding the properties
tried comparing the object but when we have an array of object i.e office, comparing with index(i.e 0,1) doesn't help as the array might not be sorted so couldn't proceed much
have tried the below code but it fails to get the desired output if the objects in an array are in different sequence as compared to the other array
ex. office1: [
{ name: 'Tom', age: 22, salutation: { title: 'Mr'}},
{ name: 'John', age: 31, salutation: { title: 'Mr'}}
]
office2: [
{ name: 'Tom', age: 22, salutation: { title: 'Mr'}},
{ name: 'Peter', age: 16, salutation: { title: 'Mr'}},
{ name: 'John', age: 31, salutation: { title: 'Mr'}}
]
function findDiff(obj1, obj2) {
var diffObj = Array.isArray(obj2) ? [] : {}
Object.getOwnPropertyNames(obj2).forEach(function(prop) {
if(prop !=='lenght' ){
if (typeof obj2[prop] === 'object') {
diffObj[prop] = obj1[prop]== undefined? obj2[prop]: findDiff(obj1[prop], obj2[prop])
if (Array.isArray(diffObj[prop]) && Object.getOwnPropertyNames(diffObj[prop]).length === 1 || Object.getOwnPropertyNames(diffObj[prop]).length === 0) {
delete diffObj[prop]
}
}} else if(prop !=='lenght') {
if(obj1[prop] !== obj2[prop]){
diffObj[prop] = obj2[prop]
}
}
});
return diffObj
}
This compare function seems to achieve exactly what you want :
const baseObj = {"grade":"A","queue":"1","myCollections":{"myCollection":[{"commonCollection":[{"winterCollection":[{"name":"ICE","isChilled":"true"}]}]}]},"remarks":{"remark":[{"name":"GOOD","category":"A","text":{"value":"Very Good"},"indicator":"good"}]}}
const updatedObj = {"grade":"A","queue":"1","myCollections":{"myCollection":[{"commonCollection":[{"winterCollection":[{"name":"ICE","isChilled":"true"},{"code":"SNOW","isChilled":"true"}]}]}]},"remarks":{"remark":[{"name":"GOOD","category":"A","text":{"value":"Very Good"},"indicator":"good"},{"name":"BEST","text":{"value":"Test"},"category":"O","indicator":"outstanding"}]}}
const compare = (a, b, allObj = true) => {
if (typeof a !== 'object') return a === b ? null : b
if (Array.isArray(a)) {
const arr = []
b.forEach(e => {
if (a.every(el => compare(el, e, true) !== null))
arr.push(e)
});
return arr.length === 0 ? null : arr
} else {
const keys = Object.keys(b) // assuming a and b have the same properties
const obj = allObj ? b : {}
let changed = false
keys.map(key => {
const compared = compare(a[key], b[key], true)
if (compared) {
obj[key] = compared
changed = true
}
})
return changed ? obj : null
}
}
const expectedResult = {"grade":"A","queue":"1","myCollections":{"myCollection":[{"commonCollection":[{"winterCollection":[{"code":"SNOW","isChilled":"true"}]}]}]},"remarks":{"remark":[{"name":"BEST","text":{"value":"Test"},"category":"O","indicator":"outstanding"}]}}
const result = compare(baseObj, updatedObj)
console.log(result)
console.log(expectedResult)
console.log(JSON.stringify(result) === JSON.stringify(expectedResult))
PS: I compared each pair but it is O(n^2). The best way is if you had an id property on every of array children.

How to loop through an object and then use array methods on its keys?

I see that all the loops for objects returns the key as string and the value, but I want to operate on the keys of the object itself. If I have this object:
const data = {
person1: [
{ id: 1, name: Mike, age: 24 },
{ id: 2, name: Bob, age: 31 }
],
person2: [
{ id: 3, name: Christin, age: 21 },
{ id: 4, name: Michelle, age: 33 }
],
}
const removePersonById = (id) => {
// Check which person the id belongs to and remove that person
const persons = Object.keys(data).map(person => ...)
}
I wanted to loop through data and run .includes on each person in order to remove them by the id, but I am at a loss on how to do that.
You can loop through all keys and delete that the person you want by id using the filter() method
const removePersonById = (id) => {
var all = Object.keys(data);
for(let person of all){
data[person] = data[person].filter(a => a.id!=id);
}
}
You could get the values and find the index. Then splice.
const
removePersonById = id => {
Object.values(data).forEach(a => {
const index = a.findIndex(o => o.id === id);
if (index !== 0) a.splice(index, 1);
});
};
You could use .some()
const data = {
person1: [
{ id: 1, name: "Mike", age: 24 },
{ id: 2, name: "Bob", age: 31 }
],
person2: [
{ id: 3, name: "Christin", age: 21 },
{ id: 4, name: "Michelle", age: 33 }
],
}
const removePersonById = (id) => {
// Check which person the id belongs to and remove that person
Object.keys(data).map(person => {
if (data[person].some(p => p.id === id)) delete data[person];
})
}
removePersonById(3)
console.log(data)
Use Object.entries() so you can iterate over the keys and values together. Then you can test the value to see if the id is found.
Then use the delete operator to remove that key from the object.
const removePersonById = id => {
delete data[id];
};
const data = {
person1: [
{ id: 1, name: "Mike", age: 24 },
{ id: 2, name: "Bob", age: 31 }
],
person2: [
{ id: 3, name: "Christin", age: 21 },
{ id: 4, name: "Michelle", age: 33 }
],
};
const removePersonById = (id) =>
Object.entries(data).forEach(([key, value]) => {
if (value.some(({id: personid}) => personid == id)) {
delete data[key];
}
});
removePersonById(3);
console.log(data);
let data = {
person1: [
{ id: 1, name: "Mike", age: 24 },
{ id: 2, name: "Bob", age: 31 }
],
person2: [
{ id: 3, name: "Christin", age: 21 },
{ id: 4, name: "Michelle", age: 33 }
],
}
const removePersonById = (id) => {
// Check which person the id belongs to and remove that person
data = Object.keys(data).reduce((acc,key) => {
if(data[key].some(person=>person.id===id)) return acc
acc[key]= data[key]
return acc
}, {})
console.log(`Remove person with ID ${id}: `,data)
}
removePersonById(1)

Sort() with non-existent values

I know that undefined values should be sent to the end of the result, but what about non-existent keys? (Shouldn't be the same?) It seems sort doesn't work in those cases:
const names = [
{
name: "John",
age: 27
},{
name: "Charles",
},{
name: "Ellen",
age: 30
},{
name: "Mario",
},
{
name: "Emanuelle",
age: 18
}
]
names.sort(function (a, b) {
if (a.age > b.age) return 1;
if (a.age < b.age) return -1;
return 0;
})
console.log(names) // Sort not working, prints original order
Ideally I want to modify the "names" array and not create/reassign more variables.
Your default sort solution is set to keep the item in its current position --> return 0. You could provide another conditional that captures undefined and return -1
const
names = [{ name: "John", age: 27 }, { name: "Charles" }, { name: "Ellen", age: 30 }, { name: "Mario" }, { name: "Emanuelle", age: 18 }];
names.sort(function (a, b) {
if(b.age === undefined) return -1;
if (a.age > b.age) return 1;
if (a.age < b.age) return -1;
return 0;
})
console.log(names) // Sort not working, prints original order
So check if the age is defined. If not set it to a large number to force it to the end.
const names = [
{
name: "John",
age: 27
},{
name: "Charles",
},{
name: "Ellen",
age: 30
},{
name: "Mario",
},
{
name: "Emanuelle",
age: 18
}
]
function getAge (obj) {
return obj.age === undefined ? Number.MAX_VALUE : obj.age;
}
names.sort(function (a, b) {
// if (a.age === b.age) {
// return a.name.localeCompare(b.name);
// }
return getAge(a) - getAge(b);
})
console.log(names);
You could check for the property and if not exist, move this objects to bottom. for the rest sort by age.
const
names = [{ name: "John", age: 27 }, { name: "Charles" }, { name: "Ellen", age: 30 }, { name: "Mario" }, { name: "Emanuelle", age: 18 }];
names.sort((a, b) =>
('age' in b) - ('age' in a) || // sort object without `age` to bottom
a.age - b.age // sort by `age`
);
console.log(names);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Sort not work properly because any number operation with undefined are NaN I.E: 1 - undefined = NaN.
For this sort, you can use destructuring + defaults, try this:
const names = [
{
name: "John",
age: 27
},{
name: "Charles",
},{
name: "Ellen",
age: 30
},{
name: "Mario",
},
{
name: "Emanuelle",
age: 18
}
]
names.sort(
({ age: agea = Number.MAX_VALUE }, { age: ageb = Number.MAX_VALUE }) =>
agea - ageb
);
console.log(names)

Angular/JS/Typescript- Recursively access properties in an object

Suppose I got an object defined as such:
const me = {
id: 1,
name: 'James',
age: 40,
family: {
mother: {
id: 101,
name: 'Bea',
age: 66
},
father: {
id: 102,
name: 'Martin',
age: 69
},
children: [
{
id: 11,
name: 'Tom',
age: 18,
},
{
id: 12,
name: 'Nancy',
age: 13,
},
],
},
}
How does one easily access a value by just giving an array of strings of the chained properties' names?
For example, calling:
search(me, ['family', 'father', 'age'])
which would be the same as:
me['family']['father']['age']
would return 69.
PS:
What about having search(me, ['family', 'children', 'name']) return ['Tom', 'Nancy']?
PSS:
Or even search(me, ['family', 'children', ['name', 'age']]) returning
[
{
name: 'Tom',
age: 18
},
{
name: 'Nancy',
age: 13
}
]
EDIT:
I went checking out lodash/deepdash libraries, but couldn't really figure it out by myself.
You can do that with this simple recursive function which takes an array as the query:
const me = {
id: 1,
name: 'James',
age: 40,
family: {
mother: {
id: 101,
name: 'Bea',
age: 66
},
father: {
id: 102,
name: 'Martin',
age: 69
},
children: [
{
id: 11,
name: 'Tom',
age: 18,
},
{
id: 12,
name: 'Nancy',
age: 13,
},
],
},
}
function search(obj, [first, ...rest]) {
return rest.length ? search(obj[first], rest) : obj[first];
}
const result = search(me, ['family', 'father', 'age']);
console.log(result);
Though it's bit lengthy, you could try this for all your combinations.
const me={id:1,name:'James',age:40,family:{mother:{id:101,name:'Bea',age:66},father:{id:102,name:'Martin',age:69},children:[{id:11,name:'Tom',age:18,},{id:12,name:'Nancy',age:13,},],},}
function search(data, searchPattern) {
const keys = [...searchPattern];
//picking last key and it's corresponding value
const lastKey = keys.pop();
const resultInst = keys.reduce((acc,key)=>{
return acc[key];
}, data);
// if it's array iterating it further to construct the response
if (Array.isArray(resultInst)) {
return resultInst.map(inst => {
if (Array.isArray(lastKey)) {
return lastKey.reduce((accInner,key) => {
accInner[key] = inst[key];
return accInner;
}, {});
} else {
return inst[lastKey];
}
});
} else {
// else just returning property's value
return resultInst[lastKey];
}
}
console.log(search(me, ['family', 'father', 'age']))
console.log(search(me, ['family', 'children', 'name']))
console.log(search(me, ['family', 'children', ['name', 'age']]))
Some interesting requirements you have there! Here is an all-in-one answer using object-scan and lodash
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/lodash#4.17.21"></script>
<script type="module">
import objectScan from 'https://cdn.jsdelivr.net/npm/object-scan#18.1.2/lib/index.min.js';
const myData = { id: 1, name: 'James', age: 40, family: { mother: { id: 101, name: 'Bea', age: 66 }, father: { id: 102, name: 'Martin', age: 69 }, children: [{ id: 11, name: 'Tom', age: 18 }, { id: 12, name: 'Nancy', age: 13 }] } };
const search = (data, needle) => objectScan([needle], {
reverse: false,
rtn: ['key', 'value'],
useArraySelector: false,
afterFn: (state) => {
if (state.result.length === 0) {
state.result = undefined;
} else if (needle.includes('{')) {
const prefixLength = lodash
.zip(...state.result.map(([k]) => k))
.findIndex((e) => new Set(e).size > 1);
const result = [];
state.result.forEach(([k, v]) => lodash.set(result, k.slice(prefixLength), v));
state.result = result;
} else if (
state.result.length === 1
&& state.result.every(([k]) => k.every((p) => typeof p === 'string'))
) {
state.result = state.result[0][1];
} else {
state.result = state.result.map(([k, v]) => v);
}
}
})(data);
console.log(search(myData, 'family.father.age'));
// => 69
console.log(search(myData, 'family.children.name'));
// => [ 'Tom', 'Nancy' ]
console.log(search(myData, 'family.children.{age,name}'));
// => [ { name: 'Tom', age: 18 }, { name: 'Nancy', age: 13 } ]
console.log(search(myData, 'family.father.unknown'));
// => undefined
</script>
Disclaimer: I'm the author of object-scan

How to check for equal property values in 2 Array of Objects in JavaScript?

Let's say I have 2 arrays:
const array_1 = [ { id: 1, sex: 'M' }, { id: 2, sex: 'F' }, { id: 3, sex: 'M' } ]
const array_2 = [ { id: 12, age:1 }, { id: 22, age: 4 }, { id: 3, age: 2 } ]
And I want to check if there's a value in array_1 that is also in array_2. I can do something like:
array_1.forEach((el_1) => {
array_2.forEach((el_2, i) => {
if(el_1.id === el_2.id) alert(i);
})
})
Or using ECMAScript 2015 findIndex method:
array_1.forEach((el_1) => {
const i = array_2.findIndex(x => x.id === el_1.id)
if(i >= 0) alert(i);
})
If you want to check the output: https://jsfiddle.net/v7bjnpa7/1/
Normally that would work, but is there a cleaner way to do this? By clean I mean a better way that would not affect the performance? Any help would be much appreciated.
if(array_1.find( e1 => array_2.find( e2 => e1.id == e2.id))) {
alert('found');
}
You Can user Underscore js [http://underscorejs.org/][1]
There you will have methods to compare, intersection and lot more.
try it
or
const array_1 = [ { id: 1, sex: 'M' }, { id: 2, sex: 'F' }, { id: 3, sex: 'M' } ];
const array_2 = [ { id: 12, age:1 }, { id: 22, age: 4 }, { id: 3, age: 2 } ];
array_1.forEach(function(item)
{
array_2.forEach(function (item2) {
if(item.id==item2.id)
{
console.log(item,item2)
}
});
});

Categories

Resources