Sort Map/Hash by values, retaining keys

2019-09-01 09:49发布

问题:

I am having a hell of a time finding an answer to this one.

I would like to have a hash in JavaScript, like so:

const hash = {
   a: 'A',
   c: 'C',
   b: 'B'
}

and I would like to sort the object by the value, such that it might become:

const hash = {
   a: 'A',
   b: 'B',
   c: 'C',
}

now, I realize that POJSOs don't guarantee the order of their keys, so I was thinking of using immutableJS or some object that will guarantee the order of the keys in the hash.

so I am looking for something like:

var newHash = Immutable.Map(hash).sortBy(function(key,val){
       return val;
});

But I don't believe this exists. is this too much to ask? How can I find this functionality?

回答1:

now, I realize that POJSOs don't guarantee the order of their keys

They do now, as of ES2015. You still can't access that order via for-in or Object.keys, but you can via Object.getOwnPropertyNames. The order is given by the specification operation [[OwnPropertyKeys]]. Ignoring inherited properties, it's basically: Keys that are strings that qualify as integer indexes, in numeric order; followed by remaining keys that are strings, in the order the properties were created; followed by keys that are Symbols, in the order they were created.

With that information, if your keys are all strings that don't qualify as integer indexes, then on an ES2015-compliant engine, we can do this:

  1. Start with the properties in any order (e.g., a, c, b)

  2. Use Object.keys to get those keys as an array

  3. Use Array#map to create an array of objects with properties for the key and value (I've used k and v below)

  4. Sort the array by value

  5. Use Array#reduce to create a new object, adding the properties to it in the sorted order. (Some, including myself, may argue that this is a slight misuse of reduce, because the accumulator never actually changes, but hey, it lets us be all l33t [is that still a thing?] and avoid an intermediate variable. Um, because that's a goal? :-) )

The end result is an object with the properties in order of creation, which is the order of their values because we created them in that order. We can access that order via Object.getOwnPropertyNames.

// The unsorted data
let data = {
  a: 'A',
  c: 'C',
  b: 'B'
};

// Create it sorted
const hash = Object.keys(data)
  .map(key => ({k: key, v: data[key]}))
  .sort((a, b) => a.v.localeCompare(b.v))
  .reduce((o, e) => {
    o[e.k] = e.v;
    return o;
  }, {});

// Display the sorted result
Object.getOwnPropertyNames(hash).forEach(n => {
  snippet.log("hash['" + n + "'] = " + hash[n]);
});
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="//tjcrowder.github.io/simple-snippets-console/snippet.js"></script>


All of that aside, as I said in a comment on the question, I'm not sure why you'd want to do this. It seems like if we knew what your ultimate goal was, we might be able to point you in a more useful direction...



回答2:

Lodash one-liner:

sorted = _(hash).toPairs().sortBy(1).fromPairs().value();

Live Example:

var data = {a: 'A', c: 'C', b: 'B'};
var hash = _(data).toPairs().sortBy(1).fromPairs().value();
Object.getOwnPropertyNames(hash).forEach(n => {
  snippet.log("hash['" + n + "'] = " + hash[n]);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.6.1/lodash.min.js"></script>
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="//tjcrowder.github.io/simple-snippets-console/snippet.js"></script>