【问题标题】:Sort version-dotted number strings in Javascript?在Javascript中对版本点数字字符串进行排序?
【发布时间】:2016-10-23 09:41:40
【问题描述】:

我有以下字符串的数组:

['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'] 

...等等。

我需要一个能够为我提供以下有序结果的解决方案

['4.5.0', '4.21.0', '4.22.0', '5.1.0', '5.5.1', '6.1.0'].

我试图实现一个排序,所以它首先按第一个位置的数字排序,而不是在相等的情况下,按第二个位置的数字排序(在第一个点之后),依此类推......

我尝试使用 sort()localeCompare(),但如果我有元素 '4.5.0''4.11.0',我会将它们排序为 ['4.11.0','4.5.0'],但我需要得到['4.5.0','4.11.0']

我怎样才能做到这一点?

【问题讨论】:

  • 你还有一些文字,还是罗马数字?

标签: javascript sorting


【解决方案1】:

您可以将所有部分添加到固定大小的字符串中,然后对其进行排序,最后再次删除填充。

var arr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
arr = arr.map( a => a.split('.').map( n => +n+100000 ).join('.') ).sort()
         .map( a => a.split('.').map( n => +n-100000 ).join('.') );

console.log(arr)

显然,您必须明智地选择数字 100000 的大小:它应该比您的最大数字部分至少多一个数字。

带正则表达式

当您使用replace 方法的回调参数时,无需拆分和连接即可实现相同的操作:

var arr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
arr = arr.map( a => a.replace(/\d+/g, n => +n+100000 ) ).sort()
         .map( a => a.replace(/\d+/g, n => +n-100000 ) );

console.log(arr)

只定义一次填充函数

由于 padding 和它的反向函数非常相似,使用一个函数 f 似乎是一个很好的练习,并带有一个额外的参数来定义“方向"(1=填充,-1=未填充)。这导致了这个非常晦涩和极端的代码。考虑这只是为了好玩,而不是真正使用:

var arr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
arr = (f=>f(f(arr,1).sort(),-1)) ((arr,v)=>arr.map(a=>a.replace(/\d+/g,n=>+n+v*100000)));

console.log(arr);

使用sort比较回调函数

您可以使用sort 的比较函数参数来实现相同的目的:

arr.sort( (a, b) => a.replace(/\d+/g, n => +n+100000 )
                     .localeCompare(b.replace(/\d+/g, n => +n+100000 )) );

但是对于较大的数组,这会导致性能下降。这是因为排序算法通常需要多次比较某个值,每次都与数组中的不同值进行比较。这意味着必须为相同的数字多次执行填充。因此,对于较大的数组,首先在整个数组中应用填充,然后使用标准排序,然后再次删除填充会更快。

但对于较短的数组,这种方法可能仍然是最快的。在这种情况下,所谓的 natural 排序选项——可以通过localeCompare 的额外参数实现——将比填充方法更有效:

var arr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
arr = arr.sort( (a, b) => a.localeCompare(b, undefined, { numeric:true }) );

console.log(arr);

更多关于填充和一元加号

要查看填充的工作原理,请查看它生成的中间结果:

[ "100005.100005.100001", "100004.100021.100000", "100004.100022.100000", 
  "100006.100001.100000", "100005.100001.100000" ]

关于表达式+n+100000,请注意第一个+unary plus,它是将字符串编码的十进制数转换为其等效数字的最有效方法。添加 100000 以使数字具有固定位数。当然,它也可以是 200000 或 300000。请注意,此添加不会更改数字按数字排序时的顺序。

以上只是填充字符串的一种方法。请参阅此Q&A 了解其他一些替代方案。

【讨论】:

  • 我没有使用+part + 100000,而是使用常规的字符串填充:'0'.repeat(8 - part.length) + part。这样你也支持像“1.0a”这样的版本。
  • 啊啊,IE缺少String.prototype.repeat()……正因为如此,你不得不这样做:'00000000'.substr(0, 8 - part.length) + part
  • 还发现此方法在['1.1', '1.01'] 上失败...
  • @GrasDouble,['1.1', '1.01'] 是一个有趣的案例。这可能会让人感到困惑:['4.21.0', '4.5.0', '4.13.0', '4.06.0']... 至少有两种方法可以解释这个升序:(1)通过数值:['4.5.0', '4.06.0', '4.13.0', '4.21.0'],或(2)通过字符比较:['4.06.0', '4.13.0', '4.21.0', '4.5.0'] 和OP 的示例 4.21 预计会高于 4.5,这是根据逻辑 (1)。我想人们必须选择应该通过哪种逻辑(1 或 2)生成订单。
  • 是的,这种情况很棘手,无论是解释还是程序解析。这可能就是“语义版本控制”禁止它的原因。不过,如果需要支持,我有 published a function 支持它。
【解决方案2】:

如果您正在寻找一个 npm 包来比较两个 semver 版本,https://www.npmjs.com/package/compare-versions 就是其中一个。

然后你可以像这样对版本进行排序:

// ES6/TypeScript
import compareVersions from 'compare-versions';

var versions = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
var sorted = versions.sort(compareVersions);

【讨论】:

    【解决方案3】:

    您可以拆分字符串并比较各个部分。

    function customSort(data, order) {
    
      function isNumber(v) {
        return (+v).toString() === v;
      }
    
      var sort = {
        asc: function (a, b) {
          var i = 0,
            l = Math.min(a.value.length, b.value.length);
    
          while (i < l && a.value[i] === b.value[i]) {
            i++;
          }
          if (i === l) {
            return a.value.length - b.value.length;
          }
          if (isNumber(a.value[i]) && isNumber(b.value[i])) {
            return a.value[i] - b.value[i];
          }
          return a.value[i].localeCompare(b.value[i]);
        },
        desc: function (a, b) {
          return sort.asc(b, a);
        }
      }
      var mapped = data.map(function (el, i) {
        return {
          index: i,
          value: el.split('')
        };
      });
    
      mapped.sort(sort[order] || sort.asc);
      return mapped.map(function (el) {
        return data[el.index];
      });
    }
    
    var array = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0'];
    
    console.log('sorted array asc', customSort(array));
    console.log('sorted array desc ', customSort(array, 'desc'));
    console.log('original array ', array);
    .as-console-wrapper { max-height: 100% !important; top: 0; }

    【讨论】:

    • 这是正确的解决方案。 trincot 的回答有点老套。
    【解决方案4】:

    如果值不同,您可以在循环中检查,返回差异,否则继续

    var a=['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
    
    a.sort(function(a,b){
      var a1 = a.split('.');
      var b1 = b.split('.');
      var len = Math.max(a1.length, b1.length);
      
      for(var i = 0; i< len; i++){
        var _a = +a1[i] || 0;
        var _b = +b1[i] || 0;
        if(_a === _b) continue;
        else return _a > _b ? 1 : -1
      }
      return 0;
    })
    
    console.log(a)

    【讨论】:

    • 不是 Math.max,因为这样你就超出了较短列表的范围。使用 min 然后 return b1.length - a1.length 而不是零。
    【解决方案5】:

    虽然有点晚了,但这将是我的解决方案;

    var arr = ["5.1.1","5.1.12","5.1.2","3.7.6","2.11.4","4.8.5","4.8.4","2.10.4"],
     sorted = arr.sort((a,b) => {var aa = a.split("."),
                                     ba = b.split(".");
                                 return +aa[0] < +ba[0] ? -1
                                                        : aa[0] === ba[0] ? +aa[1] < +ba[1] ? -1
                                                                                            : aa[1] === ba[1] ? +aa[2] < +ba[2] ? -1
                                                                                                                                : 1
                                                                                                              : 1
                                                                          : 1;
                                });
     console.log(sorted);

    【讨论】:

      【解决方案6】:

      如果点之间只有数字,这似乎可行:

      var a = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0']
      
      a = a.map(function (x) {
          return x.split('.').map(function (x) {
              return parseInt(x)
          })
      }).sort(function (a, b) {
          var i = 0, m = a.length, n = b.length, o, d
          o = m < n ? n : m
          for (; i < o; ++i) {
              d = (a[i] || 0) - (b[i] || 0)
              if (d) return d
          }
          return 0
      }).map(function (x) {
          return x.join('.')
      })
      

      【讨论】:

        【解决方案7】:
        'use strict';
        
        var arr = ['5.1.2', '5.1.1', '5.1.1', '5.1.0', '5.7.2.2'];
        
        Array.prototype.versionSort = function () {
        
            var arr = this;
        
            function isNexVersionBigger (v1, v2) {
                var a1 = v1.split('.');
                var b2 = v2.split('.');
                var len = a1.length > b2.length ? a1.length : b2.length;
                for (var k = 0; k < len; k++) {
                    var a = a1[k] || 0;
                    var b = b2[k] || 0;
                    if (a === b) {
                        continue;
                    } else
        
                        return b < a;
                }
            }
        
            for (var i = 0; i < arr.length; i++) {
                var min_i = i;
                for (var j = i + 1; j < arr.length; j++) {
                    if (isNexVersionBigger(arr[i], arr[j])) {
                        min_i = j;
                    }
                }
                var temp = arr[i];
                arr[i] = arr[min_i];
                arr[min_i] = temp;
            }
            return arr;
        }
        
        console.log(arr.versionSort());
        

        【讨论】:

          【解决方案8】:

          此解决方案考虑了可能不是完整的 3 部分格式的版本号(例如,如果版本号之一只是 2 或 2.0 或 0.1 等)。

          我写的自定义排序函数可能主要是你要找的,它只需要{"major":X, "minor":X, "revision":X}格式的对象数组:

          var versionArr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
          var versionObjectArr = [];
          var finalVersionArr = [];
          /*
          split each version number string by the '.' and separate them in an
          object by part (major, minor, & revision).  If version number is not
          already in full, 3-part format, -1 will represent that part of the
          version number that didn't exist.  Push the object into an array that
          can be sorted.
          */
          for(var i = 0; i < versionArr.length; i++){
            var splitVersionNum = versionArr[i].split('.');
            var versionObj = {};
            switch(splitVersionNum.length){
              case 1:
                versionObj = {
                  "major":parseInt(splitVersionNum[0]),
                  "minor":-1,
                  "revision":-1
                };
                break;
              case 2:
                versionObj = {
                  "major":parseInt(splitVersionNum[0]),
                  "minor":parseInt(splitVersionNum[1]),
                  "revision":-1
                };
                break;
              case 3:
                versionObj = {
                  "major":parseInt(splitVersionNum[0]),
                  "minor":parseInt(splitVersionNum[1]),
                  "revision":parseInt(splitVersionNum[2])
                };
            }
            versionObjectArr.push(versionObj);
          }
          
          //sort objects by parts, going from major to minor to revision number.
          versionObjectArr.sort(function(a, b){
            if(a.major < b.major) return -1;
            else if(a.major > b.major) return 1;
            else {
              if(a.minor < b.minor) return -1;
              else if(a.minor > b.minor) return 1;
              else {
                if(a.revision < b.revision) return -1;
                else if(a.revision > b.revision) return 1;
              }
            }
          });
          
          /*
          loops through sorted object array to recombine it's version keys to match the original string's value.  If any trailing parts of the version
          number are less than 0 (i.e. they didn't exist so we replaced them with
          -1) then leave that part of the version number string blank. 
          */
          for(var i = 0; i < versionObjectArr.length; i++){
            var versionStr = "";
            for(var key in versionObjectArr[i]){
              versionStr = versionObjectArr[i].major;
              versionStr += (versionObjectArr[i].minor < 0 ? '' : "." + versionObjectArr[i].minor);
              versionStr += (versionObjectArr[i].revision < 0 ? '' : "." + versionObjectArr[i].revision);
            }
            finalVersionArr.push(versionStr);
          }
          console.log('Original Array: ',versionArr);
          console.log('Expected Output: ',['4.5.0', '4.21.0', '4.22.0', '5.1.0', '5.5.1', '6.1.0']);
          console.log('Actual Output: ', finalVersionArr);

          【讨论】:

            【解决方案9】:

            灵感来自已接受的答案,但与 ECMA5 兼容,并使用常规字符串填充(请参阅我的 cmets 答案):

            function sortCallback(a, b) {
            
                function padParts(version) {
                    return version
                        .split('.')
                        .map(function (part) {
                            return '00000000'.substr(0, 8 - part.length) + part;
                        })
                        .join('.');
                }
            
                a = padParts(a);
                b = padParts(b);
            
                return a.localeCompare(b);
            }
            

            用法:

            ['1.1', '1.0'].sort(sortCallback);
            

            【讨论】:

              【解决方案10】:

              const arr = ["5.1.1","5.1.12","5.1.2","3.7.6","2.11.4","4.8.5","4.8.4","2.10.4"];
              const sorted = arr.sort((a,b) => {
                const ba = b.split('.');
                const d = a.split('.').map((a1,i)=>a1-ba[i]);
                return d[0] ? d[0] : d[1] ? d[1] : d[2]
              });
              
              console.log(sorted);

              【讨论】:

              • 不支持重复版本。
              【解决方案11】:

              这可以使用排序方法以一种更简单的方式实现,无需对任何数字进行硬编码,并且采用更通用的方式。

              enter code here
              
              var arr = ['5.1.2', '5.1.1', '5.1.1', '5.1.0', '5.7.2.2'];
              
              splitArray = arr.map(elements => elements.split('.'))
              
              //now lets sort based on the elements on the corresponding index of each array
              
              //mapped.sort(function(a, b) {
              //  if (a.value > b.value) {
              //    return 1;
              //  }
              //  if (a.value < b.value) {
              //    return -1;
              //  }
              //  return 0;
              //});
              
              //here we compare the first element with the first element of the next version number and that is [5.1.2,5.7.2] 5,5 and 1,7 and 2,2 are compared to identify the smaller version...In the end use the join() to get back the version numbers in the proper format.
              
              sortedArray = splitArray.sort((a, b) => {
                for (i in a) {
                  if (parseInt(a[i]) < parseInt(b[i])) {
                    return -1;
                    break
                  }
                  if (parseInt(a[i]) > parseInt(b[i])) {
                    return +1;
                    break
                  } else {
                    continue
                  }
                }
              }).map(p => p.join('.'))
              
              sortedArray = ["5.1.0", "5.1.1", "5.1.1", "5.1.2", "5.7.2.2"]

              【讨论】:

                【解决方案12】:
                • 排序1.0a 符号正确
                • 使用原生localeCompare1.090 表示法进行排序

                function log(label,val){
                  document.body.append(label,String(val).replace(/,/g," - "),document.createElement("BR"));
                }
                
                const sortVersions = (
                  x,
                  v = s => s.match(/[a-z]|\d+/g).map(c => c==~~c ? String.fromCharCode(97 + c) : c)
                ) => x.sort((a, b) => (a + b).match(/[a-z]/) 
                                             ? v(b) < v(a) ? 1 : -1 
                                             : a.localeCompare(b, 0, {numeric: true}))
                
                let v=["1.90.1","1.090","1.0a","1.0.1","1.0.0a","1.0.0b","1.0.0.1","1.0a"];
                log(' input : ',v);
                log('sorted: ',sortVersions(v));
                log('no dups:',[...new Set(sortVersions(v))]);

                【讨论】:

                  【解决方案13】:

                  在 ES6 中你可以不用正则表达式。

                  const versions = ["0.4", "0.11", "0.4.1", "0.4", "0.4.2", "2.0.1","2", "0.0.1", "0.2.3"];
                  
                  const splitted = versions.map(version =>
                      version
                          .split('.')
                          .map(i => +i))
                          .map(i => {
                             let items;
                             if (i.length === 1) {
                               items = [0, 0]
                               i.push(...items)
                             }
                             if (i.length === 2) {
                               items = [0]
                               i.push(...items)
                             }
                  
                             return i
                          })
                          .sort((a, b) => {
                            for(i in a) {
                              if (a[i] < b[i]) {
                                return -1;
                              }
                              if (a[i] > b[i]) {
                                return +1;
                              }
                          }
                      })
                      .map(item => item.join('.'))
                  
                  const sorted = [...new Set(splitted)]
                  

                  【讨论】:

                    【解决方案14】:

                    这是我基于 @trincot 开发的一个解决方案,即使字符串不完全是“1.2.3”,它也会按 semver 排序 - 它们可能是“v1.2.3”或“2.4”

                    function sortSemVer(arr, reverse = false) {
                        let semVerArr = arr.map(i => i.replace(/(\d+)/g, m => +m + 100000)).sort(); // +m is just a short way of converting the match to int
                        if (reverse)
                            semVerArr = semVerArr.reverse();
                    
                        return semVerArr.map(i => i.replace(/(\d+)/g, m => +m - 100000))
                    }
                    
                    console.log(sortSemVer(["1.0.1", "1.0.9", "1.0.10"]))
                    console.log(sortSemVer(["v2.1", "v2.0.9", "v2.0.12", "v2.2"], true))

                    【讨论】:

                      【解决方案15】:

                      如果 ES6 我这样做:

                      versions.sort((v1, v2) => {
                        let [, major1, minor1, revision1 = 0] = v1.match(/([0-9]+)\.([0-9]+)(?:\.([0-9]+))?/);
                        let [, major2, minor2, revision2 = 0] = v2.match(/([0-9]+)\.([0-9]+)(?:\.([0-9]+))?/);
                        if (major1 != major2) return parseInt(major1) - parseInt(major2);
                        if (minor1 != minor2) return parseInt(minor1) - parseInt(major2);
                        return parseInt(revision1) - parseInt(revision2);
                      });
                      

                      【讨论】:

                        【解决方案16】:
                        **Sorted Array Object by dotted version value**
                        
                            var sampleData = [
                              { name: 'Edward', value: '2.1.2' },
                              { name: 'Sharpe', value: '2.1.3' },
                              { name: 'And', value: '2.2.1' },
                              { name: 'The', value: '2.1' },
                              { name: 'Magnetic', value: '2.2' },
                              { name: 'Zeros', value: '0' },
                              { name: 'Zeros', value: '1' }
                            ];
                            arr = sampleData.map( a => a.value).sort();
                            var requireData = [];
                        
                            arr.forEach(function(record, index){    
                              var findRecord = sampleData.find(arr => arr.value === record);
                                if(findRecord){
                                  requireData.push(findRecord);
                                }
                              });
                            console.log(requireData);
                        
                            [check on jsfiddle.net][1]
                            [1]: https://jsfiddle.net/jx3buswq/2/
                        
                            It is corrected now!!!
                        

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 2011-12-27
                          • 1970-01-01
                          • 2018-04-02
                          • 1970-01-01
                          • 2012-11-14
                          • 2016-12-05
                          • 2010-09-08
                          相关资源
                          最近更新 更多