是否可以对 ES6 映射对象进行排序

Is it possible to sort a ES6 map object?

本文关键字:对象 排序 映射 ES6 是否      更新时间:2023-09-26

是否可以对 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 }
}

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

简答

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

<小时 />

详细示例

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,而不是像原始a[0] > b[0]那样false0,因为这被视为相等的。

使用 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[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

问候。

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

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

您可以转换为数组并对其调用数组 soring 方法:

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

2 个小时来了解细节。

请注意,问题的答案已在 https://stackoverflow.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

希望有帮助。

不幸的是,在 ES6 中并没有真正实现。您可以使用ImmutableJS的OrderedMap.sort((或Lodash的_.sortBy((使用此功能。

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

下面的代码片段按键对给定的映射进行排序,并再次将键映射到键值对象。我使用了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'}];

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

标记我在这里使用箭头函数在内部定义了一个排序函数,如果它较小,则返回 -1,否则根据我从 API 获得的数组中 Javascript 对象的属性进行排序。

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

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

将 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}
排序

会创建一个新对象,并且指向未排序对象的所有其他指针都会损坏。

略有变化 - 我没有传播语法,我想在object而不是Map上工作。

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

这是通过递减对 Map(( 进行排序的函数。

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