理解sort()compareFunction

时间:2013-07-10 03:44:29

标签: javascript jquery

我正在使用的电子商务平台缺乏重新排序产品属性字段选项的功能。它真的很糟糕,因为要插入一个新选项,你几乎必须删除所有现有选项并重新开始。我试图做客户端而不是。这就是我正在使用的(这是鞋子尺码):

  • 9 EE
  • 9 1/2 EE
  • 10 EE
  • 10 1/2 EE
  • 11 EE
  • 11 1/2 EE
  • 9 EEEE
  • 9 1/2 D
  • 9 1/2 EEEE
  • 10 EEEE
  • 10 1/2 EEEE
  • 11 EEEE
  • 9 D
  • 11 1/2 EEEE

这些实际上是表单中某些<option>的文本。值的格式为X Y Z,其中:

  • X是一个整数
  • Y是字符串“1/2”,可能不存在
  • Z是一个字母代码,可以是“D”,“E”,“EEE”或“EEEE”,也可能不存在

以上所需的顺序是:

  • 9 D
  • 9 1/2 D
  • 9 EE
  • 9 1/2 EE
  • 9 EEEE
  • 9 1/2 EEEE
  • 10 EE
  • 10 1/2 EE
  • 10 EEEE
  • 10 1/2 EEEE
  • 11 EE
  • 11 1/2 EE
  • 11 EEEE
  • 11 1/2 EEEE

我已经学习了一些关于javascript sort()函数的知识,但是还没有完全理解你可以传递给它的比较函数是如何工作的。到目前为止我已经有了这个:

<select>
    <option>9 EE</option>
    <option>9 1/2 EE</option>
    <option>10 EE</option>
    <option>10 1/2 EE</option>
    <option>11 EE</option>
    <option>11 1/2 EE</option>
    <option>9 EEEE</option>
    <option>9 1/2 D</option>
    <option>9 1/2 EEEE</option>
    <option>10 EEEE</option>
    <option>10 1/2 EEEE</option>
    <option>11 EEEE</option>
    <option>9 D</option>
    <option>11 1/2 EEEE</option>
</select>

我从这个答案的代码开始:https://stackoverflow.com/a/667198/398242

$("select").html($("option").sort(function (a, b) {
    return a.text == b.text ? 0 : a.text < b.text ? -1 : 1
}));

对这样的项目进行排序(即使是第一个标准也不起作用):

  • 10 1/2 EE
  • 10 1/2 EEEE
  • 10 EE
  • 10 EEEE
  • 11 1 / 2 EE
  • 11 1/2 EEEE
  • 11 EE
  • 11 EEEE
  • 9 1/2 D
  • < li> 9 1/2 EE
  • 9 1/2 EEEE
  • 9 D
  • 9 EE
  • 9 EEEE

我在javascript '11' > '9'中看到false返回function compare(a, b) { if (a is less than b by some ordering criterion) return -1; if (a is greater than b by the ordering criterion) return 1; // a must be equal to b return 0; } ,这对我来说毫无意义。

MDN describes the compare function argument就这样,我得到了它:

{{1}}

...但我不知道如何调整它以符合我的要求。我尝试了一些东西,但我觉得我在黑暗中拍摄。我试图表明我花了一些时间来尝试理解这个问题。我有兴趣了解更多,但是现在我只想解决这个问题。

http://jsfiddle.net/DnwJ6/任何线索?

6 个答案:

答案 0 :(得分:2)

按照您希望的顺序将值放在数组中:

var shoeSizes = [ '9 D','9 1/2 D','9 EE','9 1/2 EE', ...]

使用 Array.protoype.indexOf with a shim for older browsers)获取数组中匹配文本的索引。使用索引进行比较,例如:

function(a,b) {
  return shoeSizes.indexOf(a) - shoeSizes.indexOf(b);
}

如果您需要处理数组中没有的值,请测试 indexOf 返回的值,如果它是-1,则替换默认值。

或者,您可以将大小设置为对象中属性值的名称,并指定特定值:

var shoeSizes = { '9 D': 5, '9 1/2 D': 10, '9 EE': 15, '9 1/2 EE': 20, ...};

然后使用比较中的值:

function(a,b) {
  return shoeSizes[a] - shoeSizes[b];
}

或者允许默认值:

function(a,b) {
  return (a in shoeSizes? shoeSizes[a] : 1000) - (b in shoeSizes? shoeSizes[b] : 1000);
}

答案 1 :(得分:2)

由于您已经格式化了文本,因此一种方法是在比较它们之前规范化文本元素。

这个解决方案可能不是那么理想,但会完成工作

$("select").html($("option").sort(function (a, b) {
    return nomalize(a.text) < nomalize(b.text) ? -1 : 1;
}));

function nomalize(val){
    var parts =  val.split(' '), op = '';

    op = parts[0].length == 1 ? '0' + parts[0] : parts[0];
    if(parts.length > 1){
        if(/[a-z]/i.test(parts[1])){
            op += '0/0' + parts[1];
        } else {
            op += parts[1]
        }
    }

    op += parts.length > 2 ? parts[2] : '';
    return op;
}

演示:Fiddle

如果有人可以建议任何解决方案来进一步优化它

答案 2 :(得分:1)

首先要了解的是,12 确实 小于9如果您要对文本项目进行排序,而不是数字。这是因为<1> <2>小于<9> <nothing>,因为在这种情况下19是“主键”。

您面临的第二个问题是长度(9 1/2)是主键还是宽度(EE)。我怀疑前者会更有意义,所以继续这样做。

决定之后,最好的办法是为调用提供一个排序函数,将每个字符串转换为数值,然后比较该值。例如:

  1. 获取第一个(空格分隔)字段(9)并将值设置为该值。
  2. 如果下一个字段存在并且为1/2,请将0.5添加到该值。
  3. 如果最后一个字段存在(alpha字段),只需将其转换为某个小于0.5的值并添加(例如a - > 0.01,B - &gt ; 0.02,...,EEEE - &gt; 0.08等等。)
  4. 最后一个取决于宽度的相对排序,我选择了一个典型的美国系统。

    你最终得到的是一个值,它决定了正确的排序,你的排序功能可以简单地进行数值比较。一个例子如下:

    function xlat(s) {
        var s2 = s.split(" ");
    
        var n = parseInt(s2[0]);
        if (s2.length == 1) { return n; }
    
        var last = s2[1];
        if (last == '1/2') {
            n = n + 0.5;
            if (s2.length == 2) { return n; }
            last = s2[2];
        }
        var widths = ['A','B','C','D','E','EE','EEE','EEEE','F','G'];
        n = n + widths.indexOf(last) / 100;
        return n;
    }
    
    $("select").html($("option").sort(function (a, b) {
        var na = xlat(a.text);
        var nb = xlat(b.text);
        return na == nb ? 0 : na < nb ? -1 : 1;
    }));
    

    xlat函数在这里很重要。它首先将大小拆分为1个,2个或3个元素的数组,并获取第一个的数值。如果第二个和第三个不存在,则返回此值(处理“裸”大小,如913)。

    否则它决定它是否是半增量长度 - 如果第二个字段是1/2则决定。此时,它还会检测是否没有宽度并返回大小。

    一旦超过这一点,我们就有了大小(整数或一半),last变量保持宽度。然后,我们只需根据此大小在数组中的位置添加一个值,进行适当修改(除以100),这样就不会影响主键。

    通过使用您自己的代码,您可以得到(正如预期的那样):

    9 D
    9 EE
    9 EEEE
    9 1/2 D
    9 1/2 EE
    9 1/2 EEEE
    10 EE
    10 EEEE
    10 1/2 EE
    10 1/2 EEEE
    11 EE
    11 EEEE
    11 1/2 EE
    11 1/2 EEEE
    

答案 3 :(得分:1)

首先需要定义一个合适的排序键,您可以使用它进行合理的比较;以下函数使用正则表达式来挖掘有用的信息位:

function sortkey(val)
{
    var matches = val.match(/^(\d+)( 1\/2)? (\w+)$/),
    number = +matches[1];

    if (matches[2]) {
      number += 0.5; // add "1/2"
    }

    return [number, matches[3]];
}

第一场比赛被投入一个数字;如果第二个匹配可用,则添加0.5。然后,将最后一个匹配作为辅助排序键添加。返回值是这样的:

[9.5, 'EE']

然后,此结构可用于比较功能:

function compareFunc(a, b) {
    var sa = sortkey(a.text),
    sb = sortkey(b.text);

    if (sa[0] == sb[0]) {
        return sa[1] < sb[1] ? -1 : 1;
    } else {
        return sa[0] < sb[0] ? -1 : 1;
    }
}

应用于您的特定代码:

var $sorted = $('select > option').sort(compareFunc);
$('select').html($sorted);

Demo

答案 4 :(得分:1)

检查一下:

$("select").html($("option").sort(function (a, b) {
    var regex = /(\d+)((?: 1\/2)? )([DE]+)/;
    var abreakdown = a.text.match(regex), bbreakdown = b.text.match(regex);
    if (parseInt(abreakdown[1]) === parseInt(bbreakdown[1])) {
        if (abreakdown[3] === bbreakdown[3]) {
            return (abreakdown[2] === bbreakdown[2] ? 0 : (abreakdown[2] < bbreakdown[2] ? -1 : 1));    
        } else {
            return abreakdown[3] < bbreakdown[3] ? -1 : 1;
        }
    } else { return parseInt(abreakdown[1]) - parseInt(bbreakdown[1]); }
}));

它使用正则表达式来分解碎片,然后根据每个组件进行比较。

Demo fiddle.

答案 5 :(得分:1)

我看到你已经有了一个有效的解决方案,但仅仅是为了比较(双关语),这是其他许多方法之一(fiddle):

// Make a shoe size sortable as text
function sortableSize( text ) {
    // Split the size into parts separated by spaces
    var parts = text.split( ' ' );
    // The first part is the size number;
    // make sure it is two digits (09,10,etc.)
    if( parts[0].length == 1 )
        parts[0] = '0' + parts[0];
    // If it wasn't a 1/2 size, make it 0/2
    if( parts.length == 2 )
        parts.splice( 1, 0, '0/2' );
    // So '9 EE' becomes '09 0/2 EE'
    return parts.join( ' ' );
}

var $options = $('#sizes option');
$options = $options.sort( function( a, b ) {
    a = sortableSize( a.text );
    b = sortableSize( b.text );
    return a < b ? -1 : a > b ? 1 : 0;
});
$('#sizes').html( $options );

此方法创建每个鞋码的文本表示,可直接排序为文本。然后它使用那些文本表示进行排序。