自然排序,对象数组,多列,反向等

时间:2014-10-12 08:37:22

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

我迫切需要实现客户端排序,它通过我们的tastypie 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(降序)排序。我无法理解这一点, 首先,它必须是#34;自然排序"如果我们谈论排序单个列,它应该很容易获得,但我需要能够排序多个。

此外,我不确定为什么在使用lodash _.sortBy时,我会得到不同的结果(来自api如何做到这一点)? _.sortBy不是"自然"或者我们的api坏了吗?

此外,我一直在寻找一个优雅的解决方案。刚刚开始使用Ramdajs,它非常棒。我敢打赌,建立我需要使用的排序会更容易吗?我已经尝试过了,仍然无法做到正确。帮助不大?

upd:

我找到this并将其与Ramda一起使用,如下所示:

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

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

5 个答案:

答案 0 :(得分:8)

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

似乎正在运作

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

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

  

我仍然不知道如何为多个字段执行此操作

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

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

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

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

    function negate(cmp) {
        return 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)

答案 1 :(得分: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);

答案 2 :(得分: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,因为创建单独的排序函数涉及相当多的工作,如果您使用相同的键集排序许多列表,则可以重复使用它们。如果这不是一个问题,可以稍微简化一下。

答案 3 :(得分:0)

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

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

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

答案 4 :(得分:-1)

使用javascript本地排序:

&#13;
&#13;
    

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);
&#13;
&#13;
&#13;

相关问题