I need to store some statistics using JavaScript in a way like I'd do it in C#:
Dictionary<string, int> statistics;
statistics["Foo"] = 10;
statistics["Goo"] = statistics["Goo"] + 1;
statistics.Add("Zoo", 1);
Is there an Hashtable or something like Dictionary<TKey, TValue> in JavaScript?
How could I store values in such a way?
Use JavaScript objects as associative arrays.
Associative Array: In simple words associative arrays use Strings instead of Integer numbers as index.
Create an object with
var dictionary = {};
JavaScript allows you to add properties to objects by using the following syntax:
Object.yourProperty = value;
An alternate syntax for the same is:
Object["yourProperty"] = value;
If you can, also create key-to-value object maps with the following syntax:
var point = { x:3, y:2 };
point["x"] // returns 3
point.y // returns 2
You can iterate through an associative array using the for..in loop construct as follows
for(var key in Object.keys(dict)){
var value = dict[key];
/* use key/value for intended purpose */
}
var associativeArray = {};
associativeArray["one"] = "First";
associativeArray["two"] = "Second";
associativeArray["three"] = "Third";
If you are coming from an object-oriented language you should check this article.
All modern browsers support a JavaScript Map object. There are a couple of reasons that make using a Map better than Object:
An Object has a prototype, so there are default keys in the map.
The keys of an Object are Strings, where they can be any value for a Map.
You can get the size of a Map easily while you have to keep track of size for an Object.
Example:
var myMap = new Map();
var keyObj = {},
keyFunc = function () {},
keyString = "a string";
myMap.set(keyString, "value associated with 'a string'");
myMap.set(keyObj, "value associated with keyObj");
myMap.set(keyFunc, "value associated with keyFunc");
myMap.size; // 3
myMap.get(keyString); // "value associated with 'a string'"
myMap.get(keyObj); // "value associated with keyObj"
myMap.get(keyFunc); // "value associated with keyFunc"
If you want keys that are not referenced from other objects to be garbage collected, consider using a WeakMap instead of a Map.
Unless you have a specific reason not to, just use a normal object. Object properties in JavaScript can be referenced using hashtable-style syntax:
var hashtable = {};
hashtable.foo = "bar";
hashtable['bar'] = "foo";
Both foo and bar elements can now then be referenced as:
hashtable['foo'];
hashtable['bar'];
// Or
hashtable.foo;
hashtable.bar;
Of course this does mean your keys have to be strings. If they're not strings they are converted internally to strings, so it may still work. Your mileage may vary.
Since every object in JavaScript behaves like - and is generally implemented as - a hashtable, I just go with that...
var hashSweetHashTable = {};
In C# the code looks like:
Dictionary<string,int> dictionary = new Dictionary<string,int>();
dictionary.add("sample1", 1);
dictionary.add("sample2", 2);
or
var dictionary = new Dictionary<string, int> {
{"sample1", 1},
{"sample2", 2}
};
In JavaScript:
var dictionary = {
"sample1": 1,
"sample2": 2
}
A C# dictionary object contains useful methods, like dictionary.ContainsKey()
In JavaScript, we could use the hasOwnProperty like:
if (dictionary.hasOwnProperty("sample1"))
console.log("sample1 key found and its value is"+ dictionary["sample1"]);
If you require your keys to be any object rather than just strings, then you could use my jshashtable.
Note:
Several years ago, I had implemented the following hashtable, which has had some features that were missing to the Map class. However, that's no longer the case — now, it's possible to iterate over the entries of a Map, get an array of its keys or values or both (these operations are implemented copying to a newly allocated array, though — that's a waste of memory and its time complexity will always be as slow as O(n)), remove specific items given their key, and clear the whole map.
Therefore, my hashtable implementation is only useful for compatibility purposes, in which case it'd be a saner approach to write a proper polyfill based on this.
function Hashtable() {
this._map = new Map();
this._indexes = new Map();
this._keys = [];
this._values = [];
this.put = function(key, value) {
var newKey = !this.containsKey(key);
this._map.set(key, value);
if (newKey) {
this._indexes.set(key, this.length);
this._keys.push(key);
this._values.push(value);
}
};
this.remove = function(key) {
if (!this.containsKey(key))
return;
this._map.delete(key);
var index = this._indexes.get(key);
this._indexes.delete(key);
this._keys.splice(index, 1);
this._values.splice(index, 1);
};
this.indexOfKey = function(key) {
return this._indexes.get(key);
};
this.indexOfValue = function(value) {
return this._values.indexOf(value) != -1;
};
this.get = function(key) {
return this._map.get(key);
};
this.entryAt = function(index) {
var item = {};
Object.defineProperty(item, "key", {
value: this.keys[index],
writable: false
});
Object.defineProperty(item, "value", {
value: this.values[index],
writable: false
});
return item;
};
this.clear = function() {
var length = this.length;
for (var i = 0; i < length; i++) {
var key = this.keys[i];
this._map.delete(key);
this._indexes.delete(key);
}
this._keys.splice(0, length);
};
this.containsKey = function(key) {
return this._map.has(key);
};
this.containsValue = function(value) {
return this._values.indexOf(value) != -1;
};
this.forEach = function(iterator) {
for (var i = 0; i < this.length; i++)
iterator(this.keys[i], this.values[i], i);
};
Object.defineProperty(this, "length", {
get: function() {
return this._keys.length;
}
});
Object.defineProperty(this, "keys", {
get: function() {
return this._keys;
}
});
Object.defineProperty(this, "values", {
get: function() {
return this._values;
}
});
Object.defineProperty(this, "entries", {
get: function() {
var entries = new Array(this.length);
for (var i = 0; i < entries.length; i++)
entries[i] = this.entryAt(i);
return entries;
}
});
}
Documentation of the class Hashtable
Methods:
get(key)
Returns the value associated to the specified key.
Parameters:
key: The key from which to retrieve the value.
put(key, value)
Associates the specified value to the specified key.
Parameters:
key: The key to which associate the value.
value: The value to associate to the key.
remove(key)
Removes the specified key, together with the value associated to it.
Parameters:
key: The key to remove.
clear()
Clears the whole hashtable, by removing all its entries.
indexOfKey(key)
Returns the index of the specified key, according to the order entries have been added.
Parameters:
key: The key of which to get the index.
indexOfValue(value)
Returns the index of the specified value, according to the order entries have been added.
Parameters:
value: The value of which to get the index.
Remarks:
Values are compared by identity.
entryAt(index)
Returns an object with a key and a value properties, representing the entry at the specified index.
Parameters:
index: The index of the entry to get.
containsKey(key)
Returns whether the hashtable contains the specified key.
Parameters:
key: The key to look for.
containsValue(value)
Returns whether the hashtable contains the specified value.
Parameters:
value: The value to look for.
forEach(iterator)
Iterates through all the entries in the hashtable, calling specified iterator.
Parameters:
iterator: A method with three parameters, key, value and index, where index represents the index of the entry according to the order it's been added.
Properties:
length (Read-only)
Gets the count of the entries in the hashtable.
keys (Read-only)
Gets an array of all the keys in the hashtable.
values (Read-only)
Gets an array of all the values in the hashtable.
entries (Read-only)
Gets an array of all the entries in the hashtable. They're represented the same as the method entryAt().
function HashTable() {
this.length = 0;
this.items = new Array();
for (var i = 0; i < arguments.length; i += 2) {
if (typeof (arguments[i + 1]) != 'undefined') {
this.items[arguments[i]] = arguments[i + 1];
this.length++;
}
}
this.removeItem = function (in_key) {
var tmp_previous;
if (typeof (this.items[in_key]) != 'undefined') {
this.length--;
var tmp_previous = this.items[in_key];
delete this.items[in_key];
}
return tmp_previous;
}
this.getItem = function (in_key) {
return this.items[in_key];
}
this.setItem = function (in_key, in_value) {
var tmp_previous;
if (typeof (in_value) != 'undefined') {
if (typeof (this.items[in_key]) == 'undefined') {
this.length++;
} else {
tmp_previous = this.items[in_key];
}
this.items[in_key] = in_value;
}
return tmp_previous;
}
this.hasItem = function (in_key) {
return typeof (this.items[in_key]) != 'undefined';
}
this.clear = function () {
for (var i in this.items) {
delete this.items[i];
}
this.length = 0;
}
}
https://gist.github.com/alexhawkins/f6329420f40e5cafa0a4
var HashTable = function() {
this._storage = [];
this._count = 0;
this._limit = 8;
}
HashTable.prototype.insert = function(key, value) {
// Create an index for our storage location by passing
// it through our hashing function
var index = this.hashFunc(key, this._limit);
// Retrieve the bucket at this particular index in
// our storage, if one exists
//[[ [k,v], [k,v], [k,v] ] , [ [k,v], [k,v] ] [ [k,v] ] ]
var bucket = this._storage[index]
// Does a bucket exist or do we get undefined
// when trying to retrieve said index?
if (!bucket) {
// Create the bucket
var bucket = [];
// Insert the bucket into our hashTable
this._storage[index] = bucket;
}
var override = false;
// Now iterate through our bucket to see if there are any conflicting
// key value pairs within our bucket. If there are any, override them.
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i];
if (tuple[0] === key) {
// Override value stored at this key
tuple[1] = value;
override = true;
}
}
if (!override) {
// Create a new tuple in our bucket.
// Note that this could either be the new empty bucket we created above
// or a bucket with other tupules with keys that are different than
// the key of the tuple we are inserting. These tupules are in the same
// bucket because their keys all equate to the same numeric index when
// passing through our hash function.
bucket.push([key, value]);
this._count++
// Now that we've added our new key/val pair to our storage
// let's check to see if we need to resize our storage
if (this._count > this._limit * 0.75) {
this.resize(this._limit * 2);
}
}
return this;
};
HashTable.prototype.remove = function(key) {
var index = this.hashFunc(key, this._limit);
var bucket = this._storage[index];
if (!bucket) {
return null;
}
// Iterate over the bucket
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i];
// Check to see if key is inside bucket
if (tuple[0] === key) {
// If it is, get rid of this tuple
bucket.splice(i, 1);
this._count--;
if (this._count < this._limit * 0.25) {
this._resize(this._limit / 2);
}
return tuple[1];
}
}
};
HashTable.prototype.retrieve = function(key) {
var index = this.hashFunc(key, this._limit);
var bucket = this._storage[index];
if (!bucket) {
return null;
}
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i];
if (tuple[0] === key) {
return tuple[1];
}
}
return null;
};
HashTable.prototype.hashFunc = function(str, max) {
var hash = 0;
for (var i = 0; i < str.length; i++) {
var letter = str[i];
hash = (hash << 5) + letter.charCodeAt(0);
hash = (hash & hash) % max;
}
return hash;
};
HashTable.prototype.resize = function(newLimit) {
var oldStorage = this._storage;
this._limit = newLimit;
this._count = 0;
this._storage = [];
oldStorage.forEach(function(bucket) {
if (!bucket) {
return;
}
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i];
this.insert(tuple[0], tuple[1]);
}
}.bind(this));
};
HashTable.prototype.retrieveAll = function() {
console.log(this._storage);
//console.log(this._limit);
};
/******************************TESTS*******************************/
var hashT = new HashTable();
hashT.insert('Alex Hawkins', '510-599-1930');
//hashT.retrieve();
//[ , , , [ [ 'Alex Hawkins', '510-599-1930' ] ] ]
hashT.insert('Boo Radley', '520-589-1970');
//hashT.retrieve();
//[ , [ [ 'Boo Radley', '520-589-1970' ] ], , [ [ 'Alex Hawkins', '510-599-1930' ] ] ]
hashT.insert('Vance Carter', '120-589-1970').insert('Rick Mires', '520-589-1970').insert('Tom Bradey', '520-589-1970').insert('Biff Tanin', '520-589-1970');
//hashT.retrieveAll();
/*
[ ,
[ [ 'Boo Radley', '520-589-1970' ],
[ 'Tom Bradey', '520-589-1970' ] ],
,
[ [ 'Alex Hawkins', '510-599-1930' ],
[ 'Rick Mires', '520-589-1970' ] ],
,
,
[ [ 'Biff Tanin', '520-589-1970' ] ] ]
*/
// Override example (Phone Number Change)
//
hashT.insert('Rick Mires', '650-589-1970').insert('Tom Bradey', '818-589-1970').insert('Biff Tanin', '987-589-1970');
//hashT.retrieveAll();
/*
[ ,
[ [ 'Boo Radley', '520-589-1970' ],
[ 'Tom Bradey', '818-589-1970' ] ],
,
[ [ 'Alex Hawkins', '510-599-1930' ],
[ 'Rick Mires', '650-589-1970' ] ],
,
,
[ [ 'Biff Tanin', '987-589-1970' ] ] ]
*/
hashT.remove('Rick Mires');
hashT.remove('Tom Bradey');
//hashT.retrieveAll();
/*
[ ,
[ [ 'Boo Radley', '520-589-1970' ] ],
,
[ [ 'Alex Hawkins', '510-599-1930' ] ],
,
,
[ [ 'Biff Tanin', '987-589-1970' ] ] ]
*/
hashT.insert('Dick Mires', '650-589-1970').insert('Lam James', '818-589-1970').insert('Ricky Ticky Tavi', '987-589-1970');
hashT.retrieveAll();
/* NOTICE HOW THE HASH TABLE HAS NOW DOUBLED IN SIZE UPON REACHING 75% CAPACITY, i.e. 6/8. It is now size 16.
[,
,
[ [ 'Vance Carter', '120-589-1970' ] ],
[ [ 'Alex Hawkins', '510-599-1930' ],
[ 'Dick Mires', '650-589-1970' ],
[ 'Lam James', '818-589-1970' ] ],
,
,
,
,
,
[ [ 'Boo Radley', '520-589-1970' ],
[ 'Ricky Ticky Tavi', '987-589-1970' ] ],
,
,
,
,
[ [ 'Biff Tanin', '987-589-1970' ] ] ]
*/
console.log(hashT.retrieve('Lam James')); // 818-589-1970
console.log(hashT.retrieve('Dick Mires')); // 650-589-1970
console.log(hashT.retrieve('Ricky Ticky Tavi')); //987-589-1970
console.log(hashT.retrieve('Alex Hawkins')); // 510-599-1930
console.log(hashT.retrieve('Lebron James')); // null
You can create one using like the following:
var dictionary = { Name:"Some Programmer", Age:24, Job:"Writing Programs" };
// Iterate over using keys
for (var key in dictionary) {
console.log("Key: " + key + " , " + "Value: "+ dictionary[key]);
}
// Access a key using object notation:
console.log("Her name is: " + dictionary.Name)
Related
Assuming I have an object like this:
var result = {
items: [
'item1', 'item2', 'item3'
]
}
I'm looking for a way create an object that would abstract my result object and yet would remain compatible to it:
function wrapper(result) {
this.items = ???
}
In order to able able to do this
var p = new wrapper(result);
for(var x = 0; x < p.items.length; x++) {
console.log(p[x]) // = item1, item2, item3
}
This can be easily accomplished in PHP using ArrayIterator. I was wondering if it can be done in JS, specifically in NodeJS.
Just to clarify:
Imagine the original object is a result from a db query. However, the formatting of that result object cannot be fed into the consumer as-is and needs to be adapted. In this case I did not dwell on the adaptation but for the sake of the example assume each value needs to be uppercased.
Additionally, the original list is very large. I could obviously iterate, copy all values and uppercase them, but in most cases it will not be required for all values, therefore it would be inefficient to do this for all items every time. The idea of the wrapper is to act as a proxy that I would be able to iterate. The wrapper will in turn retrieve the result from the original object, and modify it on the fly.
If you're using Node 4 or 5 you can consider using ES6's Symbol.Iterator. It's the closest thing to PHP's ArrayIterator that I can think of.
Here's an example of a use-case which you describe in your post:
'use strict';
let result = {
items: [
'item1', 'item2', 'item3'
],
[Symbol.iterator](cb) {
let index = 0;
return {
next: () => {
let value = this.items[index];
let done = index >= this.items.length;
// Note that arrow functions won't bind `this.items` to cb's `this`.
if (typeof cb === 'function') {
return cb.call(this.items, value, done, this.items, index++);
} else {
index++;
return { value, done };
}
}
}
}
}
let cb = (value, done, items, index) => {
// Modify original array.
items[index] = items[index] && items[index].toUpperCase();
value = items[index];
return { value, done };
};
let iterator1 = result[Symbol.iterator](cb);
let iterator2 = result[Symbol.iterator]();
console.log(iterator1.next()); // { value: 'ITEM1', done: false }
console.log(iterator2.next()); // { value: 'ITEM1', done: false }
console.log(iterator1.next()); // { value: 'ITEM2', done: false }
console.log(iterator1.next()); // { value: 'ITEM3', done: false }
console.log(result.items); // [ 'ITEM1', 'ITEM2', 'ITEM3' ]
Note that you can define multiple iterators to iterate concurrently, modify the referenced array, and add your own methods to the iterator to emulate ArrayIterator as you please.
I should also mention that the for-of construct works well with this:
'use strict';
let result = {
items: [
'item1', 'item2', 'item3'
]
}
let cb = (value, done, items, index) => {
// Modify original array.
items[index] = items[index] && items[index].toUpperCase();
value = items[index];
return { value, done };
};
let modifiableIterableIterator = {
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
let value = result.items[index];
let done = index >= result.items.length;
// Note that arrow functions won't bind `this.items` to cb's `this`.
if (typeof cb === 'function') {
return cb.call(result.items, value, done, result.items, index++);
} else {
index++;
return { value, done };
}
}
}
}
};
for (let item of modifiableIterableIterator) console.log(item);
modifiableIterableIterator[Symbol.iterator] is the same as the previous result[Symbol.iterator], except there is no more cb parameter. When passing an iterable iterator (an iterable iterator is one that defines a next method, and Symbol.iterator on the object), no arguments are passed to Symbol.iterator, so we just make the callback explicit from within the method. The loop will perform the same operations as before: uppercase all the values and modify the array in-place.
There are some design decisions about how this can be structured properly, but this is up to you and the way your application is structured.
Since you want to support the "older" way to iterate while maintaining this behavior, the only way you can do this by overloading [] is to use ES6's Proxy object. This allows you to do some metaprogramming. Unfortunately, the native support is lacking, but you can use shims if you need to (I got this working on Node 5 using the harmony-reflect module and passing --harmony_proxies on the command-line).
let proxy = new Proxy(result.items, {
get(target, index) {
target[index] = target[index] && target[index].toUpperCase();
return target[index];
}
});
for (let x = 0; x < result.items.length; x++) console.log(proxy[x]); // 'ITEMx'
console.log(result.items); // [ 'ITEM1', 'ITEM2', 'ITEM3' ]
Combine the new iterators feature and a Proxy, and you have a solution which works with your existing implementation and future-proof's your code.
I would recommend creating a class similar to ArrayIterator.
Create an npm module
Put this in your module's index.js:
function wrapper( items ) {
this.items = items;
this.count = items.length;
this.iterateReverse = ( cb ) => {
var x = this.count;
while ( x-- ) cb( this.items[ x ] );
}
this.iterate = ( cb ) => {
var x = -1;
while ( x++ < this.count-1 ) cb( this.items[ x ] );
}
}
wrapper.prototype.items = (this.items);
module.exports = wrapper;
Now in your main environment:
const
wrapper = require( './wrapper' );
var p = new wrapper( [ 'item1', 'item2', 'item3' ] );
for( var x = 0; x < p.count; x++ ) {
console.log( p.items[x] ); // = item1, item2, item3
}
p.iterateReverse(( v ) => {
console.log( v ); // = item3, item2, item1
});
p.iterate(( v ) => {
console.log( v ); // = item1, item2, item3
});
This will result with // = item1, item2, item3
First of all: I already found this thread, which basically is exactly what I want, but I tried my best to apply it to my needs - I couldn't.
So, I have the following javascript function:
function loadRelationData(object) {
var result = [];
var parents = []
parents = getParentObjectsByObjectID(object['ObjectID']);
var tmpFirstObjects = [];
var tmpOtherObjects = [];
$.each(parents, function (_, parent) {
var keyName = 'Übergeordnete ' + parent['ObjectType'];
var pushObject = {};
if (parent['ObjectType'] == object['ObjectType']) {
pushObject['Fieldname'] = keyName;
pushObject['Value'] = parent['Name'];
tmpFirstObjects.push(pushObject);
} else {
pushObject['Fieldname'] = keyName;
pushObject['Value'] = parent['Name'];
tmpOtherObjects.push(pushObject);
}
});
result = result.concat(tmpFirstObjects).concat(tmpOtherObjects);
return result;
}
The parents array looks like this
And my function creates this result
This might be a bit complicated, but I need to split it up like this, because I need the order.
What I want is an array with both "TEC_MapLocations" joined together like this:
[
{Fieldname: 'Übergeordnete TEC_Equipment', Value: 'E0192'},
{Fieldname: 'Übergeordnete TEC_MapLocation', Value: ['M100', 'M200']},
{Fieldname: 'Übergeordnete TEC_FunctionalLocation', Value: 'FL456'}
]
Any ideas on how to alter my code to achieve the desired result right away or how to merge the results array?
edit: I used Joseph's solution and used the following (quick and dirty) sort function to get back my desired sorting:
output.sort(function (a, b) {
if (a.ObjectType == object.ObjectType) {
return -1
} else {
return 1
}
});
What you'd want to do first is build a hash with Fieldname as key, and an array as value. Then you'd want to use reduce to add the values into the hash and array. Then you can transform it into an array using Object.keys and map.
var input = [
{Name: 'M100', ObjectID: 1, ObjectType: 'TEC_MapLocation'},
{Name: 'M200', ObjectID: 2, ObjectType: 'TEC_MapLocation'},
{Name: 'FL456', ObjectID: 4, ObjectType: 'TEC_FunctionalLocation'},
{Name: 'E0192', ObjectID: 5, ObjectType: 'TEC_Equipment'}
];
var hash = input.reduce(function(carry, item){
// Create the name
var name = 'Übergeordnete ' + item.ObjectType;
// If array with name doesn't exist, create it
if(!carry[name]) carry[name] = [];
// If item isn't in the array, add it.
if(!~carry[name].indexOf(item.Name)) carry[name].push(item.Name);
return carry;
}, {});
// Convert the hash into an array
var output = Object.keys(hash).map(function(key, index, array){
return { Fieldname: key, Value: hash[key] }
});
document.write(JSON.stringify(output));
Try this:
function joinObjects( array ) {
// Start with empty array
var ret = new Array();
// Iterate array
for ( var i = 0; i < array.length; i++ ) {
// Search by fieldname
var match = false;
var j;
for ( j = 0; j < ret.length; j++ ) {
if ( array[i].Fieldname == ret[j].Fieldname ) { match = true; break; }
}
// If not exists
if ( !match ) {
// Intert object
ret.push({
Fieldname: array[i].Fieldname,
Value: new Array()
});
}
// Insert value
ret[j].Value.push( array[i].Value );
}
// Return new array
return ret;
}
http://jsfiddle.net/6entfv4x/
i'm trying to sort my hash table pull down menu alphabetically... using this function:
function getSortedKeys(obj) {
var keys = [];
for(var key in obj) {
keys.push(obj[key]);
keys[keys.length-1]['key'] = key;
}
return keys.sort(function(a,b){
return a.name > b.name ? 1 : a.name < b.name ? -1 : 0;
});
}
This sorts the pull down menu...although it changes the original id# of my menu items which screws some things up on my site... is it possible to keep the original id# of each menu item and still sort?
sorry..here's the hash code:
var clientProjectsHash = {};
clientProjectsHash['1'] = {};
clientProjectsHash['1']['name'] = 'RONA';
clientProjectsHash['2'] = {};
clientProjectsHash['2']['name'] = 'CMS';
clientProjectsHash['3'] = {};
clientProjectsHash['3']['name'] = 'ALT';
and getSortedKeys is called by:
function getInitialClient() {
clientProjectsHash = getSortedKeys(clientProjectsHash);
for (clientKey in clientProjectsHash) {
if(clientKey > 0) {
return clientKey;
}
}
}
The problem is you are returning an array, and expecting it to be an object. These are different things in JavaScript. Nothing is "changing"; your "ids" (or "hashes") are not being modified.
Your clientProjectsHash starts as an object! Objects are unordered and can have any string as a key. When you do getSortedKeys(clientProjectsHash); you are being returned an array! Arrays are ordered, and have numeric indexes (keys) that start at 0.
clientProjectsHash = getSortedKeys(clientProjectsHash);
for (clientKey in clientProjectsHash) {
}
This overwrites clientProjectsHash with an array. Then you for..in over it (you should not use for..in for arrays, by the way).
The array returned from getSortedKeys looks like this:
[
{
name: 'ALT',
key: 3
},
{
name: 'CMS',
key: 2
},
{
name: 'RONA',
key: 1
}
]
So, in your for..in, clientKey will be 0, 1, and 2. The indexes of the array. Your key values are not changing, you are just reading the wrong value.
Try this instead:
function getInitialClient() {
var clientKeys = getSortedKeys(clientProjectsHash);
for(var i = 0, len = clientKeys.length; i < len; i++){
var clientKey = clientKeys[i];
if(clientKey.key > 0){
return clientKey.key;
}
}
}
I have an associative array like:
var arr = {};
arr['alz'] = '15a';
arr['aly'] = '16b';
arr['alx'] = '17a';
arr['alw'] = '09c';
I need to find the previous and next key of any selected element. Say, for key 'aly' it will be 'alz' and 'alx'. If possible, I want to access the array by index rather than the key.
Currently, I am doing this using a separate array containing keys, e.g.
var arrkeys = ['alz','aly','alx','alw'];
Ordering of the object's properties is undefined. You can use this structure...
[{ key: 'alz', value: '15a'},
{ key: 'aly', value: '16b'},
{ key: 'alx', value: '17a'}]
... though searching for the element with the given key (like 'give me the element which key is 'alz') is not as straight-forward as with simple object. That's why using it like you did - providing a separate array for ordering of the indexes - is another common approach. You can attach this array to that object, btw:
var arr={};
arr['alz']='15a';
arr['aly']='16b';
arr['alx']='17a';
arr['alw']='09c';
arr._keysOrder = ['alz', 'aly', 'alx', 'alw'];
This is an object, not an array, and it sounds like you don't really want those strings to be keys.
How about a nice array?
var ar = [
{ key: 'alz', value: '15a' },
{ key: 'aly', value: '16b' },
{ key: 'alx', value: '17a' },
{ key: 'alw', value: '09c' }
];
How about adding some syntactic sugar in the form of an OrderedObject object? Then you could do something like this:
myObj = new OrderedObject();
myObj.add('alz', '15a');
myObj.add('aly', '16b');
myObj.add('alx', '17a');
myObj.add('alw', '09c');
console.log(myObj.keyAt(2)); // 'alx'
console.log(myObj.valueAt(3)); // '09c'
console.log(myObj.indexOf('aly')); // 1
console.log(myObj.length()) // 4
console.log(myObj.nextKey('aly')); // 'alx'
The following code makes this work. See it in action in a jsFiddle.
function OrderedObject() {
var index = [];
this.add = function(key, value) {
if (!this.hasOwnProperty(key)) {
index.push(key);
}
this[key] = value;
};
this.remove = function(key) {
if (!this.hasOwnProperty(key)) { return; }
index.splice(index.indexOf(key), 1);
delete this[key];
}
this.indexOf = function(key) {
return index.indexOf(key);
}
this.keyAt = function(i) {
return index[i];
};
this.length = function() {
return index.length;
}
this.valueAt = function(i) {
return this[this.keyAt(i)];
}
this.previousKey = function(key) {
return this.keyAt(this.indexOf(key) - 1);
}
this.nextKey = function(key) {
return this.keyAt(this.indexOf(key) + 1);
}
}
I made some decisions that may not work for you. For example, I chose to use an Object as the prototype rather than an Array, so that you could preserve enumerating your object with for (key in myObj). But it didn't have to be that way. It could have been an Array, letting you use the property .length instead of the function .length() and then offering an each function that enumerates the keys, or perhaps an .object() function to return the inner object.
This could be a little awkward as you'd have to remember not to add items to the object yourself. That is, if you do myObj[key] = 'value'; then the index will not be updated. I also did not provide any methods for rearranging the order of things or inserting them at a particular position, or deleting by position. If you find my object idea useful, though, I'm sure you can figure out how to add such things.
With the newer versions of EcmaScript you can add true properties and make them non-enumerable. This would allow the new object to more seamlessly and smoothly act like the ideal OrderedObject I am imagining.
If you have to know the order of everything, and still use the keys and values, try this:
var arr = [
{ key: 'alz', value: '15a' },
{ key: 'aly', value: '16b' },
{ key: 'alx', value: '17a' },
{ key: 'alw', value: '09c' }
];
You can then access them sequentially as follows: arr[0].key and arr[0].value. Similarly, you can find siblings inside of the loop with the following:
for(var i = 0; i < arr.length; i++)
{
var previous_key = (i > 0) ? arr[(i - 1)].key : false;
var next_key = (i < (arr.length - 1)) ? arr[(i + 1)].key : false;
}
You may try this
function sortObject(obj, order)
{
var list=[], mapArr = [], sortedObj={};
for(var x in obj) if(obj.hasOwnProperty(x)) list.push(x);
for (var i=0, length = list.length; i < length; i++) {
mapArr.push({ index: i, value: list[i].toLowerCase() });
}
mapArr.sort(function(a, b) {
if(order && order.toLowerCase()==='desc')
return a.value < b.value ? 1 : -1;
else return a.value > b.value ? 1 : -1;
});
for(var i=0; i<mapArr.length;i++)
sortedObj[mapArr[i].value]=obj[mapArr[i].value];
return sortedObj;
}
// Call the function to sort the arr object
var sortedArr = sortObject(arr); // Ascending order A-Z
var sortedArr = sortObject(arr, 'desc'); // Descending order Z-A
DEMO.
Remember, this will return a new object and original object will remain unchanged.
I have a data dictionary like this:
var data = {
'text1': 1,
'text2': 2,
'text3': 3,
...
'text20': 20
];
I need to pick a random selection of those keys and then shuffle it's values. In the example, it should write something like this:
> console.log(choose(data, 5));
[ { key: 'text15', value: 8 },
{ key: 'text6', value: 3 },
{ key: 'text3', value: 15 },
{ key: 'text19', value: 6 },
{ key: 'text8', value: 19 } ]
For now I'm extracting the keys into another array and sorting by Math.random() but I'm stuck at swaping the values because no key should have the same value it initially had.
How would you swap key/values here?
Thanks
I put together a possible solution using underscore.js to simplify traversing the object and arrays in a cross browser manner:
var data = {
text1: 1,
text2: 2,
text3: 3,
text4: 4,
text5: 5,
text6: 6,
text7: 7,
text8: 8,
text9: 9,
text10: 10
};
function choose(data, num)
{
var keys = _.sortBy(
_.keys(data),
function(k)
{
return (Math.random() * 3) - 1;
}
),
results = [],
k1, k2;
if (num > keys.length) {
throw new Error('Impossible to retrieve more values than exist');
}
while (results.length < num) {
k1 = k2 || keys.pop();
k2 = keys.pop();
results.push({key:k1, value: data[k2]});
}
return results;
}
console.log(choose(data, 5));
This isn't necessarily an optimal approach but it seems to meet your requirements. I first grab all of the keys and sort them randomly. I then loop through the random keys creating a new object with one key and the following keys value. That way you'll always end up with a different value associated with each key. If you need it to work when the value of num passed in to the function == the number of keys in the data then you'll have to add a little more code - I'll leave that as an exercise for the reader :)
You can have a play with this code on jsfiddle:
http://jsfiddle.net/zVyQW/1/
You could do this:
collect names and corresponding values in two arrays names and values
shuffle both arrays independently of each other
take the first n items of both arrays and combine them
Here’s an example implementation:
Array.prototype.shuffle = function() {
for (var i=this.length-1, j, tmp; i>0; i--) {
j = Math.round(Math.random()*i);
tmp = this[i], this[i] = this[j], this[j] = tmp;
}
return this;
};
function choose(data, number) {
var names = [], values = [], pick = [];
for (var name in data) {
if (data.hasOwnProperty(name)) {
names.push(name);
values.push(data[name]);
}
}
names = names.shuffle(), values = values.shuffle();
for (var i=Math.min(number >>> 0, names.length-1); i>=0; i--) {
pick.push({key: names[i], value: values[i]});
}
return pick;
}
Been a while since this was answered, but I was working on shuffling and found the following to be by far the fastest implementation with an evenly random distribution.
It's fast because it only makes one call to Math.random on each iteration, all the rest is done by property access. It doesn't modify the array, just reassigns values.
function shuffle(a) {
var t, j, i=a.length, rand=Math.random;
// For each element in the array, swap it with a random
// element (which might be itself)
while (i--) {
k = rand()*(i+1)|0;
t = a[k];
a[k]=a[i];
a[i]=t;
}
return a;
}
It uses a combination of three functions (including the Array shuffle prototype method).
Here is the complete code:
var obj = {
"red":"RED",
"blue":"BLUE",
"green":"GREEN",
"yellow":"YELLOW",
"purple":"PURPLE"
};
Array.prototype.shuffle = function(){
for (var i = 0; i < this.length; i++){
var a = this[i];
var b = Math.floor(Math.random() * this.length);
this[i] = this[b];
this[b] = a;
}
}
obj = shuffleProperties(obj); // run shuffle
function shuffleProperties(obj) {
var new_obj = {};
var keys = getKeys(obj);
keys.shuffle();
for (var key in keys){
if (key == "shuffle") continue; // skip our prototype method
new_obj[keys[key]] = obj[keys[key]];
}
return new_obj;
}
function getKeys(obj){
var arr = new Array();
for (var key in obj)
arr.push(key);
return arr;
}
for(key in obj){
alert(key);
}
Check all post,
Best Regards.
Use an implementation of random that randomizes a discrete set of values, such as Math.rand seen here. For each index, randomize Math.rand(index, length-1) to get a list of random indexes, the location off all indices will change.