2010-02-01 22 views
13

Tengo un simple TStringList. Hago un TStringList.Sort en él.¿Cómo puedo obtener TStringList para ordenar de manera diferente en Delphi

Luego observo que el guión bajo "_" se ordena antes de la letra mayúscula "A". Esto contrastaba con un paquete de terceros que estaba ordenando el mismo texto y ordenado _ después de A.

De acuerdo con el juego de caracteres ANSI, AZ son los caracteres 65 - 90 y _ es 95. Por lo tanto, parece que el tercero el paquete está usando esa orden y TStringList.Sort no.

Me metí en las tripas de TStringList.Sort y está clasificando usando AnsiCompareStr (Case Sensitive) o AnsiCompareText (no distingue entre mayúsculas y minúsculas). Lo intenté en ambos sentidos, estableciendo el valor de CaseSensitive de StringList en verdadero y luego falso. Pero en ambos casos, el "_" se clasifica primero.

No me puedo imaginar que esto sea un error en TStringList. Entonces debe haber algo más aquí que no estoy viendo. ¿Qué podría ser eso?

Lo que realmente necesito saber es cómo puedo ordenar mi TStringList para que esté en el mismo orden que el otro paquete.

Como referencia, estoy usando Delphi 2009 y estoy usando cadenas Unicode en mi programa.


Así que la respuesta final aquí es para anular la norma ANSI compara con lo que quieras (por ejemplo, no ANSI compara) de la siguiente manera:

type 
    TMyStringList = class(TStringList) 
    protected 
    function CompareStrings(const S1, S2: string): Integer; override; 
    end; 

function TMyStringList.CompareStrings(const S1, S2: string): Integer; 
begin 
    if CaseSensitive then 
    Result := CompareStr(S1, S2) 
    else 
    Result := CompareText(S1, S2); 
end; 
+3

Windows también ordena ''' antes de' A', por lo que TStringlist es al menos consistente con el sistema operativo. –

+2

obteniendo resultados que no esperas, no significa que sea un error. No es un error, está diseñado de esta manera para respaldar correctamente la elección del orden de clasificación del usuario (u OS en nombre del usuario). –

+0

Está escribiendo esta pregunta con la presunción de que * es * una forma correcta de ordenar caracteres no alfabéticos. ¿Dónde aparecen las palabras de subrayado en tu diccionario? –

Respuesta

35

Definir " correctamente".
i18n clasificación depende totalmente de su configuración regional.
Por lo tanto, estoy totalmente de acuerdo con PA que esto no es un error: el comportamiento predeterminado Sort funciona como está diseñado para permitir que i18n funcione correctamente.

Como Gerry menciona, TStringList.Sort utiliza AnsiCompareStr y AnsiCompareText (Voy a explicar en pocas líneas cómo se hace eso).

Pero: TStringList es flexible, contiene Ordenar, CustomSort y CompareStrings, todos los cuales son virtuales (por lo que se los puede sustituir en una clase descendiente)
Por otra parte, cuando se llama CustomSort, puede conectar su propia función Comparar.

en el de esta respuesta es un Compare función que hace lo que quiere:

  • mayúsculas y minúsculas
  • No utilizar cualquier localidad
  • Basta con comparar el valor ordinal de los caracteres de las cadenas

CustomSort se define como este:

procedure TStringList.CustomSort(Compare: TStringListSortCompare); 
begin 
    if not Sorted and (FCount > 1) then 
    begin 
    Changing; 
    QuickSort(0, FCount - 1, Compare); 
    Changed; 
    end; 
end; 

Por defecto, el Ordenar método tiene una aplicación muy simple, pasando un defecto Compare función llamada StringListCompareStrings:

procedure TStringList.Sort; 
begin 
    CustomSort(StringListCompareStrings); 
end; 

Por lo tanto, si define su propio TStringListSortCompare compatible Comparar el método, luego puede definir su propia clasificación.
TStringListSortCompare se define como una función global que toma el TStringList y dos índices de referencia los elementos que desee comparar:

type 
    TStringListSortCompare = function(List: TStringList; Index1, Index2: Integer): Integer; 

Puede utilizar los StringListCompareStrings como una guía para la implementación de su propia:

function StringListCompareStrings(List: TStringList; Index1, Index2: Integer): Integer; 
begin 
    Result := List.CompareStrings(List.FList^[Index1].FString, 
           List.FList^[Index2].FString); 
end; 

Así, por defecto TStringList.Sort remite a TList.CompareStrings:

function TStringList.CompareStrings(const S1, S2: string): Integer; 
begin 
    if CaseSensitive then 
    Result := AnsiCompareStr(S1, S2) 
    else 
    Result := AnsiCompareText(S1, S2); 
end; 

Qué a continuación, utilizar la mentira bajo función de la API de Windows con el usuario CompareString regional predeterminada LOCALE_USER_DEFAULT:

function AnsiCompareStr(const S1, S2: string): Integer; 
begin 
    Result := CompareString(LOCALE_USER_DEFAULT, 0, PChar(S1), Length(S1), 
    PChar(S2), Length(S2)) - 2; 
end; 

function AnsiCompareText(const S1, S2: string): Integer; 
begin 
    Result := CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, PChar(S1), 
    Length(S1), PChar(S2), Length(S2)) - 2; 
end; 

Finalmente el Compare función que necesita. De nuevo las limitaciones:

  • mayúsculas y minúsculas
  • No utilizar cualquier localidad
  • Basta con comparar el valor ordinal de los caracteres de las cadenas

Este es el código:

function StringListCompareStringsByOrdinalCharacterValue(List: TStringList; Index1, Index2: Integer): Integer; 
var 
    First: string; 
    Second: string; 
begin 
    First := List[Index1]; 
    Second := List[Index2]; 
    if List.CaseSensitive then 
    Result := CompareStr(First, Second) 
    else 
    Result := CompareText(First, Second); 
end; 

Delphi no está cerrado, todo lo contrario: a menudo es una arquitectura realmente flexible.
A menudo es solo un poco de excavación para ver dónde puede enganchar esa flexibilidad.

--jeroen

+0

¡Muy bonito! Sabía sobre esto, pero no había visto todo esto descrito en un lugar antes. –

+0

Es por eso que lo hice un wiki de la comunidad :-) –

5

AnsiCompareStr/AnsiCompareText tomar más de un número de caracteres en cuenta. Se llevan a los usuarios locale en cuenta, por lo que "e" clasificarán junto con "E", "E", etc.

Para que sea ordenarla en forma ASCII, utilice una costumbre comparar la función as described here

0

AnsiCompareStr (CompareString con LOCALE_USER_DEFAULT) tiene fallos, porque se pone caracteres con punteado como igual:

e1 E1 e2 E2

orden correcta es (por ejemplo para checo):

e1 e2 é1 é2

¿Alguien sabe cómo evitar este error en el pedido?


11.2.2010: Debo disculparme comportamiento descrito es completamente según reglas lingüísticas. Aunque creo que es tonto y "malo", no es un error en la función API.

Explorer en Windows XP usa el llamado orden de nombre de usuario intuitivo que ofrece mejores resultados pero no se puede usar de forma programática.

Cuestiones relacionadas