JavaScript multi-level sorting with multi dimension array - javascript

I have a JSON object like below:
[
{
"name": "Robert",
"age":32,
"country": "UK"
},
{
"name": "Prasad",
"age":28,
"country": "India"
},
{
"name": "Benny",
"age":45,
"country": "USA"
},
{
"name": "Robin",
"age":34,
"country": "UK"
},
{
"name": "Bob",
"age":20,
"country": "India"
}
]
I have applied the array sorting for "name" column alone. I want to apply sort for “name” column first and then “age”.
This is how i sort the array by name:
var sort_by = function(field, reverse, primer){
var key = primer ?
function(x) {return primer(x[field])} :
function(x) {return x[field]};
reverse = [-1, 1][+!!reverse];
return function (a, b) {
return a = key(a), b = key(b), reverse * ((a > b) - (b > a));
}
}
Call the sort function:
arrayToSort.sort(
sort_by( “name”, true, function(a){
return a.toUpperCase();
}) );
How can I get the array sorted like below?
[{
"name": "Bob",
"age":20,
"country": "India"
},
{
"name": "Benny",
"age":45,
"country": "USA"
},
{
"name": "Prasad",
"age":28,
"country": "India"
},
{
"name": "Robert",
"age":32,
"country": "UK"
},
{
"name": "Robin",
"age":34,
"country": "UK"
}]

I think what you are looking for is a way to "chain" sort_by(..) calls so as to be able to operate on more than one field.
Below is a slightly modified version of your code. Its pretty much self-explanatory.
arrayToSort = [ ...];
var sort_by = function(field, reverse, primer){
var key = primer ?
function(x) {return primer(x[field]); }:
function(x) {return x[field] };
reverse = [-1, 1][+!!reverse];
return function (a, b) {
a = key(a);
b = key(b);
return a==b ? 0 : reverse * ((a > b) - (b > a));
//^ Return a zero if the two fields are equal!
}
}
var chainSortBy = function(sortByArr) {
return function(a, b) {
for (var i=0; i<sortByArr.length; i++) {
var res = sortByArr[i](a,b);
if (res != 0)
return res; //If the individual sort_by returns a non-zero,
//we found inequality, return the value from the comparator.
}
return 0;
}
}
arrayToSort.sort(
chainSortBy([
sort_by( "name", true, function(a){
return a.toUpperCase();
}),
sort_by("age", true, null)
])
);
console.log(arrayToSort); //Check browser console.
For output: check the JSFiddle

The solution is back to native, just :
function orderByProp(arr,prop){
var order = [], ordered=[];
//create temp ID and Save the real index
for(i=0; i < arr.length;++i){ order.push(arr[i][prop]+"-:-"+i);}
ordered.sort();
for(i=0; i < arr.length;++i){
var val = order.split("-:-");
ordered.push(arr[val[1]]); Get the real array by saved index
}
return ordered;
}
// Apply
var arr = [{
"name": "Bob",
"age":20,
"country": "India"
},
{
"name": "Benny",
"age":45,
"country": "USA"
},
{
"name": "Prasad",
"age":28,
"country": "India"
},
{
"name": "Robert",
"age":32,
"country": "UK"
},
{
"name": "Robin",
"age":34,
"country": "UK"
}];
var sort = orderByProp(arr,"name");
i'm not tested this. but hope it could solve your problems

This is relatively trivial with the Array.sort method by using the || operator, where it will use the second value if the first comparison returns 0, meaning the value was the same:
const data = [
{
"name": "Robert",
"age": 32,
},
{
"name": "David",
"age": 24,
},
{
"name": "Robert",
"age": 28,
},
];
const sortedData = data.sort((a, b) => a.name.localeCompare(b.name) || a.age - b.age);
console.log(sortedData);
Credit for this goes to #NinaScholz for her answer here.

Related

Flattening json with an key exclusion list

I need to flatten the json, but want to consider an exclusion_keys_array list which are not to be processed/added to the list
for example
if I have an exclusion_keys_array = ["addresses.metadata", "pageToken"]
//only metadata of addresses will be skipped (second level skip)
if I have an exclusion_keys_array = ["metadata", "pageToken"]
//metadata of parent json will be skipped (top level key skip)
How do I flatten a JSON using an exclusion array?
Code source: Dynamically generate a 2d array from JSON with varying columns
var exlusion_list = ["metadata", "meta", "pageToken"];
var crowds = [{
"name": [{
"firstName": "John",
"middleName": "Joseph",
"lastName": "Briggs",
}],
"addresses": [{
"type": "home",
"poBox": "111",
"city": "City1",
"postalCode": "1ER001",
"country": "USA",
}, {
"type": "work",
"poBox": "222",
"city": "City2",
"region": "Region2",
"postalCode": "1ER002",
}],
"photos": [{
"url": "photo.org/person1",
"default": true,
}, {
"url": "imagur.org/person1",
"default": true,
}],
"metadata": [{
"meta-id": "1234",
}],
}, {
"name": [{
"firstName": "Bill",
"lastName": "Thatcher",
}],
"addresses": [{
"type": "home",
"city": "City3",
"region": "Region3",
"postalCode": "1ER003",
"country": "USA",
}, {
"type": "work",
"poBox": "444",
"region": "Region4",
"postalCode": "1ER004",
}, {
"poBox": "555",
"region": "Region5",
"postalCode": "1ER005",
}],
"metadata": [{
"meta-id": "1234",
}],
}];
function flatten(obj, res = {}, key = '') {
let add = (d, s) => key ? key + d + s : s;
if (Array.isArray(obj)) {
obj.forEach((v, n) => flatten(v, res, add(' #', n + 1)));
} else if (typeof obj === 'object') {
Object.entries(obj).forEach(([k, v]) => flatten(v, res, add(': ', k)));
} else {
res[key] = obj;
}
return res;
}
let flats = crowds.map(obj => flatten(obj));
function combineKeys(objs) {
let keys = objs.reduce((k, obj) => k.concat(Object.keys(obj)), []);
return [...new Set(keys)];
}
let keys = combineKeys(flats);
let table = flats.map(f => keys.map(k => f[k] ?? ''));
table.unshift(keys);
console.log({ table });
// document.write(JSON.stringify(table));
.as-console-wrapper { min-height: 100%!important; top: 0; }
// .as-console-wrapper { min-height: 70%!important; bottom: 0; }
A quick fix would be filter the keys like below. I think there is a more efficient way to do it but I didn't look into the codes too deep.
let keys = combineKeys(flats).filter(
key => !exlusion_list.includes(key.split(":")[0].split(" ")[0])
);

Sort by name and number

I have an Array
people = [
{ "name": "Bob Mike", "nickname": "john" , "points": 5 ) },
{ "name": "Andrea Maria", "nickname": "bob", "points": 5 )}
{ "name": "Larry Kiu", "nickname": "larry", "points": 4 ) }
];
I want to sort it like this
Andrea Maria - 5
Bob Mike - 5
Larry Kiu - 4
I'm not into the Sort Method, I found this little Code, but it only Sorts the Points, not including the ASC from the name field.
people.sort(dynamicSort('name')).sort(dynamicSort('points'));
function dynamicSort(property) {return function(a, b) {
return (a[property] > b[property]) ? -1 : (a[property] < b[property])? 1 : 0;
}}
Edit: Thank you Erazihel
You can first sort by points and then use localeCompare to sort by names.
var people = [
{ "name": "Bob Mike", "nickname": "john" , "points": 5 },
{ "name": "Andrea Maria", "nickname": "bob", "points": 5},
{ "name": "Larry Kiu", "nickname": "larry", "points": 4 }
];
people.sort(function(a, b) {
return b.points - a.points || a.name.localeCompare(b.name)
})
console.log(people)
You can use localeCompare to compare two strings
const people = [
{ name: "Bob Mike", nickname: "john" , points: 5 },
{ name: "Andrea Maria", nickname: "bob", points: 5 },
{ name: "Larry Kiu", nickname: "larry", points: 4 }
];
people.sort(function(a, b) {
return b.points - a.points || a.name.localeCompare(b.name)
});
console.log(people.map(a => a.name + ' - ' + a.points));
What you want is something like this
var people = [
{ "name": "Bob Mike", "nickname": "john" , "points": 5 },
{ "name": "Andrea Maria", "nickname": "bob", "points": 5},
{ "name": "Larry Kiu", "nickname": "larry", "points": 4 }
];
people.sort(compare('name')).sort(compare('points'));
function compare(property){
return function(a, b){
return a[property] <= b[property];
}
}
console.log(people);
the compare function returns a function which would work on the property specified
By keeping the idea of a function for checking a single key of the object for sorting, you could use an array with the keys for sorting with their order and build a new array with the callbacks for using with sort.
In the sort callbak, the functions are invoked until the result value is different from a truthy value, which stops the iteration.
function dynamicSort(key, order) {
return function(a, b) {
return +(order === 'ASC' || -1) * +(a[key] < b[key] && -1 || a[key] > b[key]);
};
}
var people = [{ name: "Bob Mike", nickname: "john" , points: 5 }, { name: "Andrea Maria", nickname: "bob", points: 5 }, { name: "Larry Kiu", nickname: "larry", points: 4 }],
sortBy = [['points', 'DESC'], ['name', 'ASC']].map(a => dynamicSort(...a));
people.sort(function (a, b) {
var v = 0;
sortBy.some(f => v = v || f(a, b));
return v;
});
console.log(people);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Filter values of multiple properties with an array of values

So I really would like to make a duplicate out of my previous failed question because I wasn't really able to explain myself properly and thus didn't get the answer (and haven't really solved my issue).
There is the JSON:
[
{
"id": 2,
"name": "Google",
"country": "Sweden"
},
{
"id": 3,
"name": "Google",
"country": "USA"
},
{
"id": 9,
"name": "Google",
"country": "Ukraine"
},
{
"id": 10,
"name": "Bing",
"country": "Sweden"
}
]
What I failed to do previously I guess is to post what I have so far and explain better what I need. This is what I have right now:
$scope.loadSearchEngines = function ($query) {
return $http.get('search-engines.json', {cache: true}).then(function (response) {
var tags = response.data;
return tags.filter(function (tag) {
return tag.name.toLowerCase().indexOf($query.toLowerCase()) != -1;
});
});
};
This filter restricts me to search through only the name property, while ideally I would like to search through any property (or well, all the properties, rather) and I'd like the filter to work with an array of parameters so that each word would separately look through each property in the object and find a match.
In the previous post a person posted a for loop for it, but that is exactly the kind of unintuitive thing I want to avoid.
I know what I want the result to be but I just can't think of an approach (lodash/underscore seemingly almost does something like this, but I think there is something missing from somewhere or I am really bad at understanding the docs).
Examples:
In case I write "google sweden", it will only show the 1 result, if I write "sweden", it would show the one from google and the one from bing.
In case I write "in", it should show "bINg" and google "ukraINe" I guess.
This seemed like a much less of a hassle at first but I can't really wrap my head around this using a filter. I really don't have any ideas to post because they have been embarrassingly bad and this is a really grey area for me.
From the question it looks as if you want to filter a list where the values in each item contains every keyword:
var keywords = ['Google', 'Sweden'];
var result = _.filter(data, function(item){
return _.every(keywords, function(keyword){
return _.some(item, function(value){
return _.isString(value) && value.indexOf(keyword) != -1;
})
});
});
Perhaps something like this (note, probably not the most efficient one). Doesn't handle lowercase, but it's simple to add.
http://jsfiddle.net/W4QfJ/520/
var stuff = [
{
"id": 2,
"name": "Google",
"country": "Sweden"
},
{
"id": 3,
"name": "Google",
"country": "USA"
},
{
"id": 9,
"name": "Google",
"country": "Ukraine"
},
{
"id": 10,
"name": "Bing",
"country": "Sweden"
}
];
var searchTerm = 'Google Sweden';
var searchTermArr = searchTerm.split(' ');
var results = [];
var obj;
searchTermArr.forEach(function (val, index, arr) {
obj = _(stuff)
.filter(function (s) {
return JSON.stringify(s).indexOf(val) > -1;
}).value();
results = results.concat(obj);
});
console.log(_.unique(results));
Version A shapes the result for the whole data set to the wanted set which iterates throu the search words.
var data = [{
"id": 2,
"name": "Google",
"country": "Sweden"
}, {
"id": 3,
"name": "Google",
"country": "USA"
}, {
"id": 9,
"name": "Google",
"country": "Ukraine"
}, {
"id": 10,
"name": "Bing",
"country": "Sweden"
}];
function search(s) {
var searchA = s.split(' ').map(function (a) { return a.toLowerCase(); });
var result = JSON.parse(JSON.stringify(data));
searchA.forEach(function (a) {
result = result.reduce(function (res, el) {
var i;
for (i in el) {
if (~('' + el[i]).toLowerCase().indexOf(a)) {
res.push(el);
break;
}
}
return res;
}, []);
});
return result;
}
out('search for \'google sweden\'\n' + JSON.stringify(search('google sweden'), null, 4), true);
out('search for \'bing\'\n' + JSON.stringify(search('bing'), null, 4), true);
out('search for \'IN\'\n' + JSON.stringify(search('IN'), null, 4), true);
out('search for \'x\'\n' + JSON.stringify(search('x'), null, 4), true);
function out(s, pre) {
var descriptionNode = document.createElement('div');
if (pre) {
var preNode = document.createElement('pre');
preNode.innerHTML = s + '<br>';
descriptionNode.appendChild(preNode);
} else {
descriptionNode.innerHTML = s + '<br>';
}
document.getElementById('out0').appendChild(descriptionNode);
}
<div id="out0"></div>
Version B with different approach. Here I collect the objects who match the requirements.
var data = [{
"id": 2,
"name": "Google",
"country": "Sweden"
}, {
"id": 3,
"name": "Google",
"country": "USA"
}, {
"id": 9,
"name": "Google",
"country": "Ukraine"
}, {
"id": 10,
"name": "Bing",
"country": "Sweden"
}];
function search(s) {
var searchA = s.split(' ').map(function (a) { return a.toLowerCase(); });
return data.reduce(function (res, el) {
var i, found = 0;
for (i in el) {
found += searchA.some(function (a) {
return ~('' + el[i]).toLowerCase().indexOf(a);
});
}
found === searchA.length && res.push(el);
return res;
}, []);
}
out('search for \'google sweden\'\n' + JSON.stringify(search('google sweden'), null, 4), true);
out('search for \'bing\'\n' + JSON.stringify(search('bing'), null, 4), true);
out('search for \'IN\'\n' + JSON.stringify(search('IN'), null, 4), true);
out('search for \'x\'\n' + JSON.stringify(search('x'), null, 4), true);
function out(s, pre) {
var descriptionNode = document.createElement('div');
if (pre) {
var preNode = document.createElement('pre');
preNode.innerHTML = s + '<br>';
descriptionNode.appendChild(preNode);
} else {
descriptionNode.innerHTML = s + '<br>';
}
document.getElementById('out').appendChild(descriptionNode);
}
<div id="out"></div>

How to merge two javascript arrays based on property values?

I have two arrays
array1 = [{"id":1,"name":"Michale Sharma","gender":"Male","age":25,"salary":10000},{"id":2,"name":"Sunil Das","gender":"Male","age":24,"salary":5000},{"id":3,"name":"Robin Pandey","gender":"Male","age":35,"salary":45000},{"id":4,"name":"Mona Singh","gender":"Female","age":27,"salary":12000}]
array2 = [{"Deptid":1,"Deptname":"IT"},{"Deptid":12,"Deptname":"HR"},{"Deptid":3,"Deptname":"HW"}]
Output:
{ "0": { "id": 1, "name": "Michale Sharma", "gender": "Male", "age": 25, "salary": 10000, "Deptid": 1, "Deptname": "IT" }, "1": { "id": 2, "name": "Sunil Das", "gender": "Male", "age": 24, "salary": 5000}, "2": { "id": 3, "name": "Robin Pandey", "gender": "Male", "age": 35, "salary": 45000, "Deptid": 3, "Deptname": "HW" }, "3": { "id": 4, "name": "Mona Singh", "gender": "Female", "age": 27, "salary": 12000 }, "4" :{ "Deptid": 12, "Deptname": "HR" } }
I want to merge them based on the property values e.g id of array1 and Deptid of array2
Which means if id value = 1 and Deptid value = 1, then merge the records, if not then keep the values from one array and the other will be null. In another words, kind of the functionality of FULL OUTER JOIN. Because the array data cannot be a sequential one and may not be of the same length.
I have tried with Jquery.extend as under
$.extend(true, array1,array2)
It does not accept any property but merges the arrays.
I have also seen this but it does not help.
Looks like you need to have a custom logic. Here is sample using lodash library:
var array1 = [{"id":1,"name":"Michale Sharma","gender":"Male","age":25,"salary":10000},{"id":2,"name":"Sunil Das","gender":"Male","age":24,"salary":5000},{"id":3,"name":"Robin Pandey","gender":"Male","age":35,"salary":45000},{"id":4,"name":"Mona Singh","gender":"Female","age":27,"salary":12000}]
var array2 = [{"Deptid":1,"Deptname":"IT"},{"Deptid":12,"Deptname":"HR"},{"Deptid":3,"Deptname":"HW"}]
var defaults = {
Deptid: null,
DeptName: null
};
_.each(array1, function (obj) {
var dept = _.find(array2, { Deptid: obj.id });
_.defaults(obj, dept, defaults);
});
Using LoDash, roughly something like this:
_.reduce(array1, function(res, item1) {
var found = _.find(array2, function(item2) {
return item1.id === item2.Deptid;
});
if (found) res.push(_.merge({}, item1, found));
return res;
}, []);
EDIT: Sorry, I forgot about merging empty props.
It would be something like this (unoptimized):
function join(array1, array2) {
var keys = _.keys(array2[0]);
var vals = _.map(keys, function() { return '' });
return _.map(array1, function(item1) {
var found = _.find(array2, function(item2) {
return item1.id === item2.Deptid;
});
if (found)
return _.merge({}, item1, found);
return _.merge({}, item1, _.zipObject(keys, vals));
}, []);
}

Javascript sort object string with numbers

I have the following JSON object in javascript:
var stuff = [{
"id": "20",
"serial": "0/0/19:46,0/0/149:63"
}, {
"id": "8",
"serial": "0/0/151:215,0/0/151:233"
}, {
"id": "54",
"serial": "0/0/151:26,0/0/151:37"
}, {
"id": "22",
"serial": "0/0/155:29,0/0/155:36"
}, {
"id": "4",
"serial": "0/0/151:48,0/0/151:152"
}];
I would like to know how to sort the object by the "serial" field, leaving it like this (taking into account the value of the integers in the serial string):
var stuff = [{
"id": "20",
"serial": "0/0/19:46,0/0/149:63"
}, {
"id": "54",
"serial": "0/0/151:26,0/0/151:37"
}, {
"id": "4",
"serial": "0/0/151:48,0/0/151:152"
}, {
"id": "8",
"serial": "0/0/151:215,0/0/151:233"
}, {
"id": "22",
"serial": "0/0/155:29,0/0/155:36"
}];
Thanks in advance.
This will do it for you:
var normalizer = /[:\/]/g;
function serialCompare(a, b) {
var alist = a.serial.replace(normalizer, ',').split(','),
blist = b.serial.replace(normalizer, ',').split(','),
i = 0, l = alist.length;
while (alist[i] === blist[i] && i < l) {
i += 1;
};
return (parseInt(alist[i], 10) - parseInt(blist[i], 10));
}
sortedstuff = stuff.sort(serialCompare);
// returns array sorted as you asked
See it in a fiddle.
If you are going to be sorting often, or the list is very long, you should consider creating a "normalized" version of the serial value that gets stored in the object. It could be the array as calculated inside the serialCompare function, or it could be the text number parts padded to the same lengths with leading zeroes.
You have an array of objects, which you want to sort by one of their properties. You could very easily do it like this:
stuff.sort(function(a,b) {return a.serial == b.serial ? 0 : a.serial < b.serial ? -1 : 1;});
Alternatively, you could have a more general function:
function sort(input,prop) {
input.sort(function(a,b) {return a[prop] == b[prop] ? 0 : a[prop] < b[prop] ? -1 : 1;});
}
// call with:
sort(stuff,'serial');

Categories