sort javascript array in object, maintaining key - javascript

I have a javascript object with two array's as shown,
var Object = {'name': [Matt, Tom, Mike...], 'rank': [34,1,17...]};
I am trying to sort by rank 1,2,3.. but keep the name associated with the rank.
Object.name[0] // tom
Object.rank[0] // tom's rank of 1.
Should I reconfigure my object to make sorting easier?
I am currently using the
Object.rank.sort(function(a,b){return a-b});
to order rank, but the name does not stay with it.
All help appreciated. Thanks!

Yes, reconfigure. Say you had this instead:
var people = [{name:"Matt", rank:34}, {name:"Tom", rank:1}, {name:"Mike", rank:17}];
Then you could sort like this:
people.sort(function(a, b) {
return a.rank - b.rank;
}
Edit
Since you have parallel lists, just zip them together:
var people = [];
for (var i = 0; i < Object.name.length; i++) {
people.push({name:Object.name[i], rank:Object.rank[i]});
}

The real world object:
o = {name: ['Matt', 'Tom', 'Mike'], rank: [34,1,17]};
Make an array for better data structure:
var arr =[];
o.name.forEach(function(name, i){
arr.push({name: name, rank: o.rank[i]})
});
Sort by rank:
arr.sort(function(a,b){return a.rank - b.rank});
Sort by name:
arr.sort(function(a,b){return a.name- b.name});
Revert back to your original data structure:
o = {name:[], rank:[]}
arr.forEach(function(item){
o.name.push(item.name);
o.rank.push(item.rank);
});

Well, yes, if the i-th object in names array is connected to the i-th object in the rank array, you should represent it that way. This means, you should use a Person (or whatever it is) object with two properties: name and rank.
// person constructor
function Person(name, rank) {
this.name = name;
this.rank = rank;
}
// create the object with the array
var myObject = {
myArray: new Array()
};
// populate the array
myObject.myArray.push(new Person('Matt', 34));
myObject.myArray.push(new Person('Tom', 1));
myObject.myArray.push(new Person('Mike', 17));
// sort the Person objects according to their ranks
myObject.myArray.sort(function(a, b) {
return b.rank - a.rank;
});

You'll have to write your own sort function that for each sorting operation, remembers what index comes where per iteration in the ranks array. The do the same move from source index to destination index in the names array. (edit) One algortihm for this from the top of my head is the bubblesort, look it up.
The other option is to look for some kind of "map" collection implementation.

Related

How to pass by reference (simulation)

I'm having a trouble approaching to this problem, althouh I have a working solution I doubt it is the most optimal one.
Here is the problem:
Imagine an array of objects, each object represents a person.
var people = [
{id:1, name:"John", points: 50},
{id:2, name:"Mark", points: 80},
{id:3, name:"Peter", points: 25},
];
In our people array, we have 3 persons with an unique id property.
Now imagine that we have multiple functions that modify/update person objects.
Obviously this wouldn't work, since the outer object won't be affected
by the changes made in incrementPoints() function.
var myPerson = people[0];
incrementPoints(myPerson){
myPerson.points++;
};
// myPerson.points = 50
addPoints(myPerson); // We're passing an person object to addPoints;
// myPerson.points = 50 (Did not change, not affected by addPoints)
This however, would work! But the price we pay is the cost of
iteration through persons array and matching the id of desired person.
function getPersonIndexById(personId){
// Iterate through all persons in 'people' array
for(var index = 0; index < people.length; index++)
people[i].id === personId ? return index : continue;
}
function incrementPoints(personId){
people[ getPersonIndexById(personId) ].points++;
}
function decrementPoints(personId){
people[ getPersonIndexById(personId) ].points--;
}
Is there a better/simpler/cleaner/intended concept for dealing with such situations. Obviously, the ideal solution would be a pass by &reference but javascript does not allow that. I'm not trying to do achieve an useless hack, but rather get understanding of what developers do when they stumble upon similar situations and how they solve them.
var people = [
{id:1, name:"John", points: 50},
{id:2, name:"Mark", points: 80},
{id:3, name:"Peter", points: 25},
];
var myPerson = people[0];
function incrementPoints(myPerson){
myPerson.points++;
};
function addPropertyToPerson(myPerson, lastname) {
myPerson.lastName = lastname;
}
// The properties of the myPerson object can still be modified since
// the value passed in to the function is the reference to the object
incrementPoints(myPerson);
console.log(myPerson);
// Similarly, additional properties can still be added since
// the value passed in to the function is the reference to
// the outer object
addPropertyToPerson(myPerson, "smith");
console.log(myPerson);
Objects are passed as reference
You defined a function with name incrementPoints with a wrong syntax but you are calling addPoints. I hope it is a typo/mistake.
if I suppose it is addPoints then it is working fine. You will get 51 points after that function call.
You can use Array.prototype.find to find a person by id, it is builtin and it will be fast.
function incrementPoints(personId){
var person = people.find(function(p){
return p.id === personId;
});
if (person) {
person.points++;
}
}
I would prefer to convert array to map while getting data.
var personMap = {}; people.forEach(function(person){
personMap[id] = person;
});
With this now you have map
You can get person as personMap[personId]
Here creating map is one time activity so you need a single round of iteration
Please bear with my response.. I am replying from mobile so formatting might be not good

How would you count the instances of objects in arrays within an array

So, essentially I am getting a set of records as an array of objects like
[{name: tyler, categories: ["friends", "neighbor"]}, {name: joe, categories: ["friends"]}].
and I want to count the contents of the internal array instances. So in this example, the return would be friends: 2 and neighbor: 1. As some background info, I am getting a collection of records from mongo within the meteor framework. I am then using fetch() to convert these records to an array of objects like above. I then want to use these objects to create a pie graph based on the counts of the specific instances of each object within the inner array of each object (these objects would be the ones returned by the db query) within the outer array.
You can write a simple function to count your categories counts and store the result in a dictionary of key/value pairs.
function countCategories(docs){
// accumulate results inside a JS object acting as a key/value dict
var result = {};
docs.forEach(function(doc){
doc.categories.forEach(function(category){
// initialize count to 0 when a new key is found
if(_.isUndefined(result[category])){
result[category] = 0;
}
// increment the corresponding count
result[category]++;
});
});
return result;
}
Given the sample data in your question, this function will return :
Object {friends: 2, neighbor: 1}
EDIT :
You can then convert this dictionary to an array of objects.
function convertToArray(dict){
var result = [];
_.each(dict, function(value, key){
var object = {
category: key,
count: value
};
result.push(object);
});
return result;
}
Using underscore and reduce:
result = _.reduce( data, function( counter, o ) {
_.each( o.categories, function(c){
counter[c] = 1 + _.result(counter, c, 0);
});
return counter;
}, {});
Demo in this fiddle
reduce goes through your array (first arg) and applies
the function you give it (second arg) and a starting value for
the memo (third arg). This memo is passed to each call to
your function as the first argument, you can us it to store
stuff you want to remember.
I've set the starting value for the memo to be an empty object
which we will use as a counter.
result = _.reduce( data, function( counter, o ) {
// current element of the array: o
// stuff you want to remember for: counter
return counter;
}, {});
You might attach a function to the array and count the elements inside of it.
yourArray = [1,2,3];
yourArray.countElements = function(){
var elements=0;
for(x=0;this[x]!=undefined;x++){
instances++
}
return instances;
};
yourArray.countElements(); // outputs 3
Modify this, using "neighbors" and "friends" instead of "elements" and counting them only if this["categories"]["the_category"] is different of undefined.
Also you could attach it to Array.prototype

Sort javascript key/value pairs inside object

I have some problem with sorting items inside object. So I have something like this:
var someObject = {
'type1': 'abc',
'type2': 'gty',
'type3': 'qwe',
'type4': 'bbvdd',
'type5': 'zxczvdf'
};
I want to sort someObject by value, and this is where I have problem.
I have sorting function that should return key/value pairs sorted by value:
function SortObject(passedObject) {
var values = [];
var sorted_obj = {};
for (var key in passedObject) {
if (passedObject.hasOwnProperty(key)) {
values.push(passedObject[key]);
}
}
// sort keys
values.sort();
// create new object based on Sorted Keys
jQuery.each(values, function (i, value) {
var key = GetKey(passedObject, value);
sorted_obj[key] = value;
});
return sorted_obj;
}
and function to get key:
function GetKey(someObject, value) {
for (var key in someObject) {
if (someObject[key] === value) {
return key;
}
}
}
The problem is in last part when creating new, returning object - it's sorted by key again. Why? And this is specific situation when i have to operate on object NOT on array (yes I know that would be easier...)
Does anyone know how to sort items in object?
Plain objects don't have order at all. Arrays -that are a special types of objects- have.
The most close thing that you can have is an array with the object values sorted . Something like, for example:
_valuesOfAnObjectSorted = Object.keys(object).map(function(k){ return object[k]; }).sort();
You have two possibilities:
Refactor your object into an array
Something like this:
var myObj = [
['type1', 'abc'],
['type2', 'gty'],
...
];
Or even better, since using it somewhere would not rely on array positions but full named keys:
var myObj = [
{name: 'type1', val:'abc'},
{name: 'type2', val:'gty'},
...
];
Use your object with an auxiliar array
Wherever you want to use your object ordered by the keys, you can extract the keys as an array, order it and traverse it to access the object
var ordKeys = Object.keys(myObj).sort(); // pass inside a function if you want specific order
var key;
for (var i = 0, len = ordKeys.length; i < len; i +=1) {
key = ordKeys[i]
alert(key + " - " + myObj[key]);
}
Combination of both of them
If the object is not constructed by you, but comes from somewhere else, you can use the second option approach to construct an array of objects as in the first option. That would let you use your array anywhere with perfect order.
EDIT
You might want to check the library underscore.js. There you have extremely useful methods that could do the trick pretty easily. Probably the method _.pairs with some mapping would do all the work in one statement.

Javascript object sum value based on properties effeicently

I do have three objects inside an array in JS like below
[{"2013-03-02T00:00": 300}, {"2013-03-01T00:00": 200},{"2013-03-02T00:00": 50}]
I want something like below as output from the above array.
[{"2013-03-02T00:00": 350} , {"2013-03-01T00:00": 200}]
It can be done by looping through and adding, are there any efficient way I can do it?
Thanks in advance.
var myList = [{"2013-03-02T00:00": 300}, {"2013-03-01T00:00": 200},{"2013-03-02T00:00": 50}];
var result = {};
var item = null, key = null;
for(c=0; c<myList.length; c++) {
item=myList[c];
key = Object.keys(item)[0];
item=item[key];
if(!result[key]) result[key] = item;
else result[key] += item;
}
console.log(result);
I leave it as an exercise for the reader to put the result into the requested form. (after all you should solve at least some part of your problem yourself :)
If you can add the key "date" as another value of the object you can sort and group it, this will be quicker and more optimized when you have more values in the array.
[UPDATE]: fixed a little bug.
var arr = [{"date":"2013-03-02T00:00",val: 300}
, {"date":"2013-03-01T00:00",val: 200}
,{"date":"2013-03-02T00:00",val: 50}];
function groupArray(arr){
if(arr.length===0){return [];}
var pref,i;
// sort by date
arr.sort(function(a,b){
return (a.date>b.date)?1:(a.date<b.date)?-1:0;
});
// loop through the array grouping objects by date
pref=arr[0].date;
for(i=1;i<arr.length;i++){
if(arr[i].date===pref){
//set the total
arr[i-1].val=arr[i-1].val+arr[i].val;
//remove the element
arr.splice(i,1);
// set i one back
i--;
}
pref=arr[i].date;
}
return arr;
}
console.log(groupArray(arr));
A more complicated example is when you dynamically want to provide the key to sort on, in the example above the key is "hard coded" to be date but you maybe need to group on another key value, the following is a piece of code I had laying around that I've simplified to group by one key (original used an array of keys). You can pass a onMerge variable that should be a function that handles how to merge 2 items. This function is not generic and is specific to adding the val properties of the to be merged objects.
var arr = [{"date":"2013-03-02T00:00",val: 300}
, {"date":"2013-03-01T00:00",val: 200}
, {"date":"2013-03-01T00:00",val: 200}
, {"date":"2013-03-01T00:00",val: 200}
, {"date":"2013-03-01T00:00",val: 200}
,{"date":"2013-03-02T00:00",val: 50}];
/**
* #param arr is the array to be merged
* #param key is the key to use for merge
* (like date) will merge on items with same
* date value
* #param onMerge function to call when 2 items
* are merged with the 2 items as parameters
**/
function groupArray(arr,key,onMerge){
if(arr.length===0){return [];}
var pref,i;
// sort by key
arr.sort(function(a,b){
return (a[key]>b[key])?1:(a[key]<b[key])?-1:0;
});
// loop through the array grouping objects by key
pref=arr[0][key];
for(i=1;i<arr.length;i++){
if(arr[i][key]===pref){
//merge 2 items, call the onMerge callback
arr[i-1]=onMerge(arr[i-1],arr[i]);
//remove the element
arr.splice(i,1);
// set i one back
i--;
}
pref=arr[i][key];
}
return arr;
}
// functon that will be called when 2 items are merged
// stay will stay and gone will be gone
// this function is specific to your data type
function onMergeCallback(stay,gone){
stay.val=stay.val+gone.val;
return stay;
}
console.log(groupArray(arr,"date",onMergeCallback));
You might want to have a look at underscore.js, which has implementations for a lot of helpful, efficient functions for manipulating and dealing with data. Specifically, you'd want to have a look at the _.groupBy function:
var data = [{"2013-03-02T00:00": 300}, {"2013-03-01T00:00": 200},{"2013-03-02T00:00": 50}]
_.groupBy(data, function(obj) { return Object.keys(obj)[0]; })
You'd still have to iterate and sum the values, but that's why we have reduce functions!
If you want something more specific to your use case, I would have a look at the source on github.
https://github.com/documentcloud/underscore

Count and record partially repeating objects in objects in a new Array Javascript

I need to work through a source array of objects, many of the objects in the array have three property values which will be the same. It is these values that will be used to create a new object and push it on to destination array. When another object on the source array comes up with the same three property values as one of the objects on the destination array the matching object on the destination array will have its visit count incremented by one.
To help you understand, in the source array each object is a record of a meal that belongs to a user. In the second array I need to store the user details and the number of their meals.
I've tried a few solutions which have failed like the one below. I thought that the code below would create a literal object, check if it is in the destination array by finding it's indexOf (-1 for not found) and if it's not found push it on. The problem is that it never finds the objects, if I search through 3000 meals the second array ends up 3000 long!
The code below does not try to store the visit count.
userArray = new Array();
for (var i = 0; i < filteredObjects.length; i++) {
var user = {
forname: filteredObjects[i].forname,
surname: filteredObjects[i].surname,
dincat: filteredObjects[i].dincat,
};
var index = userArray.indexOf(user);
if (index = -1) {
userArray.push(user);
}
}
This doesn't work because the user object that you create in the loop is not the same as any of the objects you added inside userArray. They might contain the same keys and values, but strictly speaking (===) they're not the same.
To help your code, you can add a user map object:
var userArray = new Array(),
userMap = {};
for (var i = 0, item; item = filteredObjects[i]; ++i) {
var userKey = item.forname + '-' + item.surname + '-' + item.dincat;
if (!(userKey in userMap)) {
userArray.push({
forname: filteredObjects[i].forname,
surname: filteredObjects[i].surname,
dincat: filteredObjects[i].dincat,
});
userMap[userKey] = true;
}
}
The user map is an object that uses its keys to determine whether you have already inserted a user before. This works by choosing a user identifier, in your case the combination of first name, surname and dincat.
indexOf compares search element to elements of the Array using strict equality. If both operands are objects, they're compared as objects, and the equality test is true only if both refer the same object. In your case, you create a new object every time and compare it with the existing objects in the array and it will return false.
There's several syntax errors there, but the major reason that code's not working is that what you're doing is creating a new object with the value of the object you currently at in the array loop, then looking for that new object in the array, so it's never going to be there.
I'm actually a little curious myself if there's a more efficient solution, but one possibility is
var demo = [
{a: 'green', b: 'blue', c:'red'},
{a: 'blue', b: 'green', c: 'not blue'},
{a: 'green', b: 'blue', c: 'red'}
],
records= {};
for (var i=0; i<demo.length; i++){
if (records.hasOwnProperty(demo[i].a) &&
records[demo[i].a].hasOwnProperty(demo[i].b) &&
records[demo[i].a][demo[i].b].hasOwnProperty(demo[i].c)
){
//do something with a match
} else {
if (!records.hasOwnProperty(demo[i].a))
records[demo[i].a] = {};
if (!records[demo[i].a].hasOwnProperty(demo[i].b))
records[demo[i].a][demo[i].b] = {};
records[demo[i].a][demo[i].b][demo[i].c] = 'yes';
//no match found
}
}
Just substitute your values in for a, b, and c and it should work.

Categories

Resources