Is it possible to sort a ES6 map object? - javascript

Is it possible to sort the entries of a es6 map object?
var map = new Map();
map.set('2-1', foo);
map.set('0-1', bar);
results in:
map.entries = {
0: {"2-1", foo },
1: {"0-1", bar }
}
Is it possible to sort the entries based on their keys?
map.entries = {
0: {"0-1", bar },
1: {"2-1", foo }
}

According MDN documentation:
A Map object iterates its elements in insertion order.
You could do it this way:
var map = new Map();
map.set('2-1', "foo");
map.set('0-1', "bar");
map.set('3-1', "baz");
var mapAsc = new Map([...map.entries()].sort());
console.log(mapAsc)
Using .sort(), remember that the array is sorted according to each character's Unicode code point value, according to the string conversion of each element. So 2-1, 0-1, 3-1 will be sorted correctly.

Short answer
new Map([...map].sort((a, b) =>
// Some sort function comparing keys with a[0] b[0] or values with a[1] b[1]
))
If you're expecting strings: As normal for .sort you need to return -1 if lower and 0 if equal; for strings, the recommended way is using .localeCompare() which does this correctly and automatically handles awkward characters like ä where the position varies by user locale.
So here's a simple way to sort a map by string keys:
new Map([...map].sort((a, b) => String(a[0]).localeCompare(b[0])))
...and by string values:
new Map([...map].sort((a, b) => String(a[1]).localeCompare(b[1])))
These are type-safe in that they won't throw an error if they hit a non-string key or value. The String() at the start forces a to be a string (and is good for readability), and .localeCompare() itself forces its argument to be a string without hitting an error.
In detail with examples
tldr: ...map.entries() is redundant, just ...map is fine; and a lazy .sort() without passing a sort function risks weird edge case bugs caused by string coercion.
The .entries() in [...map.entries()] (suggested in many answers) is redundant, probably adding an extra iteration of the map unless the JS engine optimises that away for you.
In the simple test case, you can do what the question asks for with:
new Map([...map].sort())
...which, if the keys are all strings, compares squashed and coerced comma-joined key-value strings like '2-1,foo' and '0-1,[object Object]', returning a new Map with the new insertion order:
Note: if you see only {} in SO's console output, look in your real browser console
const map = new Map([
['2-1', 'foo'],
['0-1', { bar: 'bar' }],
['3-5', () => 'fuz'],
['3-2', [ 'baz' ]]
])
console.log(new Map([...map].sort()))
HOWEVER, it's not a good practice to rely on coercion and stringification like this. You can get surprises like:
const map = new Map([
['2', '3,buh?'],
['2,1', 'foo'],
['0,1', { bar: 'bar' }],
['3,5', () => 'fuz'],
['3,2', [ 'baz' ]],
])
// Compares '2,3,buh?' with '2,1,foo'
// Therefore sorts ['2', '3,buh?'] ******AFTER****** ['2,1', 'foo']
console.log('Buh?', new Map([...map].sort()))
// Let's see exactly what each iteration is using as its comparator
for (const iteration of map) {
console.log(iteration.toString())
}
Bugs like this are really hard to debug - don't risk it!
If you want to sort on keys or values, it's best to access them explicitly with a[0] and b[0] in the sort function, like above; or with array destructuring in the function arguments:
const map = new Map([
['2,1', 'this is overwritten'],
['2,1', '0,1'],
['0,1', '2,1'],
['2,2', '3,5'],
['3,5', '2,1'],
['2', ',9,9']
])
// Examples using array destructuring. We're saying 'keys' and 'values'
// in the function names so it's clear and readable what the intent is.
const sortStringKeys = ([a], [b]) => String(a).localeCompare(b)
const sortStringValues = ([,a], [,b]) => String(a).localeCompare(b)
console.log('By keys:', new Map([...map].sort(sortStringKeys)))
console.log('By values:', new Map([...map].sort(sortStringValues)))
And if you need a different comparison than alphabetical order of strings, don't forget to always make sure you return -1 and 1 for before and after, not false or 0 as with raw a[0] > b[0] because that is treated as equals.

Convert Map to an array using Array.from, sort array, convert back to Map, e.g.
new Map(
Array
.from(eventsByDate)
.sort((a, b) => {
// a[0], b[0] is the key of the map
return a[0] - b[0];
})
)

I would suggest to use a custom iterator for your map object to achieve a sorted access, like so:
map[Symbol.iterator] = function* () {
yield* [...map.entries()].sort((a, b) => a[0].localeCompare(b[0]));
}
Using an iterator has the advantage of that it has only to be declared once. After adding/deleting entries in the map, a new for-loop over the map would automatically reflect this changes using an iterator. Sorted copies as shown in most of the above answers would not as they only reflect the map's state at exactly one point in time.
Here's the complete working example using your initial situation.
var map = new Map();
map.set('2-1', { name: 'foo' });
map.set('0-1', { name: 'bar' });
for (let [key, val] of map) {
console.log(key + ' - ' + val.name);
}
// 2-1 - foo
// 1-0 - bar
map[Symbol.iterator] = function* () {
yield* [...map.entries()].sort((a, b) => a[0].localeCompare(b[0]));
}
for (let [key, val] of map) {
console.log(key + ' - ' + val.name);
}
// 1-0 - bar
// 2-1 - foo
map.set('2-0', { name: 'zzz' });
for (let [key, val] of map) {
console.log(key + ' - ' + val.name);
}
// 1-0 - bar
// 2-0 - zzz
// 2-1 - foo
Regards.

The idea is to extract the keys of your map into an array. Sort this array. Then iterate over this sorted array, get its value pair from the unsorted map and put them into a new map. The new map will be in sorted order. The code below is it's implementation:
var unsortedMap = new Map();
unsortedMap.set('2-1', 'foo');
unsortedMap.set('0-1', 'bar');
// Initialize your keys array
var keys = [];
// Initialize your sorted maps object
var sortedMap = new Map();
// Put keys in Array
unsortedMap.forEach(function callback(value, key, map) {
keys.push(key);
});
// Sort keys array and go through them to put in and put them in sorted map
keys.sort().map(function(key) {
sortedMap.set(key, unsortedMap.get(key));
});
// View your sorted map
console.log(sortedMap);

You can convert to an array and call array soring methods on it:
[...map].sort(/* etc */);

Unfortunately, not really implemented in ES6. You have this feature with OrderedMap.sort() from ImmutableJS or _.sortBy() from Lodash.

One way is to get the entries array, sort it, and then create a new Map with the sorted array:
let ar = [...myMap.entries()];
sortedArray = ar.sort();
sortedMap = new Map(sortedArray);
But if you don't want to create a new object, but to work on the same one, you can do something like this:
// Get an array of the keys and sort them
let keys = [...myMap.keys()];
sortedKeys = keys.sort();
sortedKeys.forEach((key)=>{
// Delete the element and set it again at the end
const value = this.get(key);
this.delete(key);
this.set(key,value);
})

2 hours spent to get into details.
Note that the answer for question is already given at https://stackoverflow.com/a/31159284/984471
However, the question has keys that are not usual ones,
A clear & general example with explanation, is below that provides some more clarity:
Some more examples here: https://javascript.info/map-set
You can copy-paste the below code to following link, and modify it for your specific use case: https://www.jdoodle.com/execute-nodejs-online/
.
let m1 = new Map();
m1.set(6,1); // key 6 is number and type is preserved (can be strings too)
m1.set(10,1);
m1.set(100,1);
m1.set(1,1);
console.log(m1);
// "string" sorted (even if keys are numbers) - default behaviour
let m2 = new Map( [...m1].sort() );
// ...is destructuring into individual elements
// then [] will catch elements in an array
// then sort() sorts the array
// since Map can take array as parameter to its constructor, a new Map is created
console.log('m2', m2);
// number sorted
let m3 = new Map([...m1].sort((a, b) => {
if (a[0] > b[0]) return 1;
if (a[0] == b[0]) return 0;
if (a[0] < b[0]) return -1;
}));
console.log('m3', m3);
// Output
// Map { 6 => 1, 10 => 1, 100 => 1, 1 => 1 }
// m2 Map { 1 => 1, 10 => 1, 100 => 1, 6 => 1 }
// Note: 1,10,100,6 sorted as strings, default.
// Note: if the keys were string the sort behavior will be same as this
// m3 Map { 1 => 1, 6 => 1, 10 => 1, 100 => 1 }
// Note: 1,6,10,100 sorted as number, looks correct for number keys
Hope that helps.

The snippet below sorts given map by its keys and maps the keys to key-value objects again. I used localeCompare function since my map was string->string object map.
var hash = {'x': 'xx', 't': 'tt', 'y': 'yy'};
Object.keys(hash).sort((a, b) => a.localeCompare(b)).map(function (i) {
var o = {};
o[i] = hash[i];
return o;
});
result: [{t:'tt'}, {x:'xx'}, {y: 'yy'}];

Perhaps a more realistic example about not sorting a Map object but preparing the sorting up front before doing the Map. The syntax gets actually pretty compact if you do it like this. You can apply the sorting before the map function like this, with a sort function before map (Example from a React app I am working on using JSX syntax)
Mark that I here define a sorting function inside using an arrow function that returns -1 if it is smaller and 0 otherwise sorted on a property of the Javascript objects in the array I get from an API.
report.ProcedureCodes.sort((a, b) => a.NumericalOrder < b.NumericalOrder ? -1 : 0).map((item, i) =>
<TableRow key={i}>
<TableCell>{item.Code}</TableCell>
<TableCell>{item.Text}</TableCell>
{/* <TableCell>{item.NumericalOrder}</TableCell> */}
</TableRow>
)

As far as I see it's currently not possible to sort a Map properly.
The other solutions where the Map is converted into an array and sorted this way have the following bug:
var a = new Map([[1, 2], [3,4]])
console.log(a); // a = Map(2) {1 => 2, 3 => 4}
var b = a;
console.log(b); // b = Map(2) {1 => 2, 3 => 4}
a = new Map(); // this is when the sorting happens
console.log(a, b); // a = Map(0) {} b = Map(2) {1 => 2, 3 => 4}
The sorting creates a new object and all other pointers to the unsorted object get broken.

Slight variation - I didn't have spread syntax and I wanted to work on an object instead of a Map.
Object.fromEntries(Object.entries(apis).sort())

Here is function that sort Map() by decreasing.
function groupBy(list, keyGetter) {
const map = new Map();
list.forEach((item) => {
const key = keyGetter(item);
const collection = map.get(key);
if (!collection) {
map.set(key, [item]);
} else {
collection.push(item);
}
});
const sortedMap = new Map();
[...map].sort((a, b) => b[1].length - a[1].length).forEach(e => sortedMap.set(e[0], e[1]));
return sortedMap;
}
const test = groupBy(array, item => item.fieldName);

Related

Sort a Map - Array of Arrays [duplicate]

Is it possible to sort the entries of a es6 map object?
var map = new Map();
map.set('2-1', foo);
map.set('0-1', bar);
results in:
map.entries = {
0: {"2-1", foo },
1: {"0-1", bar }
}
Is it possible to sort the entries based on their keys?
map.entries = {
0: {"0-1", bar },
1: {"2-1", foo }
}
According MDN documentation:
A Map object iterates its elements in insertion order.
You could do it this way:
var map = new Map();
map.set('2-1', "foo");
map.set('0-1', "bar");
map.set('3-1', "baz");
var mapAsc = new Map([...map.entries()].sort());
console.log(mapAsc)
Using .sort(), remember that the array is sorted according to each character's Unicode code point value, according to the string conversion of each element. So 2-1, 0-1, 3-1 will be sorted correctly.
Short answer
new Map([...map].sort((a, b) =>
// Some sort function comparing keys with a[0] b[0] or values with a[1] b[1]
))
If you're expecting strings: As normal for .sort you need to return -1 if lower and 0 if equal; for strings, the recommended way is using .localeCompare() which does this correctly and automatically handles awkward characters like ä where the position varies by user locale.
So here's a simple way to sort a map by string keys:
new Map([...map].sort((a, b) => String(a[0]).localeCompare(b[0])))
...and by string values:
new Map([...map].sort((a, b) => String(a[1]).localeCompare(b[1])))
These are type-safe in that they won't throw an error if they hit a non-string key or value. The String() at the start forces a to be a string (and is good for readability), and .localeCompare() itself forces its argument to be a string without hitting an error.
In detail with examples
tldr: ...map.entries() is redundant, just ...map is fine; and a lazy .sort() without passing a sort function risks weird edge case bugs caused by string coercion.
The .entries() in [...map.entries()] (suggested in many answers) is redundant, probably adding an extra iteration of the map unless the JS engine optimises that away for you.
In the simple test case, you can do what the question asks for with:
new Map([...map].sort())
...which, if the keys are all strings, compares squashed and coerced comma-joined key-value strings like '2-1,foo' and '0-1,[object Object]', returning a new Map with the new insertion order:
Note: if you see only {} in SO's console output, look in your real browser console
const map = new Map([
['2-1', 'foo'],
['0-1', { bar: 'bar' }],
['3-5', () => 'fuz'],
['3-2', [ 'baz' ]]
])
console.log(new Map([...map].sort()))
HOWEVER, it's not a good practice to rely on coercion and stringification like this. You can get surprises like:
const map = new Map([
['2', '3,buh?'],
['2,1', 'foo'],
['0,1', { bar: 'bar' }],
['3,5', () => 'fuz'],
['3,2', [ 'baz' ]],
])
// Compares '2,3,buh?' with '2,1,foo'
// Therefore sorts ['2', '3,buh?'] ******AFTER****** ['2,1', 'foo']
console.log('Buh?', new Map([...map].sort()))
// Let's see exactly what each iteration is using as its comparator
for (const iteration of map) {
console.log(iteration.toString())
}
Bugs like this are really hard to debug - don't risk it!
If you want to sort on keys or values, it's best to access them explicitly with a[0] and b[0] in the sort function, like above; or with array destructuring in the function arguments:
const map = new Map([
['2,1', 'this is overwritten'],
['2,1', '0,1'],
['0,1', '2,1'],
['2,2', '3,5'],
['3,5', '2,1'],
['2', ',9,9']
])
// Examples using array destructuring. We're saying 'keys' and 'values'
// in the function names so it's clear and readable what the intent is.
const sortStringKeys = ([a], [b]) => String(a).localeCompare(b)
const sortStringValues = ([,a], [,b]) => String(a).localeCompare(b)
console.log('By keys:', new Map([...map].sort(sortStringKeys)))
console.log('By values:', new Map([...map].sort(sortStringValues)))
And if you need a different comparison than alphabetical order of strings, don't forget to always make sure you return -1 and 1 for before and after, not false or 0 as with raw a[0] > b[0] because that is treated as equals.
Convert Map to an array using Array.from, sort array, convert back to Map, e.g.
new Map(
Array
.from(eventsByDate)
.sort((a, b) => {
// a[0], b[0] is the key of the map
return a[0] - b[0];
})
)
I would suggest to use a custom iterator for your map object to achieve a sorted access, like so:
map[Symbol.iterator] = function* () {
yield* [...map.entries()].sort((a, b) => a[0].localeCompare(b[0]));
}
Using an iterator has the advantage of that it has only to be declared once. After adding/deleting entries in the map, a new for-loop over the map would automatically reflect this changes using an iterator. Sorted copies as shown in most of the above answers would not as they only reflect the map's state at exactly one point in time.
Here's the complete working example using your initial situation.
var map = new Map();
map.set('2-1', { name: 'foo' });
map.set('0-1', { name: 'bar' });
for (let [key, val] of map) {
console.log(key + ' - ' + val.name);
}
// 2-1 - foo
// 1-0 - bar
map[Symbol.iterator] = function* () {
yield* [...map.entries()].sort((a, b) => a[0].localeCompare(b[0]));
}
for (let [key, val] of map) {
console.log(key + ' - ' + val.name);
}
// 1-0 - bar
// 2-1 - foo
map.set('2-0', { name: 'zzz' });
for (let [key, val] of map) {
console.log(key + ' - ' + val.name);
}
// 1-0 - bar
// 2-0 - zzz
// 2-1 - foo
Regards.
The idea is to extract the keys of your map into an array. Sort this array. Then iterate over this sorted array, get its value pair from the unsorted map and put them into a new map. The new map will be in sorted order. The code below is it's implementation:
var unsortedMap = new Map();
unsortedMap.set('2-1', 'foo');
unsortedMap.set('0-1', 'bar');
// Initialize your keys array
var keys = [];
// Initialize your sorted maps object
var sortedMap = new Map();
// Put keys in Array
unsortedMap.forEach(function callback(value, key, map) {
keys.push(key);
});
// Sort keys array and go through them to put in and put them in sorted map
keys.sort().map(function(key) {
sortedMap.set(key, unsortedMap.get(key));
});
// View your sorted map
console.log(sortedMap);
You can convert to an array and call array soring methods on it:
[...map].sort(/* etc */);
Unfortunately, not really implemented in ES6. You have this feature with OrderedMap.sort() from ImmutableJS or _.sortBy() from Lodash.
One way is to get the entries array, sort it, and then create a new Map with the sorted array:
let ar = [...myMap.entries()];
sortedArray = ar.sort();
sortedMap = new Map(sortedArray);
But if you don't want to create a new object, but to work on the same one, you can do something like this:
// Get an array of the keys and sort them
let keys = [...myMap.keys()];
sortedKeys = keys.sort();
sortedKeys.forEach((key)=>{
// Delete the element and set it again at the end
const value = this.get(key);
this.delete(key);
this.set(key,value);
})
2 hours spent to get into details.
Note that the answer for question is already given at https://stackoverflow.com/a/31159284/984471
However, the question has keys that are not usual ones,
A clear & general example with explanation, is below that provides some more clarity:
Some more examples here: https://javascript.info/map-set
You can copy-paste the below code to following link, and modify it for your specific use case: https://www.jdoodle.com/execute-nodejs-online/
.
let m1 = new Map();
m1.set(6,1); // key 6 is number and type is preserved (can be strings too)
m1.set(10,1);
m1.set(100,1);
m1.set(1,1);
console.log(m1);
// "string" sorted (even if keys are numbers) - default behaviour
let m2 = new Map( [...m1].sort() );
// ...is destructuring into individual elements
// then [] will catch elements in an array
// then sort() sorts the array
// since Map can take array as parameter to its constructor, a new Map is created
console.log('m2', m2);
// number sorted
let m3 = new Map([...m1].sort((a, b) => {
if (a[0] > b[0]) return 1;
if (a[0] == b[0]) return 0;
if (a[0] < b[0]) return -1;
}));
console.log('m3', m3);
// Output
// Map { 6 => 1, 10 => 1, 100 => 1, 1 => 1 }
// m2 Map { 1 => 1, 10 => 1, 100 => 1, 6 => 1 }
// Note: 1,10,100,6 sorted as strings, default.
// Note: if the keys were string the sort behavior will be same as this
// m3 Map { 1 => 1, 6 => 1, 10 => 1, 100 => 1 }
// Note: 1,6,10,100 sorted as number, looks correct for number keys
Hope that helps.
The snippet below sorts given map by its keys and maps the keys to key-value objects again. I used localeCompare function since my map was string->string object map.
var hash = {'x': 'xx', 't': 'tt', 'y': 'yy'};
Object.keys(hash).sort((a, b) => a.localeCompare(b)).map(function (i) {
var o = {};
o[i] = hash[i];
return o;
});
result: [{t:'tt'}, {x:'xx'}, {y: 'yy'}];
Perhaps a more realistic example about not sorting a Map object but preparing the sorting up front before doing the Map. The syntax gets actually pretty compact if you do it like this. You can apply the sorting before the map function like this, with a sort function before map (Example from a React app I am working on using JSX syntax)
Mark that I here define a sorting function inside using an arrow function that returns -1 if it is smaller and 0 otherwise sorted on a property of the Javascript objects in the array I get from an API.
report.ProcedureCodes.sort((a, b) => a.NumericalOrder < b.NumericalOrder ? -1 : 0).map((item, i) =>
<TableRow key={i}>
<TableCell>{item.Code}</TableCell>
<TableCell>{item.Text}</TableCell>
{/* <TableCell>{item.NumericalOrder}</TableCell> */}
</TableRow>
)
As far as I see it's currently not possible to sort a Map properly.
The other solutions where the Map is converted into an array and sorted this way have the following bug:
var a = new Map([[1, 2], [3,4]])
console.log(a); // a = Map(2) {1 => 2, 3 => 4}
var b = a;
console.log(b); // b = Map(2) {1 => 2, 3 => 4}
a = new Map(); // this is when the sorting happens
console.log(a, b); // a = Map(0) {} b = Map(2) {1 => 2, 3 => 4}
The sorting creates a new object and all other pointers to the unsorted object get broken.
Slight variation - I didn't have spread syntax and I wanted to work on an object instead of a Map.
Object.fromEntries(Object.entries(apis).sort())
Here is function that sort Map() by decreasing.
function groupBy(list, keyGetter) {
const map = new Map();
list.forEach((item) => {
const key = keyGetter(item);
const collection = map.get(key);
if (!collection) {
map.set(key, [item]);
} else {
collection.push(item);
}
});
const sortedMap = new Map();
[...map].sort((a, b) => b[1].length - a[1].length).forEach(e => sortedMap.set(e[0], e[1]));
return sortedMap;
}
const test = groupBy(array, item => item.fieldName);

what does the square brackets do in the parameter of array.map?

not sure what is happening in .map(([key, value]) => [key, value * 2]) I have a gist idea of what the square brackets do in that parameter. But can someone clarify the rules of the syntax, or explain whats happening?
Here's my belief of how the code works:
Object.entries converts object prices to array. returns something like [[banana, 1], [orange, 2], [meat, 4]]
map is an array-method and the function doubles the prices of each item.
the chagnes are then reconverted back to object.
if map's going through the items; would it make more sense to write .map((item) => [item[0], item[1] * 2])
going through a lesson from javascript.info
let prices = {
banana: 1,
orange: 2,
meat: 4,
};
let doublePrices = Object.fromEntries(
// convert to array, map, and then fromEntries gives back the object
Object.entries(prices).map(([key, value]) => [key, value * 2])
);
console.log(doublePrices.meat); // 8
In the code snippet you're asking about the left side is quite different from the right side. Let's break it down:
.map(([key, value]) => [key, value * 2])
First, map takes a function as its parameter. Here the function is essentially doing this:
(entry) => {
const key = entry[0];
const value = entry[1];
const modified_entry = [ key, value * 2 ];
return modified_entry;
}
A shorter way to write that is:
(entry) => [ entry[0], entry[1] * 2 ]
That works the same because without curly brackets around the function body, it uses the value as the return value.
Now the code you're asking about isn't using a single variable for the input parameter to the function. It's a single parameter, but it sets 2 variables by using something called deconstruction or destructuring. This is a feature in Javascript that lets you break apart an object into multiple variables in a simple way. For example look at this:
var entry = [ 'meat', 1]
var [ item, price ] = entry
// now item is 'meat', and price is 1, which is nicer than having to do this:
item = entry[0]
price = entry[1]
So getting back to your code, they've expressed entry as [key,value] so that they can be used in the function easily, and on the right hand side of the => we have the return value which is a new array with the doubled price.
Remember that on the left side we're breaking an object into new variables which get assigned values, while on the right side we're creating a new Array object that's being returned. It can be a bit confusing because they both have the same form of looking like an array in square brackets.
Note also that deconstruction works for objects as well as arrays, so this is valid code too:
var obj = { a:1, b:2, c:3 }
var { a, c } = obj
console.log(a,c) // will print "1 3"
Deconstructing an object lets you take just the parts you want out of the object and creates variables named after the key. Notice that we didn't ask for b so there's no b variable - it's just ignored.

Sorting multiple array objects with different keys

Before, I was combining 2 arrays into one array and using sort(), I was able to sort them by created_at.
let result = [...item.messages, ...item.chat_messages]
result.sort((a, b) => new Date(b.created_at) - new Date(a.created_at))
item.messages = result
Now, I am in another scenario. I want to add one more array (sms_messages) into this array and however want it to order by its scheduled_at field.
Is it possible to achieve it with this approach?
let result = [...item.messages, ...item.chat_messages, ...item.sms_messages]
// and order by messages' and chat_messages' created_at (like above) together as
// sms_messages' sheduled_at
You could use || to get the first of both properties it finds to be there, assuming that each object has at least one of both properties, but scheduled_at gets precedence:
result.sort((a, b) =>
new Date(b.scheduled_at || b.created_at) - new Date(a.scheduled_at || a.created_at))
Just check which which property exists and use it to sort the objects.
const sortByDate = props => {
return (a, b) => {
let propA = props.find(p => a.hasOwnProperty(p));
let propB = props.find(p => b.hasOwnProperty(p));
if(!propA || !propB) return 0;
return new Date(b[propB]) - new Date(a[propA]);
};
}
result.sort(sortByDate(['scheduled_at', 'created_at']));

How to implement a map or sorted-set in javascript

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"]) { }
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/
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
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
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
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
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();
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());
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

Javascript: Sorting Objects Challenge

I have a bit of a challenge. I am working on a physics application with javascript. The two main objects being used are
var force = new Object();
var torque = new Object();
with properties
force.magnitude = newArray();
force.lengthfromorigin = new Array();
force.count;
torque.lengthfromorigin= new Array();
torque.count;
now, I'd like to sort these two objects into an array based on their respective lengthfromorigins
Example: force.lengthfromorigin = [5,8] and torque.lengthfromorigin=[2,6]
so their order in this newArray would be [ torque[0], force[0], torque[1], force[1] ]
My question is it possible to have an array of different objects sorted by their respective properties, and to then use this array in a function which will make decisions based on which object is at the index. Also will I need to have an id property in each respective object to identify if the object is a torque or force.
Example:
if(newArray[i] == torque)
//do stuff
else
//do other stuff.
Something like this perhaps?
Let me explain the algorithm:
Create a new array let it be called A.
For each objects in objects:
2.1 Let the current object be called obj.
2.2 Use map to generate a new array called tuples of [obj, num] tuples
for each lengthFromOrigin numbers of obj.
3.3 Push all items of tuples into A.
Sort A on tuple[1] (which is the number) ascending.
var objects = [
{ type: 'force', lengthFromOrigin: [5, 8] },
{ type: 'torque', lengthFromOrigin: [2, 6] }
],
sorted = objects.reduce(function (arr, obj) {
arr.push.apply(arr, obj.lengthFromOrigin.map(function (num) {
return [obj, num];
}));
return arr;
}, []).sort(function (a, b) {
return a[1] - b[1];
});
console.log(sorted);
Then you can loop over sorted and easily identify if it's a torque or force by looking at the first element in the tuple.
sorted.forEach(function (tuple) {
console.log(tuple[0].type, tuple[1]);
});
//torque 2
//force 5
//torque 6
//force 8
The answer is Yes,
But you have to identify each object before you access their properties. In your case Both of the objects have a common property called lengthfromorigin which can be used to sort them properly.
To identify each object you can use a property like ID or Name.
if(Mydata[i].Name = 'torque'){
//your code goes here
}
else if(Mydata[i].Name = 'force'){
//your code goes here
}
Hope this will help you

Categories