2

我迫切需要实现客户端排序,模拟通过我们的 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's 时会得到不同的结果(从 api 的方式)_.sortBy?不是_.sortBy“自然”还是我们的api坏了?

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

更新:

我发现了这个并将它与 Ramda 一起使用,如下所示:

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

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

4

5 回答 5

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

似乎在工作

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

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

我仍然不知道如何为多个领域做到这一点

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

  • on(就像来自 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);
    }
    

现在,配备这些我们只需要比较函数(自然排序是您找到的那个的改编版本,另请参见排序数组元素(带数字的字符串),自然排序了解更多信息):

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)
于 2014-10-12T14:21:50.870 回答
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  }
];

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

于 2014-10-12T09:18:59.637 回答
1

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,因为相当一部分工作涉及创建单独的排序函数,如果您使用相同的键集对许多列表进行排序,则可以重用这些函数。如果这不是问题,这可以简化一点。

于 2014-10-12T21:34:10.250 回答
0

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

因此,给定您的原始示例,按预算然后按名称降序排序,您可以执行以下操作:

var props = ["-budget", "name"];
var comparator = Ru.compareProps(props);
var sortedList = R.sort(comparator, arr);
于 2016-04-21T14:15:55.957 回答
-1

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

于 2014-10-12T09:04:07.470 回答