【问题标题】:Natural sort, array of objects, multiple columns, reverse, etc自然排序、对象数组、多列、反向等
【发布时间】:2023-04-09 23:31:01
【问题描述】:

我迫切需要实现客户端排序,模拟通过我们的 sweetpie api 进行的排序,它可以获取多个字段并返回排序后的数据。因此,例如,如果我有如下数据:

arr = [ 
  { name: 'Foo LLC',        budget: 3500,  number_of_reqs: 1040 }, 
  { name: '22nd Amendment', budget: 1500,  number_of_reqs: 2000 },
  { name: 'STS 10',         budget: 50000, number_of_reqs: 500  },
  ...
  etc.
]

并给定列进行排序,例如:['name', '-number_of_reqs'] 它应该按name(升序)和number_of_reqs(降序)排序。我无法理解这一点, 首先它必须是“自然排序”,如果我们谈论的是对单个列进行排序,它应该很容易得到,但我需要能够对多个列进行排序。

我也不确定为什么在使用 lodash 的 _.sortBy 时会得到不同的结果(从 api 的方式)? _.sortBy 不是“自然”还是我们的 api 坏了?

我也在寻找一个优雅的解决方案。最近才开始使用Ramdajs,真是太棒了。我敢打赌,使用它来构建我需要的排序会更容易吗?我试过了,还是不能正确。一点帮助?

更新:

我找到了this,并像这样将它与 Ramda 一起使用:

fn = R.compose(R.sort(naturalSort), R.pluck("name"))
fn(arr)

似乎适用于平面数组,但我仍然需要找到一种方法将其应用于数组中的多个字段

【问题讨论】:

    标签: javascript sorting underscore.js lodash ramda.js


    【解决方案1】:
    fn = R.compose(R.sort(naturalSort), R.pluck("name"))
    

    似乎在工作

    真的吗?我希望返回一个排序后的名称数组,而不是按名称属性对对象数组进行排序。

    不幸的是,使用sortBy 不允许我们提供自定义比较函数(自然排序所需),并且将多个列组合在一个值中进行一致比较可能是可能的,但很麻烦。

    我仍然不知道如何处理多个字段

    函数式编程在这里可以做很多事情,不幸的是 Ramda 并没有真正为比较器配备有用的函数(R.comparator 除外)。我们需要三个额外的助手:

    • on(如one from Haskell),它采用a -> b 转换和b -> b -> Number 比较器函数在两个as 上生成比较器。我们可以像这样用 Ramda 创建它:

      var on = R.curry(function(map, cmp) { 返回R.useWith(cmp, map, map); 返回 R.useWith(cmp, [地图, 地图]); // 因为 Ramda >0.18 });
    • or - 就像||,但数字不限于布尔值,如R.or。这可用于将两个比较器链接在一起,只有在第一个产生0(相等)时才调用第二个比较器。或者,可以使用像 thenBy 这样的库。但是让我们自己定义它:

      var or = R.curry(function(fst, snd, a, b) { 返回 fst(a, b) || snd(a, b); });
    • negate - 一个反转比较的函数:

      函数否定(cmp){ 返回R.compose(R.multiply(-1), cmp); }

    现在,配备这些我们只需要比较函数(自然排序是您找到的那个的改编版本,另请参阅Sort Array Elements (string with numbers), natural sort 了解更多信息):

    var NUMBER_GROUPS = /(-?\d*\.?\d+)/g;
    function naturalCompare(a, b) {
        var aa = String(a).split(NUMBER_GROUPS),
            bb = String(b).split(NUMBER_GROUPS),
            min = Math.min(aa.length, bb.length);
    
        for (var i = 0; i < min; i++) {
            var x = aa[i].toLowerCase(),
                y = bb[i].toLowerCase();
            if (x < y) return -1;
            if (x > y) return 1;
            i++;
            if (i >= min) break;
            var z = parseFloat(aa[i]) - parseFloat(bb[i]);
            if (z != 0) return z;
        }
        return aa.length - bb.length;
    }
    function stringCompare(a, b) {
        a = String(a); b = String(b);
        return +(a>b)||-(a<b);
    }
    function numberCompare(a, b) {
        return a-b;
    }
    

    现在我们可以准确地对您想要的对象进行比较:

    fn = R.sort(or(on(R.prop("name"), naturalCompare),
                   on(R.prop("number_of_reqs"), negate(numberCompare))));
    fn(arr)
    

    【讨论】:

    • 哇......这是一个了不起的答案,解释得远远超出了我的要求。非常感谢你。确实 - 功能性方式有时在你学会看之前感觉不是很简单。
    • @Bergi:请注意,Ramda 的 or 确实可以根据您的需要工作。文档显然需要更新。大约,R.or(f, g) =&gt; function() {return !!(f.apply(this, arguments) || g.apply(this, arguments));}
    • @ScottSauyet: 表达式之前的!! 是问题吗?我的否认不是关于 arity 或签名等,而是因为 Boolean 演员。
    • @Bergi:当然可以。我需要更仔细地阅读! :-)。它在我使用的样式比较器中运行良好,因为我不是使用返回正值或负值的函数构建它们,而是基于报告第一个参数是否小于第二个参数的谓词。无论如何,很好的答案。
    • @Bergi:这个反馈让我们意识到,在 and/or 函数中进行布尔转换是没有理由的。它们已在 HEAD 中删除,不会出现在下一个版本中。
    【解决方案2】:

    我认为这行得通。

    var arr = [
      { name: 'Foo LLC',        budget: 3500,  number_of_reqs: 1040 }, 
      { name: '22nd Amendment', budget: 1500,  number_of_reqs: 2000 },
      { name: 'STS 10',         budget: 50000, number_of_reqs: 5000 },
      { name: 'STS 10',         budget: 50000, number_of_reqs: 500  }
    ];
    
    var columns = ['name', 'number_of_reqs'];
    
    var NUMBER_GROUPS = /(-?\d*\.?\d+)/g;
    var naturalSort = function (a, b, columnname) {
      var a_field1 = a[columnname],
          b_field1 = b[columnname],
          aa = String(a_field1).split(NUMBER_GROUPS),
          bb = String(b_field1).split(NUMBER_GROUPS),
          min = Math.min(aa.length, bb.length);
    
      for (var i = 0; i < min; i++) {
        var x = parseFloat(aa[i]) || aa[i].toLowerCase(),
            y = parseFloat(bb[i]) || bb[i].toLowerCase();
        if (x < y) return -1;
        else if (x > y) return 1;
      }
    
      return 0;
    };
    
    arr.sort(function(a, b) {
      var result;
      for (var i = 0; i < columns.length; i++) {
        result = naturalSort(a, b, columns[i]);
        if (result !== 0) return result; // or -result for decending
      }
      return 0; //If both are exactly the same
    });
    
    console.log(arr);

    【讨论】:

    • 是的,应该可以工作......但不是很优雅。我敢打赌,使用 Ramda 可以使其更具声明性
    • @Agzam 只是好奇,你所说的声明性是什么意思?
    • 而不是告诉它如何做(循环通过列等),也许可以重写它以赋予更多功能重点。它应该更好读——“获取这些数据,以这种方式操作它——对这些列进行排序”等等。
    • Err... 看来这件事需要一些改进。似乎没有做反向操作,也没有考虑非字符串字段——数字、空值等。
    • @Agzam 真的吗?数字不起作用?似乎可以通过number_of_reqs 对两个“STS 10”进行排序。
    【解决方案3】:

    Bergi 的回答很有用也很有趣,但它改变了您请求的 API。这是创建您正在寻找的 API 的一个:

    var multisort = (function() {
        var propLt = R.curry(function(name, a, b) {
            return a[name] < b[name];
        });
        return function(keys, objs) {
            if (arguments.length === 0) {throw new TypeError('cannot sort on nothing');}
            var fns = R.map(function(key) {
                return key.charAt(0) === "-" ? 
                    R.pipe(R.comparator(propLt(R.substringFrom(1, key))), R.multiply(-1)) :
                    R.comparator(propLt(key));
            }, keys);
            var sorter = function(a, b) {
                return R.reduce(function(acc, fn) {return acc || fn(a, b);}, 0, fns);
            }
            return arguments.length === 1 ? R.sort(sorter) : R.sort(sorter, objs);
        };
    }());
    
    multisort(['name', '-number_of_reqs'], arr); //=> sorted clone
    

    它是手动柯里化而不是调用R.curry,因为相当一部分工作涉及创建单独的排序函数,如果您使用相同的键集对许多列表进行排序,则可以重用这些函数。如果这不是问题,可以稍微简化一下。

    【讨论】:

    • 更仔细地查看自然比较链接,这可能不行。它适用于小样本数据,但如果你想对“AB5”propLt。
    • 然而,这确实是值得深思的好东西。谢谢斯科特。感谢 Ramda 为我的生活带来了如此多的精彩。
    【解决方案4】:

    如果您愿意为您的项目添加另一个依赖项,@panosoft/ramda-utils 带有一个 compareProps 函数,该函数完全符合原始问题的要求。

    因此,鉴于您最初的示例,要按预算然后按名称降序排序,您可以执行以下操作:

    var props = ["-budget", "name"];
    var comparator = Ru.compareProps(props);
    var sortedList = R.sort(comparator, arr);
    

    【讨论】:

      【解决方案5】:

      使用 javascript 原生排序:

          
      
      Array.prototype.multisort = function(columns) {
        var arr = this;
        arr.sort(function(a, b) {
          return compare(a, b, 0);
        });
      
        function compare(a, b, colindex) {
          if (colindex >= columns.length) return 0;
      
          var columnname = columns[colindex];
          var a_field1 = a[columnname];
          var b_field1 = b[columnname];
          var asc = (colindex % 2 === 0);
      
          if (a_field1 < b_field1) return asc ? -1 : 1;
          else if (a_field1 > b_field1) return asc ? 1 : -1;
          else return compare(a, b, colindex + 1);
        }
      }
      
      
      
      var arr = [{ name: 'Foo LLC',      budget: 3500,  number_of_reqs: 1040    }, 
                 { name: '22nd Amendment',budget: 1500, number_of_reqs: 2000    }, 
                 { name: 'STS 10',        budget: 50000,number_of_reqs: 5000    }, 
                 { name: 'STS 10',        budget: 50000,number_of_reqs: 500    }];
      arr.multisort(['name', 'number_of_reqs']);
      if (window.console) window.console.log(arr);

      【讨论】:

      • 升序降序呢?
      • 升序返回结果,-result 降序返回。您还可以在 compare 方法中取一个标志并否定输出以进行降序。
      • 这是您可以在 javascript 中获得的最原生的。在上面构建你的逻辑。
      • @agzam 如果你用your link 的算法替换他的compare,它应该可以工作,等一下,让我试试
      • @gr 我需要字母数字比较器。看到问题了吗?数组的字段中包含带有数字的字符串。
      猜你喜欢
      • 1970-01-01
      • 2023-02-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-10
      • 2018-02-07
      • 1970-01-01
      • 2017-09-27
      相关资源
      最近更新 更多