Javascript sort function with grouping - javascript

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.

Related

array.sort not working on array of objects

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/

Javascript: Find douplicated values from array with keys

Title is pretty much self explanatory...
I want to be able to find duplicated values from JavaScript array.
The array keys can be duplicated so I need to validate only the array values.
Here is an example :
var arr=[
Ibanez: 'JoeSatriani',
Ibanez: 'SteveVai',
Fender: 'YngwieMalmsteen',
Fender: 'EricJohnson',
Gibson: 'EricJohnson',
Takamine: 'SteveVai'
];
In that example:
the key is the guitar brand
the value is the guitar player name.
So:
If there is duplicated keys (like: Ibanez or Fender) as on that current example that is OK :-)
But
If there is duplicated values (like: EricJohnson or SteveVai) I'm expecting to get (return) that error:
EricJohnson,SteveVai
You can't have associative arrays in Javascript. You can create an array of objects, like:
var arr=[
{Ibanez: 'JoeSatriani'},
{Ibanez: 'SteveVai'},
{Fender: 'YngwieMalmsteen'},
{Fender: 'EricJohnson'},
{Gibson: 'EricJohnson'},
{Takamine: 'SteveVai'}
];
Then you'll need a for...in loop to go over every object in the array, create a new array of values and check that for duplicates, which is also not very straightforward - basically you'll want to sort the array and make sure no value is the same as the one after it.
var arrayOfValues = [];
arr.forEach(function(obj){
for(var prop in obj)
arrayOfValues.push(obj[prop]);
});
arrayOfValues.sort(); // by default it will sort them alphabetically
arrayOfValues.forEach(function(element,index,array){
if(array[index+1] && element==array[index+1])
alert("Duplicate value found!");
});
First of all, object keys can not be repeated.
This means that:
({
"Fender": "Jim",
"Fender": "Bob"
})["Fender"]
Would simply return: "Bob".
However, I did make a code that could allow you to find duplicates in values, but as I said, the key will have to be unique:
var arr = {
Ibanez: 'EricJohnson',
Fender: 'YngwieMalmsteen',
Gibson: 'EricJohnson',
Takamine: 'SteveVai',
"Takamine2": 'SteveVai'
};
function contains(a, obj) {
for (var i = 0; i < a.length; i++) {
if (a[i] === obj) {
return true;
}
}
return false;
}
var track = [];
var exists = [];
for (var val in arr) {
if (contains(track, arr[val])) {
exists.push(arr[val]);
} else {
track.push(arr[val])
}
}
alert(exists)
You can see it working here: http://jsfiddle.net/dr09sga6/2/
As others have commented, the example array you provided isn't a valid JavaScript array. You could, however, keep a list for each guitar type:
var mapping = {
Ibanez: ['JoeSatriani','SteveVai'],
Fender: ['YngwieMalmsteen','EricJohnson']
Gibson: ['EricJohnson'],
Takamine: ['SteveVai']
];
Or a list of each guitar/musician pair:
var pairs = [
['Ibanez','JoeSatriani'],
['Ibanez','SteveVai'],
['Fender','YngwieMalmsteen'],
['Fender','EricJohnson'],
['Gibson','EricJohnson'],
['Takamine','SteveVai']
];
Your solution is going to depend on which pattern you go with. However, in the second case it can be done in one chained functional call:
pairs.map(function(e) {return e[1]}) // Discard the brand names
.sort() // Sort by artist
.reduce(function(p,c,i,a){
if (i>0 && a[i]==a[i-1] && !p.some(function(v) {return v == c;})) p.push(c);
return p;
},[]); //Return the artist names that are duplicated
http://jsfiddle.net/mkurqmqd/1/
To break that reduce call down a bit, here's the callback again:
function(p,c,i,a){
if (i>0
&& a[i]==a[i-1]
&& !p.some(function(v) {
return v == c;
}))
p.push(c);
return p;
}
reduce is going to call our callback for each element in the array, and it's going to pass the returned value for each call into the next call as the first parameter (p). It's useful for accumulating a list as you move across an array.
Because we're looking back at the previous item, we need to make sure we don't go out of bounds on item 0.
Then we're checking to see if this item matches the previous one in the (sorted) list.
Then we're checking (with Array.prototype.some()) whether the value we've found is ALREADY in our list of duplicates...to avoid having duplicate duplicates!
If all of those checks pass, we add the name to our list of duplicate values.

Sort an array based on values from another array of different length

I've looked through several posts with similar with issues but I couldn't find one which solves my problem. The others all seemed to be sorting using another array of the same size or by value.
I have two arrays which look like this:
var allCategories = ['Campus', 'Building', 'Floor', 'Room', 'Lecture Theatre', 'Lab'];
var currentCategories = ['Room', 'Lab', 'Campus'];
How can I sort currentCategories so that the order matches that of allCategories?
Desired output:
currentCategories --> ['Campus', 'Room', 'Lab'];
"Sort this array by the indices of its elements in that array":
currentCategories.sort(function(a, b) {
return allCategories.indexOf(a) - allCategories.indexOf(b);
});
// => ["Campus", "Room", "Lab"]
If all that you want is the order of allCategories with the members of currentCategories, you can do the following.
allCategories.filter(function(x){return currentCategories.indexOf(x) != -1})
This assumes that you are treating each array as a set of non-repeating elements. As mentioned in the comments, this method may drop duplicate elements from the final value, or not order duplicate elements in the way you might intend.
The Array.Sort method can be supplied a custom function. You could do something like this:
currentCategories.sort(function(a, b) {
var i = allCategories.indexOf(a),
j = allCategories.indexOf(b);
return i - j;
});
I haven't checked the behaviour for when there are values in currentCategories that are not in allCategories. But then, it wouldn't be living up to its name if that were the case.
If this is a common case, you could generalise it along the following lines:
function sortByList(list) {
return function (a, b) { return list.indexOf(a) - list.indexOf(b); };
}
And call it thusly:
currentCategories.sort(sortByList(allCategories));

Sorting a Multidimensional Array by Text

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.

JavaScript, array.sort() two arrays based on only one of them

I just discovered array.sort() and saw that I can specify how to sort like this: (example taken from http://www.w3schools.com/jsref/jsref_sort.asp)
var points = [40,100,1,5,25,10];
points.sort(function(a,b){return a-b});
I've been doing my sorting manually just using Bubble Sort because the arrays are small, but I was wondering if array.sort() can be used in place of this:
// Sort rowCategories[i] by rowWidth[i]
swapped = true;
while (swapped) {
swapped = false;
for (var i = 0; i < rowCategories.length-1; i++) {
if (rowWidth[i] < rowWidth[i+1]) {
var swap = rowCategories[i];
rowCategories[i] = rowCategories[i+1];
rowCategories[i+1] = swap;
swap = rowWidth[i];
rowWidth[i] = rowWidth[i+1];
rowWidth[i+1] = swap;
swapped = true;
}
}
}
What would I write for the built in sort to do the equivalent work?
There is a way of multi sorting arrays but I like the array of objects better. Here is the multi sort:
function multisort(sortBy,otherArrays){
var keys=[],i,tmpKeys;
sortBy.sort(function(a,b){
var ret=(a>b)?1:(a<b)?-1:0;
// storing the return values to be used for the other arrays
keys.push(ret);
return ret;
});
for(i=0;i<otherArrays.length;i++){
// copy the stored retun values
tmpKeys=keys.concat([]);
otherArrays[i].sort(function(){
// return the saved values based on sortBy array's sort
return tmpKeys.splice(0,1);
});
}
}
var arr1=[1,2,3],
arr2=[5,6,7],
reverse=["c","b","a"];
multisort(reverse,[arr1,arr2])
console.log(arr1);
console.log(arr2);
console.log(reverse);
Sorting by object key:
var arr=[
{id:1,col1:3,col2:2},
{id:2,col1:2,col2:2},
{id:3,col1:1,col2:1}
];
function sortBy(arr,keys){
var i=0;
arr.sort(function(a,b){
var i=0;
while(a[keys[i]]===b[keys[i]]&&i<keys.length){
i++;
}
return (keys.length===i)?0:(a[keys[i]]>b[keys[i]])?1:-1;
});
}
//sort by col2 then col1
sortBy(arr,["col2","col1"]);
console.log(arr);
//sort by id
sortBy(arr,["id"]);
console.log(arr);
this only requires a little modification. Instead of storing two arrays store one array with an object with the two attributes. Then you can do something like this.
arr.sort(functiona(a,b){return a.rowWidth - b.rowWidth});
the object must contain the attributes rowWidth and rowCatagories
The built-in sort() can only sort one array at a time, and the comparison is based on the values, not the indexes.
What you're doing is similar to PHP's array_multisort() function. If you use load the php.js library, it includes an implementation of this function. The implementation is here.
Actually the vanilla the sort function uses depends on browser JS engine implementation, for instance Mozilla uses MergeSort I believe.
By default it compare array items as strings and if you need any other consideration you must pass your own compare function to sort and function must return negative, 0 or positive number indicating the items comparison result.
You can use this sort function instead of yours, that would be much faster.

Categories

Resources