能否对 ES6 的 Map 对象进行排序?

173

是否可以对es6 Map对象的条目进行排序?


var map = new Map();
map.set('2-1', foo);
map.set('0-1', bar);

导致结果如下:

map.entries = {
    0: {"2-1", foo },
    1: {"0-1", bar }
}

是否可以根据键对条目进行排序?

map.entries = {
    0: {"0-1", bar },
    1: {"2-1", foo }
}

5
地图本质上是无序的(除非按照插入顺序迭代,这需要排序然后重新添加)。 - user2864740
1
在遍历映射表时对条目进行排序(即将其转换为数组) - Halcyon
2
map = new Map([...map].sort()) OR map = new Map([...map].sort((a,b)=>a-b)) - Manohar Reddy Poreddy
15个回答

212

根据 MDN 文档:

Map 对象迭代它的元素是按插入顺序进行的。

你可以这样做:

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)

使用 .sort() 方法时,请记住数组是根据每个字符的 Unicode 代码点值进行排序的,根据每个元素的字符串转换进行排序。所以2-1, 0-1, 3-1将被正确地排序。


12
你可以将该函数(a,b)的内容缩短为以下代码:var mapAsc = new Map([...map.entries()].sort((a,b) => a[0] > b[0]));,使用箭头函数(lambda)。 - Jimmy Chandra
2
实际上,它将2-1,foo0-1,bar3-1,baz进行词法比较。 - Bergi
32
@JimmyChandra:不要使用(a,b) => a[0] > b[0] - Bergi
5
如果没有...点,你就在尝试对一个MapIterator进行排序,这些...点非常重要。 - muttonUp
2
如果地图键是数字,则在排序地图中将1e-9之类的数字放在100之后,会得到无效结果。 适用于数字的代码:new Map([...map.entries()].sort((e1, e2) => e1[0] - e2[0])) - cdalxndr
显示剩余7条评论

151

简短回答

 new Map([...map].sort((a, b) => 
   // Some sort function comparing keys with a[0] b[0] or values with a[1] b[1]
 ))

如果你期望的是字符串:正常情况下,对于.sort您需要返回-1(较低)和0(相等);对于字符串,推荐的方法是使用.localeCompare(),它可以正确地处理特殊字符,例如位置因用户区域设置而异的ä

因此,这里有一种通过键名排序映射的简单方法:

 new Map([...map].sort((a, b) => String(a[0]).localeCompare(b[0])))

...并通过字符串

 new Map([...map].sort((a, b) => String(a[1]).localeCompare(b[1])))

这些代码是类型安全的,如果遇到非字符串键或值,它们不会抛出错误。代码中的 String() 强制将 a 转换为字符串(并且有助于可读性),.localeCompare() 自身强制其参数成为一个字符串而不会产生错误。


详细解释及示例

简而言之:使用 ...map 即可,无需使用 ...map.entries();未传递排序函数的懒惰 .sort() 可能存在由于字符串转换引起奇怪的边缘情况错误。

[...map.entries()] 中的 .entries()(在许多答案中建议使用)是冗余的,除非 JS 引擎为您优化掉了该迭代的额外开销。

在简单测试用例中,您可以使用以下代码来实现所需功能:

new Map([...map].sort())

如果键全部为字符串,则比较压缩和强制转换逗号连接的键值字符串,例如'2-1,foo''0-1,[object Object]',返回一个新的具有新插入顺序的Map:

注意:如果在SO的控制台输出中仅看到{},请查看您真实的浏览器控制台

const map = new Map([
  ['2-1', 'foo'],
  ['0-1', { bar: 'bar' }],
  ['3-5', () => 'fuz'],
  ['3-2', [ 'baz' ]]
])

console.log(new Map([...map].sort()))

然而,依赖强制类型转换和字符串化并不是一个好的实践方式。你可能会得到一些意料之外的结果:

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())
}

像这样的错误很难调试 - 不要冒险!

如果您想按键或值排序,最好在排序函数中明确访问它们,如上所示使用a[0]b[0];或者在函数参数中使用数组解构:

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)))

如果你需要一种不同的比较方式而不是字符串按字母顺序排列,一定要记得始终返回-11表示前面和后面,而不是false0,就像使用原始的a[0] > b[0]那样,因为它被视为相等。


23
好的,我会尽力进行翻译:这真是一个好答案,我们需要修复S.O.的发现机制。像这样优秀的内容不应该被埋没在这里。 - serraosays
只是想要补充一下,对于想要对这种类型的映射进行排序的人: Map<String,String[]> console.log('按键排序:', new Map([...your_map].sort(([a]:[string, string[]], [b]:[string, string[]]) => String(a[0]).localeCompare(b[0])))) console.log('按值排序:', new Map([...your_map].sort(([,a]:[string, string[]], [,b]:[string, string[]]) => String(a[0]).localeCompare(b[0])))) - Jonah Tornovsky
@serraosays是的,当一个好答案出现在被接受的答案之后时,没有办法强迫提问者重新考虑哪个答案是正确的。 - jcollum

39

使用Array.fromMap转换为数组,对数组进行排序,然后将其转换回Map,例如:

new Map(
  Array
    .from(eventsByDate)
    .sort((a, b) => {
      // a[0], b[0] is the key of the map
      return a[0] - b[0];
    })
)

1
[...map.values()].sort() 对我来说不起作用,但是 Array.from(map.values()).sort() 却可以。 - Rob Juurlink
3
这确实帮了我,如果没有 Array.from,TypeScript 会报编译错误。 - icSapper

8
我建议您使用自定义迭代器来访问映射对象以实现排序访问,代码如下:


map[Symbol.iterator] = function* () {
    yield* [...map.entries()].sort((a, b) => a[0].localeCompare(b[0]));
}

使用迭代器的优点在于只需要声明一次。在添加/删除映射条目后,对映射的新循环将使用迭代器自动反映这些变化。与大多数上面的答案所示的排序副本不同,它们仅反映映射在某个时间点的状态。

以下是使用您的初始情况的完整工作示例。

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

敬礼。


1
有趣的回答。值得一提的是,map 只会在直接迭代 map 本身的迭代上进行排序,比如 [...map]for (… of map)。它不会对 map.keys()map.entries()map.values() 的迭代进行排序。例如,有人可能期望 [...map.values()][...map].map(([,value]) => value) 具有相同的输出,但应用此方法后,前者将具有插入顺序,而后者将应用此排序。 - user56reinstatemonica8

6
你可以将其转换成数组并调用数组排序方法:
[...map].sort(/* etc */);

3
那又怎样?你得到的是一个数组,而不是一个映射或对象。 - Green
5
你失去了钥匙。 - braks

6

这个想法是将您的映射键提取到一个数组中。对这个数组进行排序。然后迭代这个排序后的数组,从未排序的映射中获取其值对,并将它们放入一个新的映射中。这个新映射将被排序。以下是它的实现代码:

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);

2
未排序的键可以通过简单地使用 unsortedMap.keys() 来确定。而且 keys.sort().map... 应该改为 keys.sort().forEach... - faintsignal

4

很可惜,这个特性在ES6中并没有被完全实现。不过你可以使用ImmutableJS的OrderedMap.sort()或者Lodash的_.sortBy()来实现此功能。


4

花了2个小时深入细节。

注意,问题的答案已经在https://dev59.com/vF0Z5IYBdhLWcg3wtSE8#31159284上给出。

然而,这个问题的关键不是通常的关键字,
下面提供了一个清晰且通俗易懂的带解释的例子,以提供更多的清晰度:

.

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

希望能帮到您。

如何使用相同的方法按值进行排序? - stack0114106
请分享一个例子以便更好地理解。 - Manohar Reddy Poreddy

3

一种方法是获取条目数组,对其进行排序,然后使用排序后的数组创建新的Map:

let ar = [...myMap.entries()];
sortedArray = ar.sort();
sortedMap = new Map(sortedArray);

但是如果你不想创建一个新的对象,而是想在同一个对象上工作,你可以这样做:

// 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
下面的代码片段将给定的映射按其键排序,并再次将键映射到键值对象。由于我的映射是字符串 -> 字符串对象映射,我使用了localeCompare函数。
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'}];


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接