I'm trying to sort a multidimensional array in Javascript. I see plenty of explanations of how to sort one by number values, but I can't seem to figure out how to sort it by text.
The array that I want to sort looks like this:
var blocks = [
{
"heading": ["2013-10-1", "Chris", "11"],
"content": "stuff 1"
},
{
"heading": ["2013-10-3", "Zoe", "14"],
"content": "stuff 2"
},
{
"heading": ["2013-10-2", "Petey", "12"],
"content": "stuff 3"
}
]
I know I can sort this multidimensional array of objects like so on values that are numbers:
blocks.sort(
function(a,b){
return a.heading[2] - b.heading[2];
}
)
I'm not sure what the function I pass into the sort method would look like if I wanted to sort the array by the first or second value in the heading sub array since value at index 0 is a date and value at index 1 is text.
I tried to look up changing the date or text to a number and then evaluating it off of it's numeric value, but I wasn't able to find a way of doing so (nor do I know if that's the best way of approaching this). Any suggestions?
[Edit] I should point out that whilst the initial solution I suggested works, it's not a good solution because it's unnecessarily complex.
The > operator can be used for your purpose. When comparing strings, string a is considered smaller than string b if string a comes before b in an alphabetically sorted list.
The only catch is that this is case sensitive and so upper case comes before lower case e.g. sorting the characters AaBbCc would give you ABCabc.
This means that you can just do this:
blocks = blocks.sort(function(a,b) { return a.heading[1] > b.heading[1] }
Previous answer:
Someone may be able to provide a more elegant solution, but this will work.
Note that sort() sorts alphabetically. So all we need to do is make the sort() function work on the names of the people.
blocks = blocks.sort(
function(a,b){
var x = [a.heading[1], b.heading[1]].sort()
if (x[0] == a.heading[1]) {return -1} else
{return 1};
}
)
Try something like this:
blocks.sort(function(a, b){
if(a.heading[0] < b.heading[0]) return -1;
if(a.heading[0] > a.heading[0]) return 1;
return 0;
});
Or just by simplifying the code above:
blocks.sort(function(a, b){
if(a.heading[0] < b.heading[0]) {
return -1;
} else {
return 1;
}
return 0;
});
You can also use the following:
blocks.sort(function(a, b){
var heading_a = a.heading.toLowerCase();
var heading_a=b.heading.toLowerCase();
if (heading_a < heading_a) //sort ascending
return -1
if (heading_a > heading_a)
return 1
return 0 //just return default value indicating there is no sort
});
If your data contain unicode strings then you should use the code above like this:
blocks.sort(function(a, b){
var heading_a = a.heading[0].toLowerCase();
var heading_a=b.heading[0].toLowerCase();
return heading_a.localeCompare(heading_b);
});
This is just a sneak peak into sorting, it is just a quick example and I hope it helps.
Related
Very quick question,
I want to order an array by specific value and put it always at the middle.
For example... if my array is:
myarr = [1,2,3,4,5];
the function middlelizeArr(myarr,2) should me returns this array:
result [5,1,2,3,4]
and find the best condition in case of even array
myarr = [1,2,3,4,5,6,7,8];
result [7,8,1,2,3,4,5,6];
How could I do this? Someone can help me?
At first you need to have a size of an array and find the location of a number you want to put into the middle
If,
n = size of an array
l = location of the number you want to put it into the middle
if n/2 > l, then shift your number clockwise otherwise shift it anti clockwise.
This is done by comparing the index of the "middle" number to the length of the array, and then shifting (and pushing) until they're equal.
I've implemented it the following way:
function middlelizeArray(array, middle) {
var goal = Math.trunc(array.length/2);
while(array.indexOf(middle) != goal) {
array.push(array.shift());
}
return array;
}
This could also be added directly to the array prototype. This would allow you to just say myArray.middlelize(2);
That would be done like so:
Array.prototype.middlelize = function(middle) {
var goal = Math.trunc(this.length/2);
while(this.indexOf(middle) != goal) {
this.push(this.shift());
}
return this;
}
I have an array like this
arr1 = ["P2.13","P1.13","P4.13","P3.13", "P2.14","P2.14","P1.14","P4.14","P1.15","P2.15","P3.15","P4.15"];
How can I sort the array by the number after the dot FIRST, from 13 to 15, then sort by the number after "P" from 1 to 4? Finaly I want an array like this
arr2 = ["P1.13","P2.13","P3.13","P4.13","P1.14","P2.14","P3.14","P4.14","P1.15","P2.15","P3.15","P4.15"];
Appreciate!!
Pass a function into sort. The following will work for the precise test case provided, but would need to be modified if the input is more general.
arr1.sort(function(a, b) {
return a.slice(-2) - b.slice(-2) || a[1] - b[1];
});
Note that this will mutate arr1.
For programs that include many functional programming I choose Underscore library.
You can directly call sortBy function based on multiple attributes with a hacky trick by joining them. Here is the code:
var sortedArray = _.sortBy(arr1, function(item) {
var nums = item.split('.');
return [nums[1], nums[0]].join("_");
});
However, you can still use Javascript sort function to sort list with customized compare function. Here is the code:
arr1.sort(function(x, y) {
var numsX = x.split('.');
var numsY = y.split('.');
if (numsX[1] !== numsY[1]) {
return compare(numsX[1], numsY[1]); // compare the number after dot first
}
return compare(numsX[0], numsY[0]); // compare the number before dot after
});
// General comparison function for convenience
function compare(x, y) {
if (x === y) {
return 0;
}
return x > y ? 1 : -1;
}
Check two examples on fiddle
Underscore sort vs
Javscript sort
This is my new account, I don't have reputation to post more than 2 links. You can search underscore library.
Thanks.
I got the following object array:
var arr = [{
2: {
1: { name: "test" },
2: { name: "apple" }
},
3: {
1: { name: "banana" },
2: { name: "pear" }
}
}];
Just some sample data. Now, I got 3 textareas:
<textarea id="first"></textarea>
<textarea id="second"></textarea>
<textarea id="third"></textarea>
And I have the following custom-made function:
function sort(alt)
{
arr.sort(function (a,b)
{
console.log(a);
if (a[2].name < a[2].name)
return (alt) ? 1 : -1;
if (a[2].name > a[2].name)
return (alt) ? -1 : 1;
return 0;
});
}
It should sort the array of objects by name, ascending or descending according to parameter. Now, I got 2 problems. This way I append all the values to the textareas:
for (var key in arr[0])
{
var obj = arr[0][key];
$(ID).append(obj[2].name + '\n');
}
The first time, that code will be executed without running sort. The second time, sort will be executed with false as parameter, than that code will be executed. The third time sort will be executed with true as parameter, than that code will be executed. However, the output of all textboxes is exactly the same.
This is a link to the jsfiddle:
http://jsfiddle.net/JoshB1997/gow4vzsc/
Also, the console.log(a) doesn't get printed in the console.
So variable arr is an array but as far as I can see it contains only one object.
You're trying to sort directly onto the array, since it only has one object it will simply never sort because there is nothing to sort.
You'll want to access arr[0] which is the object containing the actual objects you want to sort however the Object prototype doesn't contain any of the array functions so you cannot call sort on it even tho technically an Array is an Object an Array inherits from Object and not the other way around so the methods from Object are available to Array but not the other way around.
Also, you're trying to compare the same a[2].name with itself so it'll always be false since it's equal, not > or <.
In your case I extract all the name properties from every nested object you have like this (considering the usage of the original arr):
var compare = [];
var alt = false;
for (k in arr[0]) {
if (arr[0].hasOwnProperty(k)) {
for (l in arr[0][k])
if (arr[0][k].hasOwnProperty(l))
compare.push(arr[0][k][l].name);
compare.sort(function(a, b) {
if (a == b)
return 0;
else if (a < b)
return alt ? 1 : -1
else
return alt ? -1 : 1
});
Now you can use the compare array to output the sorted names correctly.
Also - your construction seems overly complex? It has objects and within them are nested objects but you're only sorting and displaying names, is there any reason this structure has to be maintained?
If not I would highly recommend you simplify this to just be an array of names, the loop I made above is far from beautiful and I'd rather have you not use it since it assumes that the outmost object is an object filled with other objects that all have the name property. This code could still break without an extra arr[0][k][l].hasOwnProperty('name').
Either way, the compare array simply contains all the names and it easily sortable with the default sort if you don't make things to complex for yourself.
I suggest you to use http://underscorejs.org/ that contains quite really useful function to transform from object to arrays.
For example in this case you can use something like http://underscorejs.org/#values
let values = _.values(arr[0]);
Now values is an array that contains your two object (2,3) in the form
values = [
{
1: {
name: "test"
},
2: {
name: "apple"
}
},
{
1: {
name: "banana"
},
2: {
name: "pear"
}
}
]
and here you can call your sort function
There is my demo on your code with underscore.js http://jsfiddle.net/gow4vzsc/3/
EDIT: If you cant/wont to include an entire library you can write your code for get the values:
values = [];
for(key in arr[0]){
values.push(arr[0][key]);
}
Here a demo without underscore.js http://jsfiddle.net/3L7ttu2r/1/
Having issues writing a javascript sort function that would let me do the following:
UPDATE: this is javascript for node.js
I have a bunch of files and folders as JSON objects with the following properties
[
{"name":"Folder B","isFolder":true},
{"name":"File A","isFolder":false},
{"name":"Folder A","isFolder":true},
{"name":"File B","isFolder":false}
]
I want to sort this array so the folders are grouped at the top and alphabetically ordered, then files alphabetically ordered like so
[
{"name":"Folder A","isFolder":true},
{"name":"Folder B","isFolder":true},
{"name":"File A","isFolder":false},
{"name":"File B","isFolder":false}
]
After much researching on stackoverflow here I've come up with this, but it just groups the folders at the top, and does not sort by the name .. thoughts?
array.sort(function(a,b){
return (b.isFolder-a.isFolder) || (b.name - a.name);
}));
subtracting one string from another will always give "NaN", instead, use localeCompare().
array.sort(function(a,b){
return (b.isFolder-a.isFolder) || (a.name.toString().localeCompare(b.name));
});
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare
Your names are no numbers, you cannot get a comparison value by subtracting them. Use
array.sort(function(a,b){
return (b.isFolder-a.isFolder) || +(a.name>b.name)||-(a.name<b.name);
});
The sort function below first checks to see if the items to be compared are of different types, which is to say that one is a folder and the other is a file. If so, the folder must come first, and the function returns -1. If not, we return 1 because that's what the sort function requires if the second item is to come before the first.
If the items are of the same type, we compare the names. In this case it's possible that the two names are the same, in which case we must return 0.
var array = [ // Test data.
{"name":"Folder B","isFolder":true},
{"name":"File A","isFolder":false},
{"name":"Folder A","isFolder":true},
{"name":"File B","isFolder":false}
];
array.sort(function(a, b) {
if (a.isFolder != b.isFolder) { // Is one a folder and
return (a.isFolder ? -1 : 1); // the other a file?
} // If not, compare the
return a.name.localeCompare(b.name); // the names.
});
for (var i = 0; i < array.length; ++i) { // Check the result.
document.write(array[i].name+' '+array[i].isFolder+'<br />');
}
This is another possible solution;
var objects = [
{"name":"Folder B","isFolder":true},
{"name":"File A","isFolder":false},
{"name":"Folder A","isFolder":true},
{"name":"File B","isFolder":false}
];
function isFolder(object) {
return object.isFolder;
}
function isNotFolder(object) {
return !object.isFolder;
}
function byName(a, b) {
return a.name.localeCompare(b.name);
}
var folders = objects.filter(isFolder).sort(byName),
files = objects.filter(isNotFolder).sort(byName);
var allSorted = folders.concat(files);
console.log(allSorted);
Though its way longer than other solutions it is IMHO much more readable.
I'm using Javascript sort (with Underscore.js):
_.sortBy(["Bob", "Mary", "Alice"], function (name) {return name})
> ["Alice", "Bob", "Mary"]
I would like the array to return the other way. How do I do that?
["Mary", "Bob", "Alice"]
I don't want to reverse it after it's sorted - I want it to be created the other way around the first time.
Thanks.
Instead of throwing underscorejs away, I'd rather use it together with Array.reverse to utilize the best of both.
_.sortBy(["Bob", "Mary", "Alice"], function (name) {return name})
.reverse()
I would just do what Underscore does under the hood: use the Array#sort method.
["Bob", "Mary", "Alice"].sort(function (a, b) {
if (a < b) return 1;
if (b < a) return -1;
return 0;
});
Or if you don't want the original array modified, clone it first:
_.clone(["Bob", "Mary", "Alice"]).sort(...)
Obviously you should not do this, as it makes far more sense to sort, then reverse the results, but if you really want to sort in reverse order inside the sort function, you could do something like this...
_.sortBy(["Bob", "Mary", "Alice"], function (a) {
// split each string into single characters
// ... then swap for inverse character from unicode range
// ... and glue the characters back together into an inversely sortable string
return _.map(a.split(''), function(i){
return String.fromCharCode(65536 - i.charCodeAt(0));
}).join('');
});
... also worth noting that underscore is subtly different than native javascript sort which has a small cross platform issue regarding consistent sort order...
If compareFunction(a, b) returns 0, leave a and b unchanged with respect to each other, but sorted with respect to all different elements. Note: the ECMAscript standard does not guarantee this behaviour, and thus not all browsers (e.g. Mozilla versions dating back to at least 2003) respect this. Array.prototype.sort()
Underscore's .sortBy documentation states:
Returns a (stably) sorted copy of list. _.sortBy
Which it does by instead of returning 0 to keep items in order, it returns the index of the left side minus the index of the right side.
_.sortBy = function(obj, iteratee, context) {
iteratee = cb(iteratee, context);
return _.pluck(_.map(obj, function(value, index, list) {
return {
value: value,
index: index,
criteria: iteratee(value, index, list)
};
}).sort(function(left, right) {
var a = left.criteria;
var b = right.criteria;
if (a !== b) {
if (a > b || a === void 0) return 1;
if (a < b || b === void 0) return -1;
}
return left.index - right.index;
}), 'value');
};
You can do this with a 1 liner in ES6, just change the > depending on the direction you want.
.sort() is supported since IE6 and you just pass a function which returns 1 or -1;
["Bob", "Mary", "Alice].sort((a, b) => a > b ? 1 : -1);