是否可以对 ES6 地图对象进行排序?

Posted

技术标签:

【中文标题】是否可以对 ES6 地图对象进行排序?【英文标题】:Is it possible to sort a ES6 map object? 【发布时间】:2015-09-18 11:13:28 【问题描述】:

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

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 

【问题讨论】:

地图本质上是无序的(除了迭代插入顺序,它需要排序然后[重新]添加)。 在遍历地图时对条目进行排序(即把它变成一个数组) map = new Map([...map].sort())map = new Map([...map].sort((a,b)=>a-b)) 【参考方案1】:

根据 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 会被正确排序。

【讨论】:

您可以使用箭头函数 (lambda) 将该函数 (a,b) 内容缩短为:var mapAsc = new Map([...map.entries()].sort((a,b) => a[0] > b[0])); 实际上,它在词法上将2-1,foo0-1,bar3-1,baz 进行比较 @JimmyChandra: Don't use (a,b) => a[0] > b[0]! ... 点很重要,否则您正在尝试对 MapIterator 进行排序 如果映射键是数字,你会得到无效的结果,像1e-9这样的数字在排序后的映射中放在100之后。适用于数字的代码:new Map([...map.entries()].sort((e1, e2) => e1[0] - e2[0]))【参考方案2】:

简答

 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;对于字符串,the recommended way 正在使用 .localeCompare(),它可以正确执行此操作并自动处理像 ä 这样位置因用户区域而异的尴尬字符。

所以这里有一个简单的方法来通过字符串keys对地图进行排序:

 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() 本身将其参数强制为字符串而不会出现错误。


用例子详细说明

tldr:...map.entries() 是多余的,...map 就可以了;一个懒惰的.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 与原始 @ 987654350@,因为这被视为平等。

【讨论】:

多么好的答案,我们必须修复 S.O.发现机制。这么好的东西不应该丢在这里。【参考方案3】:

使用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];
    )
)

【讨论】:

[...map.values()].sort() 对我不起作用,但 Array.from(map.values()).sort() 对我有用 这实际上帮助了我,没有 Array.from,Typescript 给了我一个编译错误。【参考方案4】:

这个想法是将地图的键提取到一个数组中。对该数组进行排序。然后遍历这个排序数组,从未排序的映射中获取它的值对并将它们放入一个新映射中。新地图将按排序顺序排列。下面的代码是它的实现:

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

【讨论】:

可以通过unsortedMap.keys()简单地确定未排序的键。 keys.sort().map... 也应该是 keys.sort().forEach...【参考方案5】:

您可以转换为数组并在其上调用数组排序方法:

[...map].sort(/* etc */);

【讨论】:

然后呢?你得到一个数组而不是地图或对象 你丢失了钥匙【参考方案6】:

不幸的是,没有真正在 ES6 中实现。您可以通过 ImmutableJS 的 OrderedMap.sort() 或 Lodash 的 _.sortBy() 获得此功能。

【讨论】:

【参考方案7】:

一种方法是获取条目数组,对其进行排序,然后使用排序后的数组创建一个新 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);
)

【讨论】:

【参考方案8】:

下面的 sn-p 按其键对给定的映射进行排序,并再次将键映射到键值对象。我使用了 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;
        );

结果:[t:'tt', x:'xx', y: 'yy'];

【讨论】:

【参考方案9】:

花了 2 小时了解细节。

注意问题的答案已经在https://***.com/a/31159284/984471给出

但是,该问题的键不常见, 下面是一个带有解释的清晰通用示例,它提供了一些更清晰的信息:

这里有更多示例:https://javascript.info/map-set 您可以将以下代码复制粘贴到以下链接,并针对您的特定用例进行修改: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

希望对您有所帮助。

【讨论】:

【参考方案10】:

我建议为您的地图对象使用 自定义迭代器 来实现排序访问,如下所示:

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

使用迭代器的优点是它只需要声明一次。在地图中添加/删除条目后,地图上的新 for 循环将使用迭代器自动反映此更改。上述大多数答案中显示的排序副本不会,因为它们仅反映地图在某个时间点的状态。

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

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

问候。

【讨论】:

【参考方案11】:

也许是一个更现实的例子,关于不对 Map 对象进行排序,而是在执行 Map 之前预先准备排序。如果你这样做,语法实际上会变得非常紧凑。您可以像这样在 map 函数之前应用排序,在 map 之前使用排序函数(来自我正在使用 JSX 语法的 React 应用程序的示例)

标记我在这里使用箭头函数定义了一个排序函数

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

【讨论】:

【参考方案12】:

据我所知,目前无法正确排序地图。

其他将Map转换为数组并以这种方式排序的解决方案存在以下错误:

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

排序创建了一个新对象,所有其他指向未排序对象的指针都被破坏了。

【讨论】:

【参考方案13】:

略有不同 - 我没有扩展语法,我想使用 object 而不是 Map

Object.fromEntries(Object.entries(apis).sort())

【讨论】:

【参考方案14】:

除了上面的正确答案:

如果仅在角度模板中需要排序的地图,则可以使用 KeyValuePipe 轻松排序 其中包含默认排序:

<div *ngFor="let item of map | keyvalue">
  item.key :  item.value 
</div>

它按数字或字符串按字母顺序排序。见https://angular.io/api/common/KeyValuePipe

【讨论】:

以上是关于是否可以对 ES6 地图对象进行排序?的主要内容,如果未能解决你的问题,请参考以下文章

对对象图进行排序 C++

使用 Thrust CUDA 对对象进行排序

获取属性错误:“地图”对象没有属性“排序”

根据另一个地图中的值对Java Map进行排序

如何对 ES6 `Set` 进行排序?

如何根据其值的属性对地图进行排序?