2010-07-10 1 views
8

Qualcuno mi può indirizzare verso un algoritmo di ordinamento in javascript che dovrebbe ordinare allo stesso modo di SQL Server (per le colonne nvarchar/unicode)?L'ordinamento Javascript corrisponde all'ordinamento di SQL Server

Per riferimento, la mia domanda precedente su questo comportamento può essere trovato qui: SQL Server 2008 - different sort orders on VARCHAR vs NVARCHAR values

Piuttosto che tentare di modificare il comportamento di ordinamento sul lato server, c'è un modo che posso abbinare a questa sul lato client? La mia domanda precedente parlava specificamente di trattini negli ordinamenti, ma assumerò che ci sia un po 'di più che semplicemente ignorando i trattini come parte del genere.

ho aggiunto alcuni casi d'uso supplementari qui per dimostrare meglio la questione

dati campione come ordinato da SQL Server (2008):

?test 
^&$Grails Found 
bags of Garbage 
Brochures distributed 
Calls Received 
exhibit visitors 
Exhibit Visitors 
-Exhibit Visitors 
--Exhibit Visitors 
Ëxhibit Visitors 
Grails Found 

Come posso ottenere javascript per ordinare gli stessi valori in allo stesso modo?

Per favore fatemi sapere se posso chiarire ulteriormente.

+0

Così, da quella domanda, si desidera che il Javascript per ora Ordina Unicode 'A' prima Unicode' -a'? –

+0

@Brock - corretto, anche se in particolare, voglio un algoritmo di ordinamento javascript che corrisponda a quello sul lato server (immagino ci sia altro da considerare rispetto ai caratteri "-") – DanP

risposta

6

First qual è il tuo database di confronto? Ho intenzione di supporre che sia SQL_Latin1_General_CP1_CS_AS o SQL_Latin1_General_CP1_CI_AS. Se è così, allora il seguente dovrebbe funzionare (non ancora completamente testato).

Sembra scrivere un true Il sorter Unicode è un'impresa importante. Ho visto i codici fiscali più semplici delle specifiche. ;-) Sembra sempre coinvolgere la (e) tabella (i) di ricerca e almeno un ordinamento a 3 livelli - con la modifica dei caratteri e delle contrazioni da considerare.

ho limitato quanto segue alla Latin 1, Latin Extended-A, e Latin Extended-B tavoli/fascicolazione. L'algoritmo dovrebbe funzionare abbastanza bene su quegli insiemi, ma non l'ho completamente testato, né correttamente considerato la modifica dei caratteri (per risparmiare velocità e complessità).

Vedere quello in action at jsbin.com.

funzione:

function bIgnoreForPrimarySort (iCharCode) 
{ 
    /*--- A bunch of characters get ignored for the primary sort weight. 
     The most important ones are the hyphen and apostrophe characters. 
     A bunch of control characters and a couple of odds and ends, make up 
     the rest. 
    */ 
    if (iCharCode < 9)             return true; 

    if (iCharCode >= 14 && iCharCode <= 31)       return true; 

    if (iCharCode >= 127 && iCharCode <= 159)       return true; 

    if (iCharCode == 39 || iCharCode == 45 || iCharCode == 173) return true; 

    return false; 
} 


function SortByRoughSQL_Latin1_General_CP1_CS_AS (sA, sB) 
{ 
    /*--- This Sorts Latin1 and extended Latin1 unicode with an approximation 
     of SQL's SQL_Latin1_General_CP1_CS_AS collation. 
     Certain modifying characters or contractions my be off (not tested), we trade-off 
     perfect accuracy for speed and relative simplicity. 

     True unicode sorting is devilishly complex and we're not getting paid enough to 
     fully implement it in Javascript. ;-) 

     It looks like a definative sort would require painstaking exegesis of documents 
     such as: http://unicode.org/reports/tr10/ 
    */ 
    //--- This is the master lookup table for Latin1 code-points. Here through the extended set \u02AF 
    //--- Make this static? 
    var aSortOrder = [ 
        -1, 151, 152, 153, 154, 155, 156, 157, 158, 2, 3, 4, 5, 6, 159, 160, 161, 162, 163, 164, 
        165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 0, 7, 8, 9, 10, 11, 12, 210, 
        13, 14, 15, 41, 16, 211, 17, 18, 65, 69, 71, 74, 76, 77, 80, 81, 82, 83, 19, 20, 
        42, 43, 44, 21, 22, 214, 257, 266, 284, 308, 347, 352, 376, 387, 419, 427, 438, 459, 466, 486, 
        529, 534, 538, 559, 576, 595, 636, 641, 647, 650, 661, 23, 24, 25, 26, 27, 28, 213, 255, 265, 
        283, 307, 346, 350, 374, 385, 418, 426, 436, 458, 464, 485, 528, 533, 536, 558, 575, 594, 635, 640, 
        646, 648, 660, 29, 30, 31, 32, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 
        190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 
         1, 33, 53, 54, 55, 56, 34, 57, 35, 58, 215, 46, 59, 212, 60, 36, 61, 45, 72, 75, 
        37, 62, 63, 64, 38, 70, 487, 47, 66, 67, 68, 39, 219, 217, 221, 231, 223, 233, 250, 276, 
        312, 310, 316, 318, 392, 390, 395, 397, 295, 472, 491, 489, 493, 503, 495, 48, 511, 599, 597, 601, 
        603, 652, 590, 573, 218, 216, 220, 230, 222, 232, 249, 275, 311, 309, 315, 317, 391, 389, 394, 396, 
        294, 471, 490, 488, 492, 502, 494, 49, 510, 598, 596, 600, 602, 651, 589, 655, 229, 228, 227, 226, 
        235, 234, 268, 267, 272, 271, 270, 269, 274, 273, 286, 285, 290, 287, 324, 323, 322, 321, 314, 313, 
        326, 325, 320, 319, 358, 357, 362, 361, 356, 355, 364, 363, 378, 377, 380, 379, 405, 404, 403, 402, 
        401, 400, 407, 406, 393, 388, 417, 416, 421, 420, 432, 431, 428, 440, 439, 447, 446, 444, 443, 442, 
        441, 450, 449, 468, 467, 474, 473, 470, 469, 477, 484, 483, 501, 500, 499, 498, 507, 506, 527, 526, 
        540, 539, 544, 543, 542, 541, 561, 560, 563, 562, 567, 566, 565, 564, 580, 579, 578, 577, 593, 592, 
        611, 610, 609, 608, 607, 606, 613, 612, 617, 616, 615, 614, 643, 642, 654, 653, 656, 663, 662, 665, 
        664, 667, 666, 574, 258, 260, 262, 261, 264, 263, 281, 278, 277, 304, 292, 289, 288, 297, 335, 337, 
        332, 348, 349, 369, 371, 382, 415, 409, 434, 433, 448, 451, 462, 476, 479, 509, 521, 520, 524, 523, 
        531, 530, 552, 572, 571, 569, 570, 583, 582, 581, 585, 632, 631, 634, 638, 658, 657, 669, 668, 673, 
        677, 676, 678, 73, 79, 78, 680, 644, 50, 51, 52, 40, 303, 302, 301, 457, 456, 455, 482, 481, 
        480, 225, 224, 399, 398, 497, 496, 605, 604, 626, 625, 620, 619, 624, 623, 622, 621, 334, 241, 240, 
        237, 236, 254, 253, 366, 365, 360, 359, 430, 429, 505, 504, 515, 514, 675, 674, 422, 300, 299, 298, 
        354, 353, 84, 85, 86, 87, 239, 238, 252, 251, 513, 512, 243, 242, 245, 244, 328, 327, 330, 329, 
        411, 410, 413, 412, 517, 516, 519, 518, 547, 546, 549, 548, 628, 627, 630, 629, 88, 89, 90, 91, 
        92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 
        112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 
        132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 246, 247, 248, 259, 279, 280, 293, 291, 
        339, 336, 338, 331, 340, 341, 342, 423, 367, 373, 351, 370, 372, 383, 381, 384, 408, 414, 386, 445, 
        453, 452, 454, 461, 463, 460, 475, 478, 465, 508, 522, 525, 532, 550, 553, 554, 555, 545, 556, 557, 
        537, 551, 568, 333, 424, 343, 344, 586, 584, 618, 633, 637, 639, 645, 659, 649, 670, 671, 672, 679, 
        681, 682, 683, 282, 686, 256, 345, 368, 375, 425, 435, 437, 535, 684, 685, 305, 296, 306, 591, 587, 
        588, 144, 145, 146, 147, 148, 149, 150 
        ]; 

    var iLenA   = sA.length, iLenB   = sB.length; 
    var jA    = 0,   jB    = 0; 
    var sIgnoreBuff_A = [],   sIgnoreBuff_B = []; 


    function iSortIgnoreBuff() 
    { 
     var iIgLenA = sIgnoreBuff_A.length, iIgLenB = sIgnoreBuff_B.length; 
     var kA  = 0,     kB  = 0; 

     while (kA < iIgLenA && kB < iIgLenB) 
     { 
      var igA = sIgnoreBuff_A [kA++], igB = sIgnoreBuff_B [kB++]; 

      if (aSortOrder[igA] > aSortOrder[igB]) return 1; 
      if (aSortOrder[igA] < aSortOrder[igB]) return -1; 
     } 
     //--- All else equal, longest string loses 
     if (iIgLenA > iIgLenB)  return 1; 
     if (iIgLenA < iIgLenB)  return -1; 

     return 0; 
    } 


    while (jA < iLenA && jB < iLenB) 
    { 
     var cA = sA.charCodeAt (jA++); 
     var cB = sB.charCodeAt (jB++); 

     if (cA == cB) 
     { 
      continue; 
     } 

     while (bIgnoreForPrimarySort (cA)) 
     { 
      sIgnoreBuff_A.push (cA); 
      if (jA < iLenA) 
       cA = sA.charCodeAt (jA++); 
      else 
       break; 
     } 
     while (bIgnoreForPrimarySort (cB)) 
     { 
      sIgnoreBuff_B.push (cB); 
      if (jB < iLenB) 
       cB = sB.charCodeAt (jB++); 
      else 
       break; 
     } 

     /*--- Have we reached the end of one or both strings, ending on an ignore char? 
      The strings were equal, up to that point. 
      If one of the strings is NOT an ignore char, while the other is, it wins. 
     */ 
     if (bIgnoreForPrimarySort (cA)) 
     { 
      if (! bIgnoreForPrimarySort (cB)) return -1; 
     } 
     else if (bIgnoreForPrimarySort (cB)) 
     { 
      return 1; 
     } 
     else 
     { 
      if (aSortOrder[cA] > aSortOrder[cB]) 
       return 1; 

      if (aSortOrder[cA] < aSortOrder[cB]) 
       return -1; 

      //--- We are equal, so far, on the main chars. Where there ignore chars? 
      var iBuffSort = iSortIgnoreBuff(); 
      if (iBuffSort) return iBuffSort; 

      //--- Still here? Reset the ignore arrays. 
      sIgnoreBuff_A = []; 
      sIgnoreBuff_B = []; 
     } 

    } //-- while (jA < iLenA && jB < iLenB) 

    /*--- We have gone through all of at least one string and they are still both 
     equal barring ignore chars or unequal lengths. 
    */ 
    var iBuffSort = iSortIgnoreBuff(); 
    if (iBuffSort) return iBuffSort; 

    //--- All else equal, longest string loses 
    if (iLenA > iLenB)  return 1; 
    if (iLenA < iLenB)  return -1; 

    return 0; 

} //-- function SortByRoughSQL_Latin1_General_CP1_CS_AS 

prova:

var aPhrases = [ 
        'Grails Found', 
        '--Exhibit Visitors', 
        '-Exhibit Visitors', 
        'Exhibit Visitors', 
        'Calls Received', 
        'Ëxhibit Visitors', 
        'Brochures distributed', 
        'exhibit visitors', 
        'bags of Garbage', 
        '^&$Grails Found', 
        '?test' 
       ]; 

aPhrases.sort (SortByRoughSQL_Latin1_General_CP1_CS_AS); 

console.log (aPhrases.join ('\n')); 

Risultati:

?test 
^&$Grails Found 
bags of Garbage 
Brochures distributed 
Calls Received 
exhibit visitors 
Exhibit Visitors 
-Exhibit Visitors 
--Exhibit Visitors 
Ëxhibit Visitors 
Grails Found 
+0

Ho verificato che le regole di confronto del server sono impostate su: SQL_Latin1_General_CP1_CI_AS, esaminerò il tuo metodo per vedere come si analizza. Per inciso, penso di essere stato un po 'a buon mercato con la taglia ... se funziona, lascerò che scada prima di accettare la tua risposta in modo che io possa premiarti più in alto (ciò che sembra giusto/ragionevole?) – DanP

+0

@ DanP: Non preoccuparti della taglia (a meno che tu non abbia una risposta soddisfacente senza uno). Amo i punti, ma faccio anche queste cose per aiutare e come una sfida - invece, per esempio, Sudoku o un cruciverba. –

+0

mi sembra bello! – Patricia

2

Siamo spiacenti, JavaScript non ha caratteristiche di confronto. L'unico confronto di stringhe che si ottiene è direttamente sulle unità di codice UTF-16 in un String, come restituito da charCodeAt().

Per i caratteri all'interno del piano multilingue di base, è uguale a un confronto binario, quindi se hai bisogno che JS e SQL Server siano d'accordo (ignorando comunque i piani astrali), penso che questo sia l'unico modo che hai intenzione di fare esso. (Breve di costruire un collator stringa nel JS e meticolosamente copiare le regole di confronto di SQL Server, in ogni caso. Non un sacco di divertimento.)

(Qual è il caso d'uso, perché hanno bisogno per abbinare?)

+1

grazie per gli approfondimenti; use case è piuttosto semplice - sto inviando i dati ordinati dal server sql e ho funzionalità di ordinamento sul lato client in una tabella. Quando non sono d'accordo, ho problemi con il paging, ecc. – DanP

2

@BrockAdams' answer è grande, ma ho avuto un paio di casi limite con trattini nel mezzo della stringa che non corrisponde con SQL Server, non riuscivo a capire dove stava andando male, così ho ha scritto una versione più funzionale che filtra solo i caratteri ignorati e quindi confronta gli array in base ai punti del codice latino.

Probabilmente è meno performante, ma c'è meno codice da capire e funziona sulle corrispondenze dei casi di test SQL che ho aggiunto di seguito.

Stavo usando un database SQL Server con Latin1_General_100_CI_AS, quindi era case-insensitive, ma ho mantenuto il codice qui per essere case-sensitive, È abbastanza facile passare al controllo senza distinzione tra maiuscole e minuscole, creando un wrapper funzione che applica toLowerCase alle variabili.

Non c'era una differenza nell'ordinamento tra le due regole di confronto con i casi di test che avevo.

/** 
 
* This is a modified version of sortByRoughSQL_Latin1_General_CP1_CS_AS 
 
* This has a more functional approach, it is more basic 
 
* It simply does a character filter and then sort 
 
* @link https://stackoverflow.com/a/3266430/327074 
 
* 
 
* @param {String} a 
 
* @param {String} b 
 
* @returns {Number} -1,0,1 
 
*/ 
 
function latinSqlSort(a, b) { 
 
    'use strict'; 
 
    //--- This is the master lookup table for Latin1 code-points. 
 
    // Here through the extended set \u02AF 
 
    var latinLookup = [ 
 
     -1,151,152,153,154,155,156,157,158, 2, 3, 4, 5, 6,159,160,161,162,163,164, 
 
     165,166,167,168,169,170,171,172,173,174,175,176, 0, 7, 8, 9, 10, 11, 12,210, 
 
     13, 14, 15, 41, 16,211, 17, 18, 65, 69, 71, 74, 76, 77, 80, 81, 82, 83, 19, 20, 
 
     42, 43, 44, 21, 22,214,257,266,284,308,347,352,376,387,419,427,438,459,466,486, 
 
     529,534,538,559,576,595,636,641,647,650,661, 23, 24, 25, 26, 27, 28,213,255,265, 
 
     283,307,346,350,374,385,418,426,436,458,464,485,528,533,536,558,575,594,635,640, 
 
     646,648,660, 29, 30, 31, 32,177,178,179,180,181,182,183,184,185,186,187,188,189, 
 
     190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209, 
 
      1, 33, 53, 54, 55, 56, 34, 57, 35, 58,215, 46, 59,212, 60, 36, 61, 45, 72, 75, 
 
     37, 62, 63, 64, 38, 70,487, 47, 66, 67, 68, 39,219,217,221,231,223,233,250,276, 
 
     312,310,316,318,392,390,395,397,295,472,491,489,493,503,495, 48,511,599,597,601, 
 
     603,652,590,573,218,216,220,230,222,232,249,275,311,309,315,317,391,389,394,396, 
 
     294,471,490,488,492,502,494, 49,510,598,596,600,602,651,589,655,229,228,227,226, 
 
     235,234,268,267,272,271,270,269,274,273,286,285,290,287,324,323,322,321,314,313, 
 
     326,325,320,319,358,357,362,361,356,355,364,363,378,377,380,379,405,404,403,402, 
 
     401,400,407,406,393,388,417,416,421,420,432,431,428,440,439,447,446,444,443,442, 
 
     441,450,449,468,467,474,473,470,469,477,484,483,501,500,499,498,507,506,527,526, 
 
     540,539,544,543,542,541,561,560,563,562,567,566,565,564,580,579,578,577,593,592, 
 
     611,610,609,608,607,606,613,612,617,616,615,614,643,642,654,653,656,663,662,665, 
 
     664,667,666,574,258,260,262,261,264,263,281,278,277,304,292,289,288,297,335,337, 
 
     332,348,349,369,371,382,415,409,434,433,448,451,462,476,479,509,521,520,524,523, 
 
     531,530,552,572,571,569,570,583,582,581,585,632,631,634,638,658,657,669,668,673, 
 
     677,676,678, 73, 79, 78,680,644, 50, 51, 52, 40,303,302,301,457,456,455,482,481, 
 
     480,225,224,399,398,497,496,605,604,626,625,620,619,624,623,622,621,334,241,240, 
 
     237,236,254,253,366,365,360,359,430,429,505,504,515,514,675,674,422,300,299,298, 
 
     354,353, 84, 85, 86, 87,239,238,252,251,513,512,243,242,245,244,328,327,330,329, 
 
     411,410,413,412,517,516,519,518,547,546,549,548,628,627,630,629, 88, 89, 90, 91, 
 
     92, 93, 94, 95, 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, 
 
     112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131, 
 
     132,133,134,135,136,137,138,139,140,141,142,143,246,247,248,259,279,280,293,291, 
 
     339,336,338,331,340,341,342,423,367,373,351,370,372,383,381,384,408,414,386,445, 
 
     453,452,454,461,463,460,475,478,465,508,522,525,532,550,553,554,555,545,556,557, 
 
     537,551,568,333,424,343,344,586,584,618,633,637,639,645,659,649,670,671,672,679, 
 
     681,682,683,282,686,256,345,368,375,425,435,437,535,684,685,305,296,306,591,587, 
 
     588,144,145,146,147,148,149,150 
 
    ]; 
 

 
    /** 
 
    * A bunch of characters get ignored for the primary sort weight. 
 
    * The most important ones are the hyphen and apostrophe characters. 
 
    * A bunch of control characters and a couple of odds and ends, make up 
 
    * the rest. 
 
    * 
 
    * @param {Number} 
 
    * @returns {Boolean} 
 
    * @link https://stackoverflow.com/a/3266430/327074 
 
    */ 
 
    function ignoreForPrimarySort(iCharCode) { 
 
     if (iCharCode < 9) { 
 
      return true; 
 
     } 
 

 
     if (iCharCode >= 14 && iCharCode <= 31) { 
 
      return true; 
 
     } 
 

 
     if (iCharCode >= 127 && iCharCode <= 159) { 
 
      return true; 
 
     } 
 

 
     if (iCharCode == 39 || iCharCode == 45 || iCharCode == 173) { 
 
      return true; 
 
     } 
 

 
     return false; 
 
    } 
 

 
    // normal sort 
 
    function compare(a, b) { 
 
     return a === b ? 0 : a > b ? 1 : -1; 
 
    } 
 

 
    // compare two arrays return first compare difference 
 
    function arrayCompare(a, b) { 
 
     return a.reduce(function (acc, x, i) { 
 
      return acc === 0 && i < b.length ? compare(x, b[i]) : acc; 
 
     }, 0); 
 
    } 
 

 
    /** 
 
    * convert a string to array of latin code point ordering 
 
    * @param {String} x 
 
    * @returns {Array} integer array 
 
    */ 
 
    function toLatinOrder(x) { 
 
     return x.split('') 
 
      // convert to char codes 
 
      .map(function(x){return x.charCodeAt(0);}) 
 
      // filter out ignored characters 
 
      .filter(function(x){return !ignoreForPrimarySort(x);}) 
 
      // convert to latin order 
 
      .map(function(x){return latinLookup[x];}); 
 
    } 
 

 
    // convert inputs 
 
    var charA = toLatinOrder(a), 
 
     charB = toLatinOrder(b); 
 

 
    // compare the arrays 
 
    var charsCompare = arrayCompare(charA, charB); 
 
    if (charsCompare !== 0) { 
 
     return charsCompare; 
 
    } 
 

 
    // fallback to the filtered array length 
 
    var charsLenCompare = compare(charA.length, charB.length); 
 
    if (charsLenCompare !== 0) { 
 
     return charsLenCompare; 
 
    } 
 

 
    // Final fallback to a basic length comparison 
 
    return compare(a.length, b.length); 
 
} 
 

 
var tests = [ 
 
    'Grails Found', 
 
    '--Exhibit Visitors', 
 
    '-Exhibit Visitors', 
 
    'Exhibit Visitors', 
 
    'Calls Received', 
 
    'Ëxhibit Visitors', 
 
    'Brochures distributed', 
 
    'exhibit visitors', 
 
    'bags of Garbage', 
 
    '^&$Grails Found', 
 
    '?test', 
 
    '612C-520', 
 
    '612-C-122', 
 
    '612C-122 I', 
 
    '612-C-126 L', 
 
    '612C-301 B', 
 
    '612C-304 B', 
 
    '612C-306', 
 
    '612-C-306', 
 
    '612-C-306 2', 
 
    '612-C-403 H', 
 
    '612C403 O', 
 
    '612-C-403(V)', 
 
    '612E-306A/B I', 
 
    '612E-306A/B O', 
 
    '612C-121 O', 
 
    '612C-111 B', 
 
    '- -612C-111 B' 
 
].sort(latinSqlSort).join('<br>'); 
 

 
document.write(tests);

+0

Non sono sicuro che il valore '- -612C-111 B' sia ordinato correttamente, ma nel complesso questa risposta sembra buona (non voglio rivisitare questo problema con il dovuto rigore in questo momento). –

+1

@BrockAdams Quello era in realtà uno dei casi che mi ha mandato in questa tana del coniglio. Ho controllato contro SQL Server - ecco un [SQL Fiddle] (http://sqlfiddle.com/#!18/3195a/2) del tipo. – icc97