array.sort not working on array of objects - javascript

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/

Related

javascript / es6 - how to get index of first object value whos array length is greater than 0

I have an object which is full of arrays:
const errors = { name: [], date: ['invalid format'], ... }
I want to find the index (or object key, if I can't get an index) of the first value in the errors object where the array length is greater than one. So in the example above, the date array is the first array in the object that has a length, so I would just return, ideally, 1, or date if necessary.
Anybody know the most concise / fastest way to do this in javascript / es6?
You can use find() on Object.keys() and it will return first result that matches condition or undefined.
const errors = { name: [], date: ['invalid format']}
var result = Object.keys(errors).find(e => errors[e].length);
console.log(result)
JavaScript objects have no inherent order to their properties, so if an index is truly salient you probably want to use an array instead.
At that point it's just something like errors.findIndex(e => e.length > 1), adjusted as you see fit.
You can use for ..in to loop through the object and Object.prototype.toString to check if the value is an array.
Also to find the index you may need to use Object.keys which will create an array of keys from the object. Js Object does not have index
const errors = {
name: [],
test: 1,
date: ['invalid format'],
test2: 2
}
for (var keys in errors) {
if (Object.prototype.toString.call(errors[keys]) === '[object Array]' && errors[keys].length > 0) {
console.log(errors[keys])
}
}

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.

Javascript sort function with grouping

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.

Most efficient and clean way to push a value to an array only if it does not exist yet

Suppose an array named myArray containing several values but no duplicates.
Suppose I want to push a value into it only if it won't lead to duplicates presence.
How I determinate duplicates => by comparing value's id.
I thought about Lodash#uniq to do the trick:
myArray.push(aNewValue);
myArray = _.uniq(myArray,function(item){
return item.id;
});
However, I don't like the reassignment to the array and especially the fact that I have to push before checking...
Is there a more "functional" way to achieve it while being very short?
I don't want to iterate through the array explicitly in order to apply the check.
That's why I attempted to use Lodash.
You can check the presence of an item before adding it :
if(myArray.indexOf(aNewValue) == -1) {
myArray.push(aNewValue);
}
The most efficient way to do this is generally to use an object for uniqueness, because an object can have at most one key of a certain value. However, this is restricted to strings and things that stringify, since only strings can be object keys.
There are two approaches here. If you are using your array often, then you should keep parallel structures - an object for uniqueness check, an array for arrayness of it.
If you don't need your array often, i.e. you want to push a bunch of things and then have an array be unique, you can just use the object, and transform it into an array when you need it (which is somewhat expensive, so you only want to do it once, but still cheaper than manipulating two different structures all the time).
The first approach is illustrated here:
function Set() {
this.presence = {};
this.array = [];
};
Set.prototype.push = function(key, value) {
if (this.presence[key]) return;
this.presence[key] = true;
this.array.push(value);
};
var a = new Set();
a.push(3, { id: 3, value: "SOMETHING" });
a.push(7, { id: 7, value: "SOMETHING ELSE" });
a.push(3, { id: 3, value: "SOMETHING" });
console.log(a.array); // => only 2 elements
The second, here:
function Set() {
this.store = {};
};
Set.prototype.push = function(key, value) {
this.store[key] = value;
};
Set.prototype.array = function() {
var that = this;
return Object.keys(this.store).map(function(key) { return that.store[key]; })
};
...
console.log(a.array()); // note the newly added parentheses :)
Both of these are still cheaper than looking for presence inside the array using indexOf, even more so when you do your own iterating, except very much maybe in case the array is very short.
You could use Array.prototype.some() to find out if the value is already part of the array, e.g.:
if( myArray.some(function (elem) { return elem.id == newValue.id }) )
myArray.push(newValue);
You can't really get away with not looping through the array, though.

Filter duplicate collection objects (case-insenstive and trim) by property value with underscore

I'm looking for a way to filter / reject objects in a collection based on the value of a chosen property. Specifically I need to filter out objects that contain duplicate values for that chosen property. I need to convert the property value to lower case and trim the whitespace.
I already have my method for removing the duplicates but I can't figure out how to include the lowercase conversion and the trim.
removeDuplicates: function (coll, attr) {
var uniques = _.map(_.groupBy(coll, function (obj) {
return obj[attr];
}), function (grouped) {
return grouped[0];
});
return uniques;
}
Any help would be appreciated.
If the collection is defined like this
var array = [{
name: "thefourtheye"
}, {
name: "theFOURtheye"
}, {
name: "thethirdeye"
}];
You should be using _.uniq function, like this
var attr = "name";
console.log(_.unique(array, false, function(currenObject) {
return currenObject[attr].toLowerCase();
}));
# [ { name: 'thefourtheye' }, { name: 'thethirdeye' } ]
As per the signature,
uniq_.uniq(array, [isSorted], [iterator])
the second parameter is tell if the collection is already sorted. This is important, because if the collection is sorted, there are algorithms which can find the unique data very efficiently.
the third parameter, should be a function, which can transform the data to get the key value to compare. As we see in the example, we actually pick the name property from the individual objects and convert them to lower case letters. So, this lower cased name will represent this object and if two lowercased names are the same, then those objects will be considered as the duplicate of each other.

Categories

Resources