tags:

views:

578

answers:

4

I have a set of key/values eg orange=123, banana=4, apple=567. How can I store these key/values in a javascript object such that I can:

  1. Retrieve a value via lookup eg set["orange"] should return 123 and,
  2. Iterate over the set in the order in which the key/value pairs were added.

It seems that for 1. an object literal would be suitable but the iteration order is not guaranteed and that for 2. an array of key/value pairs (object literals) would provide an iteration order but not the ability to lookup the value based on a key.

@* Thanks for all the answers - Is this problem not a common one? Do libraries like jQuery not include support for this kind of type?

+1  A: 

Both of your assumptions are correct. Object literals are not guaranteed to return keys in any order.

The only way to do this without spinning your own type is to maintain an ordered list of keys:

var obj = {
    orange:123,
    banana:4,
    apple:567
}

var keys = ['orange', 'banana', 'apple'];

for (var i=0; i<keys.length; i++){
  value = obj[keys[i]]];
}

Ugly, I know.

Triptych
encapsulating all this in his own type would be better though
annakata
…or just to implement key-value pairs using arrays:var data = [["orange", 123], ["banana", 4], ["apple", 567]];
Sergei Morozov
A: 

You can do it in different ways:

var a = {'orange':123, 'banana':4, 'apple':567};

var b = new Object();
b['orange'] = 123;
b['banana'] = 4;
b['apple'] = 567;

var c = new Object();
c.orange = 123;
c.banana = 4;
c.apple = 567;

The syntax is different but internally the three declarations are equivalent.

CMS
would the var b option really work?
Click Upvote
I think you cannot iterate objects in any predictable order, so this would fail condition 2 of the question.
Tomalak
+5  A: 

How about cooking your own list constructor?

function List(obj) {

  if (this instanceof List) {
   var t     = this,
       keys  = [];

    /* inititalize: add the properties of [obj] to the list, 
       and store the keys of [obj] in the private keys array */
    for (var l in obj) {
       keys.push(l);
       t[l] = obj[l];
    }
    /* public:
       add a property to the list 
    */
     t.add = 
         function(key, value) {
              t[key] = value;
              keys.push(key);
              return t; /* allows method chaining */
            };

    /* public:
       return raw or sorted list as string, separated by [separator] 
       Without [sort] the order of properties is the order in which 
       the properties are added to the list
    */
     t.iterate =
       function(sort,separator){
         separator = separator || '\n';
         var ret   = [],
             lkeys = sort ? keys.slice().sort() : keys;

         for (var i=0;i<lkeys.length;i++){
           ret.push(lkeys[i]+': '+t[lkeys[i]]);
         }
       return ret.join(separator);
      };

  } else if (obj && obj instanceof Object) {
     return new List(obj);

  } else if (arguments.length === 2) { 
     var a    = {};
     a[String(arguments[0])] = arguments[1];
     return new List(a);

  } else { return true; }

 /* the 'if (this instanceof List)' pattern makes
    the use of the 'new' operator obsolete. The 
    constructor also allows to be initialized with
    2 parameters => 'List(key,value)' 
 */
}

now you can have it raw (the order you added props is maintained) or sorted:

var myList = 
 List( { orange:123,
         banana:4,
         apple:567 }
     );
myList.add('peach',786);
alert(myList.iterate());
  /*=>output:
    orange: 123
    banana: 4
    apple: 567
    peach: 786
  */
or: alert(myList.iterate(1));
  /*=>output:
    apple: 567
    banana: 4
    orange: 123
    peach: 786
  */
KooiInc
if you formatted and simplified this into a readable form I could be persuaded to upvote it
annakata
How would you like it to be formatted? I'll be happy to edit it
KooiInc
more prototypes, more comments, more whitespace, less code overall but I'll upvote you now anyway - I've posted my own implementation anyway
annakata
Ok, I'll add some comments. More prototypes, why's that? I can replace this constructor with a version adding it to the Object prototype. Thanks for the upvote.
KooiInc
It's one thing for you to do something later but if you post code here and someone copies it you've got no guarantee they will know to do that. You have a responsibility to post the best code possible. Plus prototyping is just so much cleaner :)
annakata
@annataka: ok, cleaned it up, added comments etc. Also added methods prototype. The reason it's cleaner should be explained: if a method is added to the prototype chain, it doesn't need to be recreated with every new instance ;-)
KooiInc
Someone seems to have made a community wiki out of this. Well. Anyway, @annataka: the prototype method declarations were nice, but restraining. You can't use private/privileged variables/methods with that (prototype methods are in another scope).
KooiInc
Right:"posts enter community wiki mode when: * The post has been edited five times by the original owner." I'll remember to be cautious with additional thoughts on or enhancing my answer afterwards.
KooiInc
+2  A: 

A simple encapsulation:

function Dictionary(p)
{
    if(!this.Add)
    {
     Dictionary.prototype.Add = function(a)
     {
      if(!a.length) {a = [a];}
      for(var i = 0, n=a.length; i<n; i++)
      {
       for(x in a[i])
       {
        this[x] = a[i][x];
        this.keys.push(x);
       }
      }
     }
    }

    this.keys = [];

    if(typeof(p)!='undefined') {this.Add(p);}
}

var a = new Dictionary({'orange':123, 'banana':4, 'apple':567});

alert(a.keys);//returns [orange,banana,apple]

alert(a.keys[0]);//returns orange

alert(a.orange) //returns 123

a.Add({'mango':88}); //another way to add data

a.Add([{kiwi:16},{grapefruit:79}]) //another way to add data

alert(a.keys);//returns [orange,banana,apple,mango,kiwi,grapefruit]
annakata