Sort javascript object by property value - javascript

How would one sort the following object to correct alphabetical order of names?
var users_by_id = {
'12': ['Ted', 34, 'Male'],
'13': ['Ben', 36, 'Male'],
'14': ['Alice', 42, 'Female']
}
var users_by_name = {
'14': ['Alice', 42, 'Female'],
'13': ['Ben', 36, 'Male'],
'12': ['Ted', 34, 'Male']
}
I have a plan which would need two passes over the object, but I am not sure if there is a simpler way to do it. The plan I have is this (I am using jQuery):
var users_by_name = {};
var names = [];
$.each(users_by_id, function(id, props) {
names.push(props[0]);
});
names.sort();
$.each(names, function(i, n) {
$.each(users_by_id, function(id, props) {
if(n == props[0]) users_by_name[id] = props;
});
});

If you want to keep your Object based storage instead of using an Array, you'll need to create Arrays of the properties of the Object, and sort that to get an order.
var users = {
'12': ['Ted', 34, 'Male'],
'13': ['Ben', 36, 'Male'],
'14': ['Alice', 42, 'Female']
};
var props = Object.keys(users);
var user_props_by_name = props.slice().sort(function(prop_a, prop_b) {
return users[prop_a][0].localeCompare(users[prop_b][0]);
});
var user_props_by_age = props.slice().sort(function(prop_a, prop_b) {
return users[prop_a][1] - users[prop_b][1];
});
Then you can iterate the Array and use the property names to look up items in the Object.
user_props_by_name.forEach(function(prop) {
console.log(users[prop]);
});
user_props_by_age.forEach(function(prop) {
console.log(users[prop]);
});
This will take some maintenance though when adding and removing users. You would probably want to create a layer that adds and removes users, and updates the sort Arrays at the same time.

sort takes an optional parameter which is a compare function
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/sort
var data =[
['Ted', 34, 'Male'],
['Ben', 36, 'Male'],
['Alice', 42, 'Female']
];
var sortbyid = data.sort(function(a,b){return a[1]<b[1] ? -1 : a[1]>b[1] ? 1 : 0});
var sortbyname = data.sort(function(a,b){return a[0]<b[0] ? -1 : a[0]>b[0] ? 1 : 0});

Sorting is usually made when you have to iterate on a collection. So... my suggestion is to ad to methods to your object's prototype - .keysById and .keysByName which have to return keys ordered by the condition you mentioned above. So when you have to iterate by name you can do:
var kbn = yourObj.keysByName();
for(var i=0; i<yourObj.length;i++) {
... yourObj[kbn[i]] ....
}
You can do this methods, i'm sure, so I won't give examples.
yourObj.prototype.keysByName = function() { ... }

OK, I got it. As has been pointed in the comments, Objects do not have any order. So no matter in what order the members are added (or defined), iterating over the object will produce the same result.
So, I should change my approach. A bit of background is neccessary to see why the new method would work (which is missing in the question by the way).
I have a users table and I want to display an html table with users in alphabetical order like so:
<tr id="user-N"><td>NAME</td><td>AGE</td><td>GENDER</td></tr>
And here is what I came by:
var name_ids = [];
var trows = '', thisNID;
$.each(users_by_id, function(id, props) {
name_ids.push(props[0]+':'+id);
});
name_ids.sort();
$.each(name_ids, function(i,nid) {
thisNID = nid.split(':');
trows += '<tr id="user-' + thisNID[1] + '">';
trows += '<td>' + users_by_id[thisNID[1]][0] + '</td>';
trows += '<td>' + users_by_id[thisNID[1]][1] + '</td>';
trows += '<td>' + users_by_id[thisNID[1]][2] + '</td>';
trows += '</tr>';
});

Another option that makes use of Array.prototype.sort function
var users_by_id = {
'0': ['Ted', 34, 'Male'],
'1': ['Ben', 36, 'Male'],
'2': ['Alice', 42, 'Female']
}
// as long as length is bigger than max object key value
users_by_id.length = Object.keys( users_by_id ).length;
Array.prototype.sort.call( users_by_id, function( a, b ) {
var aName = a[0].toLowerCase();
var bName = b[0].toLowerCase();
if( aName > bName ) return 1;
if( aName < bName ) return -1;
return 0;
});
There are a few caveats to the above:
The object must contain a length property
The length property must have a value larger than the max value of the object keys
If you do not use contiguous numbers as keys and do the following:
var users_by_id = {
'12': ['Ted', 34, 'Male'],
'13': ['Ben', 36, 'Male'],
'14': ['Alice', 42, 'Female']
}
Once sorted the object's sorted numeric keys will be renamed => 0,1,2
Take a look at the fiddle here

Related

Data structure/format of C3 data using JavaScript

I am trying to get my array of objects in the format that is needed to create a C3 bar chart but I am having trouble categorizing my data with JavaScript. Below is my JavaScript code.
data = [
{Service:"Army",Permanent:20,Itinerant:754,Region:"Western"},
{Service:"Air Force",Permanent:100,Itinerant:2,Region:"Eastern"},
{Service:"Army",Permanent:10,Itinerant:7,Region:"Western"},
{Service:"Air Force",Permanent:30,Itinerant:2,Region:"Eastern"}
]
var sumAry=[];
for (var x=0; x<data.length; x++){
var val =sumAry.indexOf(data[x].Service);
if(val === -1){
var permanent += data[x].Permanent;
sumAry.push(data[x].Service, permanent);
}else{
console.log("IN");
}
}
https://codepen.io/isogunro/pen/GYRKqE?editors=0012
The C3 chart looks for a data in the structure/format shown below:
['Army', 30],
['Airorce', 130],
['Navy', 190],
['Army1', 70],
['Airorce2', 130],
['Navy3', 100]
For each of the values, the 'Permanent' property will be added up to get the number part of the array. It becomes an aggregate of all the information.
Assuming the number in the preferred format comes from the Permanent property, you could use Array.map to transform your dataset.
var data = [{
Service: "Army",
Permanent: 654,
Itinerant: 754,
Region: "Western"
},
{
Service: "Air Force",
Permanent: 9,
Itinerant: 2,
Region: "Eastern"
},
{
Service: "Army",
Permanent: 6,
Itinerant: 7,
Region: "Western"
},
{
Service: "Air Force",
Permanent: 9,
Itinerant: 2,
Region: "Eastern"
}
];
var aggregates = data.map(function (o) {
// map original objects to new ones with zeroed-out data
return {
Service: o.Service,
Permanent: 0,
}
}).filter(function (o, index, a) {
// filter the list to have only unique `Service` properties
var service = o.Service;
var i;
for (i = 0; i < a.length; i += 1) {
if (a[i].Service === service) {
// break out of the loop when the first matching `Service` is found.
break;
}
}
// `i` is now the index of the first matching `Service`.
// if it's the first occurrence of that `Service`, keep it, otherwise ditch it.
return i === index;
});
data.forEach(function (o) {
// loop through the aggregate list and get the matching `Service` property.
var agg = aggregates.filter(function (p) {
return o.Service === p.Service;
})[0]; // first element is the match.
// sum the `Permanent` properties.
agg.Permanent += o.Permanent;
});
// now that the data has been aggregated, transform it into the needed structure.
var c3data = aggregates.map(function (d) {
return [d.Service, d.Permanent];
});
console.log(JSON.stringify(c3data));

I want to order items returned from an object based on the property key value and then assign a ranking to each item in the interface (1,2,3...etc)

Newb here with a basic question. Here is my object:
{TestOne: 12, TestTwo: 6, TestThree: 4, TestFour: 2}
I've looped through it using a for-in loop and grabbed the properties and displayed it in my UI like so:
TestOne: 12
TestTwo: 6
TestThree: 4
TestFour: 2
I need to be able to display this by giving each item a numerical ranking (1,2,3,4...etc) and then displaying them by that ranking (corresponding to their actual order). In other words, what my users need to see on the screen is:
TestOne: 4
TestTwo: 3
TestThree: 2
TestFour: 1
Not 12,6,4,2, etc. This is all new to me but I've been trying to figure out the best way to implement this and have not found anything that I understand to this point.
Here is my code. I feel like this should be easy but it is super frustrating!
var rank = "";
var title = objArrayTwo[i].Title;
var summary ={};
summary = groupBy(objArrayTwo);
for (var prop in summary) {
if (summary.hasOwnProperty(prop)) {
if(title == `${prop}`){
rank = `${summary[prop]}`;
}
}
}
function groupBy(items){
var result= {};
var sum;
$.each(items, function(index, item) {
sum = result[item.RequestName] || 0;
result[item.RequestName] = sum + parseInt(item.Rank);
});
return result;
}
var obj = {TestOne: 12, TestTwo: 6, TestThree: 4, TestFour: 2};
// sort the array
var arr = Object.entries(obj);
arr.sort((a, b) => a[1] - b[1]);
// enumerate the array the index would be the rank
var arr_with_rank = arr.map((data, index) => [data[0], index+1]).reverse()
arr_with_rank.forEach(x => console.log(x[0] + ": " + x[1]));
ES5 solution
var resultDataObject = {
TestOne: 12,
TestTwo: 6,
TestThree: 4,
TestFour: 2
};
var descendingSort = function(a, b) {
return resultDataObject[b] - resultDataObject[a]
};
var sortedResultKeys = Object.keys(resultDataObject).sort(descendingSort);
var resultWithRank = sortedResultKeys.map(function(k, i) {
return {
title: k,
score: resultDataObject[k],
rank: i + 1
};
})
console.log(resultWithRank)

Using forEach to iterate an array, add to object and use the forEach index for the object key doesn't work

I'm wanting to iterate of an array using forEach, then add the values of that array to an object.
When adding to the object I want to be able to name the object keys with an increasing number, just like when using a normal for loop using the i variable counter.
With the code below:
var myArray = [123, 15, 187, 32];
var testObj = {}
myArray.forEach(function (value, i) {
console.log('%d: %s', i, value);
var objectKeyName = "item" + i; // ⬅️ here
testObj.objectKeyName = value;
});
console.log(testObj);
I would expect testObj to be
{
item0: 123,
item1: 15,
item2: 187,
item3: 32,
}
instead it outputs:
{objectKeyName: 32}
Why is this?
Any help is greatly appreciated! 😀
The issue is because you're using a variable as the property name. In that situation you cannot use dot notation, and must instead use bracket notation to access the object.
The reason you see only one item in the object currently is because you only set the objectKeyName property, and overwrite it in each iteration of the forEach.
var myArray = [123, 15, 187, 32];
var testObj = {}
myArray.forEach(function(value, i) {
var objectKeyName = "item" + i;
testObj[objectKeyName] = value; // Note the [] surrounding the variable name here
});
console.log(testObj);
Use testObj[objectKeyName] = value instead of testObj.objectKeyName = value. The dot notation assigns the key value literally.
Here you go with the solution
var myArray = [123, 15, 187, 32];
var testObj = {}
myArray.forEach(function(value, i) {
testObj["item" + i] = value;
});
console.log(testObj);
var myArray = [123, 15, 187, 32];
var testObj = [];
myArray.forEach(function (value, i) {
console.log('%d: %s', i, value);
var objectKeyName = "item" + i; // ⬅️ here
testObj.push(objectKeyName + ":" + value);
});
console.log(testObj);
testObj.forEach(function (value, i) {
console.log(value);
});

Checking whether certain item is in certain array using javascript

I have 10 different arrays. Each array has different numbers.
array1 = [1,2,3,4,5]
array2 = [6,7,8,9,10]
...
array 10 = [51,52,53,54]
let's say I pass in 7. Then I want to know which array it is from and want to return array number. So in this case it is going to be 2.
Should I write a switch statement for each array? Appreciate it in javascript.
try:
var arrays = [array1, array2, ..., array10];
for(var i=0; i<arrays.length; ++i) {
if (arrays[i].indexOf(value) != -1) {
console.log('found in array' + (i+1));
}
}
You cannot directly retrieve the name of array.The reason is this variable is only storing a reference to the object.
Instead you can have a key inside the same array which represent its name. Then indexOf can be used to find the array which contain the number , & if it is so, then get the array name
var array1 = [1,2,3,4,5];
array1.name ="array1";
var array2 = [6,7,8,9,10];
array2.name ="array2";
var array10 = [51,52,53,54]
array10.name ="array10";
var parArray = [array1,array2,array10]
function _getArrayName(number){
for(var o=0;o<parArray.length;o++){
var _tem = parArray[o];
if(parArray[o].indexOf(number) !==-1){
console.log(parArray[o].name);
}
}
}
_getArrayName(6) //prints array2
jsfiddle
One fast method should be using hash tables or as i would like to call LUT. Accordingly this job boils down to a single liner as follows;
var arrs = {
arr1 : [1,2,3,4,5],
arr2 : [6,7,8,9,10],
arr3 : [12,14,16,17],
arr4 : [21,23,24,25,27,20],
arr5 : [31,34,35,39],
arr6 : [45,46,44],
arr7 : [58,59],
arr8 : [66,67,69,61],
arr9 : [72,73,75,79,71],
arr0 : [81,85,98,99,90,80]
},
lut = Object.keys(arrs).reduce((p,c) => {arrs[c].forEach(n => p[n]=c); return p},{}),
findar = n => lut[n];
document.write("<pre>" + findar(12) + "</pre>");
One way to do this is have the arrays in an object and iterate over the keys/values. This method doesn't presume the arrays (and therefore their names) are in sequential order.
Note: this will always return a the first match from the function and terminate the search.
var obj = {
array1: [1, 2, 3, 4, 5],
array2: [6, 7, 8, 9, 10],
array3: [51, 52, 53, 54],
array4: [51, 52, 53, 54, 7]
}
function finder(obj, test) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (obj[key].indexOf(test) > -1) {
return key.match(/\d+/)[0];
}
}
return false;
}
finder(obj, 7); // '2'
DEMO
If you want to find all instances of a value in all arrays the function needs to be altered slightly.
function finder(obj, test) {
var keys = Object.keys(obj);
var out = [];
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (obj[key].indexOf(test) > -1) {
out.push(key.match(/\d+/)[0]);
}
}
return out;
}
finder(obj, 7); // ['2', '4']
DEMO

Adding Similar Items Together in a Javascript Array

I'm trying to loop through an array in order to group and count totals.
For example:
var farm = [['Cats', 3], ['Cats', 4], ['Mice', 2], ['Dogs', 5]];
I would like to go through and first see 'Cats', then add all the cat values up, then repeat the process for other unique categories.
The final output would be:
Cats (7)
Mice (2)
Dogs (5)
Currently, I'm trying to accomplish it this way, but I'm obviously making a rookie mistake somewhere.
var farm = [];
farm.push(['Cats', 3], ['Cats', 4], ['Mice', 2], ['Dogs', 5]);
var animalCounter = function(array){
var list = '';
for(var i = 0; i<array.length; i++){
var animalID = array[i][0];
var animalCount = 0;
for (var x; x<array.length; x++){
if(animalID == array[x][0]){
animalCount += array[x][0] - 1;
}
list += animalID + " (" + animalCount + ")\n";
}
}
alert(list);
}
animalCounter(farm);
use an object to add similar animals together
var farm = [];
farm.push(['Cats', 3], ['Cats', 4], ['Mice', 2], ['Dogs', 5]);
var o = {};
for (var i=farm.length; i--;) {
var key = farm[i][0],
val = farm[i][1];
o[key] = key in o ? o[key] + val : val;
}
FIDDLE
Then it's easy to create the list
for (var key in o) {
list += key + " (" + o[key] + ")\n";
}
FIDDLE
You're missing an outer layer of brackets:
var farm = [['Cats', 3], ['Cats', 4], ['Mice', 2], ['Dogs', 5]];
What you had will end up being the same as
var farm = ['Dogs', 5];
The comma operator does strange things, especially in a var statement because , also separates individual variable declarations and initializations.
I'd probably do it a bit differently:
var farm = [];
farm.push(['Cats', 3], ['Cats', 4], ['Mice', 2], ['Dogs', 5]);
var animalCounter = function(array){
var animalObject = {};
for(var i = 0; i<array.length; i++){
var animalID = array[i][0];
var animalCount = array[i][1];
if(animalObject[animalID]) {
animalObject[animalID] += animalCount;
} else {
animalObject[animalID] = animalCount;
}
}
return animalObject;
}
The first function, animalCounter, creates an object that maps animal names to the numbers in the array. So for your example, it will return an object that looks like this:
{ 'Cats': 7, 'Mice': 2, 'Dogs': 5 }
From there, once you have the object, it's trivial to create a string to output this data in whatever format you like:
var counter = animalCounter(farm);
var list = '';
for(var key in counter) {
list += key + ': ' + counter[key] + ', ';
}
console.log(list); //=> Cats: 7, Mice: 2, Dogs: 5,
The reason your initial function didn't work is because it didn't take into account that there might be more than one instance of the same animal in your array. If your original array was [['Cats', 7], ['Mice', 2], ['Dogs', 5]] it would have been fine, but because your Cats entry was split into ['Cats', 4], ['Cats', 3], your original code saw that as two distinct animals. Notice in my function:
if(animalObject[animalID]) {
animalObject[animalID] += animalCount;
} else {
animalObject[animalID] = animalCount;
}
The code checks to see if the animal is already stored in the new object, and if it is, increments the count, rather than creating a new counter from 0. This way it deals with any duplicates.
I see three problems:
setting animalCounter equal to the number of animals - the new number of animals replaces whatever might already have been stored in animalCounter, so nothing is added up here
creating the animalCounter variable within the loop - if var animalCount is inside the loop, then you actually have a completely new variable for each element of the array
using a single variable for all the types of animals
Instead, try this:
var farm = [];
farm.push(['Cats', 3], ['Cats', 4], ['Mice', 2], ['Dogs', 5]);
var animalCounter = function (array) {
var list = '',
catsCount = 0,
miceCount = 0,
dogsCount = 0;
for (var i = 0; i < array.length; i++) {
var animalID = array[i][0];
var animalCount = array[i][1];
if (animalID === 'Cats') {
catsCount += animalCount;
} else if (animalID === 'Mice') {
miceCount += animalCount;
} else if (animalID === 'Dogs') {
dogsCount += animalCount;
}
}
list = 'Cats(' + catsCount + ') Mice(' + miceCount + ') Dogs(' + dogsCount + ')';
alert(list);
}
animalCounter(farm);
There are separate variables for each type of animal, and the value in the array is added onto the correct counter variable.
Or, for a more organized solution:
var farm = []; farm.push(['Cats', 3], ['Cats', 4], ['Mice', 2],
['Dogs', 5]);
var animalCounter = function (array) {
var list = '',
animalCounters = {};
for (var i = 0; i < array.length; i++) {
var animalID = array[i][0];
var animalCount = array[i][1];
animalCounters[animalID] = (animalCounters[animalID] || 0) + animalCount;
}
for (var id in animalCounters) {
list += id + " (" + animalCounters[id] + ")\n";
}
alert(list);
} animalCounter(farm);
In this code, the animalCounters variable is an object. JavaScript objects act like associative arrays, which lets us use the animal ID string as a "key" to an integer that is the animal sum. Each type of animal is a property of the animalCounters object, with the sum for that type of animal as its value.
I used some slightly obscure notation here, so I'll explain.
animalCounters[animalID]
This is just a different method of referring to properties of an object. In JavaScript, animalCounters.Cats and animalCounters["Cats"] access the same thing. But, if you don't know for sure that the type of animal will be Cats, you need "Cats" (or whatever other kind of animal) to be in a variable. The animalCounters["Cats"] notation takes a string, so you can say this and it will work:
var animalID = "Dogs";
alert(animalCounters[animalID);// returns animalCounters.Dogs
animalCounters[animalID] = (animalCounters[animalID] || 0) + animalCount;
Here, the (animalCounters[animalID] || 0) is saying that if animalCounters[animalID] already has a value, add that value to animalCount, otherwise add 0 to animalCount. This is necessary because if you try to add animalCounters[animalID] to animalCount before animalCounters[animalID] has been set to anything, the addition won't work right.
Just for funsies... (not very practical)
var farm = [['Cats', 3], ['Cats', 4], ['Mice', 2], ['Dogs', 5]];
farm.reduce(function(a, b, i){
var r = (i==1 ? a.slice() : a),
j = r.indexOf(b[0]);
if(j >= 0){
r[j+1] += b[1];
return r;
} else return r.concat(b);
}).reduce(function(a, b, i){
return i%2 ? a+' ('+b+')' : a+'\n'+b;
});
Rough explanation:
Iterate over each element of farm reducing the 2D array to a flat array where every odd index is the "count" that corresponds to the previous element - taking note to check if the "key" in the even index already exists in the array (in which case update it's count respectively). The slice call is in there just to make sure that the original array is left unmodified. This results in an array looking like:
["Cats", 7, "Mice", 2, "Dogs", 5]
This new array is reduced once more, this time concatenating each element into a single string - formatting dependent on whether or not the current iteration has an odd or even index.
Array.reduce is one of those functions that isn't supported in older browsers (if that is important) but there's a polyfill available on the MDN site.
When you access the amount of animals of a certain kind you made a simple mistake:
animalCount += array[x][0] - 1;
farm[x][0] will always return the animal's name which is a string, so when trying to subtract 1 from it it will result in NaN (Not a Number).
Also the first for loop: for(var i; i<array.length; i++){ ... cycles through all the array slots even if they were already counted, so cats would be counted twice so instead of cats counted as 7 they would amount to 14.
You need to create a copy of array and take off the slots already counted. The tricky part is copying the array by value and so that any changes to Temp won't change farm (see Copying Arrays):
var farm = [];
farm.push(['Cats', 3], ['Cats', 4], ['Mice', 2], ['Dogs', 5]);
function countAnimals(array) {
var Temp = [];
var d = 0;
//This while loop copies the array
while (d < farm.length) {
var s = array[d].toString();
Temp.push(s.split(","));
d++;
}
var list = "";
var done = 0;
while (done < array.length) {
if(Temp[0][1] == "Done") {
Temp.shift();
} else {
var animalID = Temp[0][0];
var count = parseFloat(Temp[0][1]);
Temp.shift();
var i = 0;
while (i < Temp.length) {
if(Temp[i][0] == animalID) {
count = count + parseFloat(Temp[i][1]);
Temp[i][1] = "Done";
}
i++;
}
list = list + "\n" + animalID + "("+count+")";
}
done++;
}
alert(list);
}
countAnimals(farm);

Categories

Resources