【问题标题】:Is it possible to sort a ES6 map object?是否可以对 ES6 地图对象进行排序?
【发布时间】: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))

标签: javascript ecmascript-6


【解决方案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 进行比较
  • ... 点很重要,否则您正在尝试对 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://stackoverflow.com/a/31159284/984471给出

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

        .

        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

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2010-10-15
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多