3

Javascript has arrays which use numeric indexes ["john", "Bob", "Joe"] and objects which can be used like associative arrays or "maps" that allow string keys for the object values {"john" : 28, "bob": 34, "joe" : 4}.

In PHP it is easy to both A) sort by values (while maintaining the key) and B) test for the existence of a value in an associative array.

$array = ["john" => 28, "bob" => 34, "joe" => 4];

asort($array); // ["joe" => 4, "john" => 28, "bob" => 34];

if(isset($array["will"])) { }

How would you acheive this functionality in Javascript?

This is a common need for things like weighted lists or sorted sets where you need to keep a single copy of a value in data structure (like a tag name) and also keep a weighted value.

This is the best I've come up with so far:

function getSortedKeys(obj) {
    var keys = Object.keys(obj);
    keys = keys.sort(function(a,b){return obj[a]-obj[b]});

    var map = {};
    for (var i = keys.length - 1; i >= 0; i--) {
      map[keys[i]] = obj[keys[i]];
    };

    return map;
}

var list = {"john" : 28, "bob": 34, "joe" : 4};
list = getSortedKeys(list);
if(list["will"]) { }
Xeoncross
  • 55,620
  • 80
  • 262
  • 364
  • 1
    For the first line you might want to use: Object.keys https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/keys – Wikunia Apr 01 '15 at 14:42
  • As soon as you have keys and values, you'll have objects. Objects are not sorted. But this is very inspiring: [http://stackoverflow.com/questions/5467129/sort-javascript-object-by-key](http://stackoverflow.com/questions/5467129/sort-javascript-object-by-key) – peter_the_oak Apr 01 '15 at 14:43
  • @Wikunia, thank you. I updated the code. http://stackoverflow.com/a/890877/99923 – Xeoncross Apr 01 '15 at 14:48
  • Do you want to sort by key or value? Cause your js script orders by key but your php one by value. And as the answer mentioned out an object is unordered. – Wikunia Apr 01 '15 at 14:48
  • @bhantol, that is no improvement on the answer I already provided and in fact adds a dependency on jQuery just for a simple operation. – Xeoncross Apr 01 '15 at 14:50
  • @Wikunia, both sort by value while maintaining the `key -> value` relationship. – Xeoncross Apr 01 '15 at 14:51
  • @Xeoncross a sorry my fault! – Wikunia Apr 01 '15 at 14:53
  • @Xeoncross - wrong link - here is the link http://stackoverflow.com/a/5467142/2103767 – bhantol Apr 01 '15 at 14:54
  • Note: [Associative Arrays or "Maps"](http://en.wikipedia.org/wiki/Associative_array) *do not imply ordering*: "Usually, for such an [iteration] operation, the order in which the bindings are returned may be arbitrary." That is a PHP-ism / implementation detail. – user2864740 Apr 01 '15 at 15:09
  • ...but sorted sets do, hence the question. – Xeoncross Apr 01 '15 at 15:10
  • It's not [just] a Map then. It's a Sorted Set (or whatever the particular implementation desired is). – user2864740 Apr 01 '15 at 15:11
  • 2
    Your solution is wrongYou can't return a sorted object in javascript because the standard ES5 doesn't guarantee order when iterating over object properties. Just keep both sorted array and map, use array for iterating and map for checking if exists. – Iftah Apr 01 '15 at 15:18

8 Answers8

2

Looking at this answer by Luke Schafer I think I might have found a better way to handle this by extending the Object.prototype:

// Sort by value while keeping index
Object.prototype.iterateSorted = function(worker, limit)
{
    var keys = Object.keys(this), self = this;
    keys.sort(function(a,b){return self[b] - self[a]});

    if(limit) {
        limit = Math.min(keys.length, limit);
    }

    limit = limit || keys.length;
    for (var i = 0; i < limit; i++) {
        worker(keys[i], this[keys[i]]);
    }
};

var myObj = { e:5, c:3, a:1, b:2, d:4, z:1};

myObj.iterateSorted(function(key, value) {
    console.log("key", key, "value", value)
}, 3);

http://jsfiddle.net/Xeoncross/kq3gbwgh/

Community
  • 1
  • 1
Xeoncross
  • 55,620
  • 80
  • 262
  • 364
  • 1
    Your code doesn't work because of this line: `keys = keys.sort(function(a,b){return this[a] - this[b]});`. In context of sort function `this` is `keys`, not `myObj` – Tho Apr 01 '15 at 15:30
  • Thank you, I was just wondering what I was missing. I can't believe I forgot scope. – Xeoncross Apr 01 '15 at 16:13
  • You should do like this: `var self = this; keys = keys.sort(function(a,b){return self [a] - self [b]});`. See my answer for more detail. – Tho Apr 01 '15 at 16:19
1

I'm not sure why none of these answers mentions the existence of a built-in JS class, Set. Seems to be an ES6 addition, perhaps that's why.

Ideally override either add or keys below... NB overriding keys doesn't even need access to the Set object's prototype. Of course you could override these methods for the entire Set class. Or make a subclass, SortedSet.

  const mySet = new Set();

  const mySetProto = Object.getPrototypeOf(mySet);
  const addOverride = function(newObj){
    const arr = Array.from(this);
    arr.add(newObj);
    arr.sort(); // or arr.sort(function(a, b)...)
    this.clear();
    for(let item of arr){
      mySetProto.add.call(this, item);
    }
  }
  mySet.add = addOverride;
    
  const keysOverride = function(){
    const arr = Array.from(this);
    arr.sort(); // or arr.sort(function(a, b)...)
    return arr[Symbol.iterator]();
  }
  mySet.keys = keysOverride;

Usage:

mySet.add(3); mySet.add(2); mySet.add(1); mySet.add(2);
for(let item of mySet.keys()){console.log(item)};

Prints out:

1 ... 2 ... 3

NB Set.keys() returns not the items in the Set, but an iterator. You could choose to return the sorted array instead, but you'd obviously be breaking the class's "contract".

Which one to override? Depends on your usage and the size of your Set. If you override both you will be duplicating the sort activity, but in most cases it probably won't matter.

NB The add function I suggest is of course naive, a "first draft": rebuilding the entire set each time you add could be pretty costly. There are clearly much cleverer ways of doing this based on examining the existing elements in the Set and using a compare function, a binary tree structure*, or some other method to determine where in it to add the candidate for adding (I say "candidate" because it would be rejected if an "identical" element, namely itself, were already found to be present).

The question also asks about similar arrangements for a sorted map... in fact it turns out that ES6 has a new Map class which lends itself to similar treatment ... and also that Set is just a specialised Map, as you might expect.

* e.g. https://github.com/Crizstian/data-structure-and-algorithms-with-ES6/tree/master/10-chapter-Binary-Tree

mike rodent
  • 14,126
  • 11
  • 103
  • 157
1

With ES6 you could choose to extend the Map constructor/class with a sort method that takes an optional compare function (just like arrays have). That sort method would take two arguments, each of which are key/value pairs so that the sorting can happen on either the keys or the values (or both).

The sort method will rely on the documented behaviour of Maps that entries are iterated in insertion order. So this new method will visit the entries according to the sorted order, and then delete and immediately re-insert them.

Here is how that could look:

class SortableMap extends Map {
    sort(cmp = (a, b) => a[0].localeCompare(b[0])) {
        for (const [key, value] of [...this.entries()].sort(cmp)) {
            this.delete(key);
            this.set(key, value); // New keys are added at the end of the order
        }
    }
}
// Demo
const mp = new SortableMap([[3, "three"],[1, "one"],[2, "two"]]);
console.log("Before: ", JSON.stringify([...mp])); // Before
mp.sort( (a, b) => a[0] - b[0] ); // Custom compare function: sort numerical keys
console.log(" After: ", JSON.stringify([...mp])); // After
trincot
  • 317,000
  • 35
  • 244
  • 286
0

You usually don't sort an object. But if you do: Sorting JavaScript Object by property value

If you want to sort an array, let's say the following

var arraylist = [{"john" : 28},{ "bob": 34},{ "joe" : 4}];

You can always use Array.prototype.sort function. Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

Community
  • 1
  • 1
  • So how do you test for the existence of a value then? A for loop? – Xeoncross Apr 01 '15 at 14:47
  • `arrayList.sort(function comparer(a, b){ /* check against a and b values here */})` If you know that the arraylist items are always compsed by key value, then check what properties are owned by `a` and `b` and check its values (for instance `a[ownedPropName]`) – Raul Fernandez Apr 01 '15 at 14:52
  • Running another sort over the values *just to check for the existence* of a value is a bad idea. Perhaps you didn't see the second part of the question... – Xeoncross Apr 01 '15 at 15:05
0

Maybe this code look like what you want:

Object.prototype.asort = function(){
    var retVal = {};
    var self = this;
    var keys = Object.keys(this);
    keys = keys.sort(function(a,b){return self[a] - self[b]});
    for (var i = 0; i < keys.length; i++) {
        retVal[keys[i]] = this[keys[i]];
    }
    return retVal;
}
var map = {"john" : 28, "bob": 34, "joe" : 4}
var sortedMap = map.asort();//sortedMap["will"]: undefined
Tho
  • 23,158
  • 6
  • 60
  • 47
0

If you use the open source project jinqJs its easy.

See Fiddler

var result = jinqJs()
                .from([{"john" : 28},{ "bob": 34},{ "joe" : 4}])
                .orderBy([{field: 0}])
                .select();
NYTom
  • 524
  • 2
  • 14
  • 1
    Thank you for [sharing this library](https://github.com/fordth/jinqJs/blob/master/jinqjs.js#L388), but this is relatively simple task in most languages and I would like to know how best write this rather than using some library. – Xeoncross Apr 02 '15 at 14:24
0

Here's an implementation of OrderedMap. Use the functions get() and set() to extract or push key value pairs to the OrderedMap. It is internally using an array to maintain the order.

class OrderedMap {
  constructor() {
    this.arr = [];
    return this;
  }
  get(key) {
    for(let i=0;i<this.arr.length;i++) {
      if(this.arr[i].key === key) {
        return this.arr[i].value;
      }
    }
    return undefined;
  }

  set(key, value) {
    for(let i=0;i<this.arr.length;i++) {
      if(this.arr[i].key === key) {
        this.arr[i].value = value;
        return;
      }
    }
    this.arr.push({key, value})
  }

  values() {
    return this.arr;
  }
}

let m = new OrderedMap();
m.set('b', 60)
m.set('a', 10)
m.set('c', 20)
m.set('d', 89)

console.log(m.get('a'));

console.log(m.values());
Ayan
  • 8,192
  • 4
  • 46
  • 51
0

https://github.com/js-sdsl/js-sdsl

The OrderedMap in Js-sdsl maybe helpful.

This is a sorted-map which implement refer to C++ STL Map.

/*
 * key value
 * 1   1
 * 2   2
 * 3   3
 * Sorted by key.
 */
const mp = new OrderedMap(
    [1, 2, 3].map((element, index) => [index, element])
);
mp.setElement(1, 2);        // O(logn)
mp.eraseElementByKey(1)     // O(logn)

// custom comparison function
mp = new OrderedMap(
    [1, 2, 3].map((element, index) => [index, element]),
    (x, y) => x - y
);

// enable tree iterator index (enableIndex = true)
console.log(new OrderedMap([[0, 1], [1, 1]], undefined, true).begin(),next().index);   // 1
Zilong Yao
  • 54
  • 4