Pregunta

Digamos que tengo una serie de registros que quiero ordenar según uno de los campos del registro.¿Cuál es la mejor manera de lograr esto?

TExample = record
  SortOrder : integer;
  SomethingElse : string;
end;

var SomeVar : array of TExample;
¿Fue útil?

Solución

Puede agregar punteros a los elementos de la matriz a un TList, luego llame TList.Sort con una función de comparación y, finalmente, cree una nueva matriz y copie los valores de TList en el orden deseado.

Sin embargo, si está utilizando la próxima versión, D2009, hay una nueva biblioteca de colecciones que puede ordenar matrices.Se necesita una opción IComparer<TExample> Implementación de órdenes de clasificación personalizadas.Aquí está en acción para su caso específico:

TArray.Sort<TExample>(SomeVar , TDelegatedComparer<TExample>.Construct(
  function(const Left, Right: TExample): Integer
  begin
    Result := TComparer<Integer>.Default.Compare(Left.SortOrder, Right.SortOrder);
  end));

Otros consejos

(Sé que esto es un año después, pero sigue siendo material útil).

La sugerencia de Skamradt de rellenar valores enteros supone que vas a ordenar usando una comparación de cadenas.Esto sería lento.Llamar a format() para cada inserción, más lento aún.En su lugar, desea hacer una comparación de números enteros.

Comienzas con un tipo de registro:

TExample = record
  SortOrder : integer;
  SomethingElse : string;
end;

No indicó cómo se almacenaron los registros ni cómo deseaba acceder a ellos una vez ordenados.Entonces supongamos que los coloca en una matriz dinámica:

var MyDA  Array of TExample; 
...
  SetLength(MyDA,NewSize);           //allocate memory for the dynamic array
  for i:=0 to NewSize-1 do begin        //fill the array with records
    MyDA[i].SortOrder := SomeInteger;
    MyDA[i].SomethingElse := SomeString;
  end;

Ahora desea ordenar esta matriz por el valor entero SortOrder.Si lo que desea es una TStringList (para que pueda usar el método ts.Find), entonces debe agregar cada cadena a la lista y agregar SortOrder como puntero.Luego ordene según el puntero:

var  tsExamples: TStringList;         //declare it somewhere (global or local)
...
  tsExamples := tStringList.create;   //allocate it somewhere (and free it later!)
...
  tsExamples.Clear;                   //now let's use it
  tsExamples.sorted := False;         //don't want to sort after every add
  tsExamples.Capacity := High(MyDA)+1 //don't want to increase size with every add
                                      //an empty dynamic array has High() = -1
  for i:=0 to High(MyDA) do begin
    tsExamples.AddObject(MyDA[i].SomethingElse,TObject(MyDA[i].SortOrder));
  end;

Tenga en cuenta el truco de convertir Integer SortOrder en un puntero TObject, que se almacena en la propiedad TStringList.Object.(Esto depende del hecho de que Integer y Pointer tengan el mismo tamaño). En algún lugar debemos definir una función para comparar los punteros TObject:

function CompareObjects(ts:tStringList; Item1,Item2: integer): Integer;
var i,j: integer;
begin
  Result := integer(ts.Objects[i]) - integer(ts.Objects[j];
end;

Ahora, podemos ordenar tsList en .Object llamando a .CustomSort en lugar de .Sort (lo que ordenaría según el valor de la cadena).

tsExample.CustomSort(@CompareObjects);     //Sort the list

TStringList ahora está ordenado, por lo que puede iterarlo de 0 a .Count-1 y leer las cadenas en orden.

Pero supongamos que no desea una TStringList, solo una matriz ordenada.O los registros contienen más datos que solo una cadena en este ejemplo, y su orden de clasificación es más complejo.Puede omitir el paso de agregar cada cadena y simplemente agregar el índice de la matriz como Elementos en una TList.Haga todo lo anterior de la misma manera, excepto usar TList en lugar de TStringList:

var Mlist: TList;                 //a list of Pointers
...
  for i:=0 to High(MyDA) do
    Mlist.add(Pointer(i));        //cast the array index as a Pointer
  Mlist.Sort(@CompareRecords);    //using the compare function below

function CompareRecords(Item1, Item2: Integer): Integer;
var i,j: integer;
begin
  i := integer(item1);            //recover the index into MyDA
  j := integer(item2);            // and use it to access any field
  Result := SomeFunctionOf(MyDA[i].SomeField) - SomeFunctionOf(MyDA[j].SomeField);
end;

Ahora que Mlist está ordenado, utilícelo como una tabla de búsqueda para acceder a la matriz en orden:

  for i:=0 to Mlist.Count-1 do begin
    Something := MyDA[integer(Mlist[i])].SomeField;
  end;

A medida que itero sobre TList, obtenemos los índices de la matriz en orden.Sólo necesitamos volver a convertirlos en números enteros, ya que TList cree que son punteros.

Me gusta hacerlo de esta manera, pero también puedes poner punteros reales a elementos de la matriz en TList agregando la dirección del elemento de la matriz en lugar de su índice.Luego, para usarlos, debería convertirlos como punteros a registros TExample.Esto es lo que Barry Kelly y CoolMagic dijeron que hicieran en sus respuestas.

Si necesita ordenar por cadena, utilice ordenado TStringList y Agregar registro por TString.AddObject(string, Pointer(int_val)).

Pero si es necesario ordenar por campo entero y cadena, utilice TObjectList y después de agregar todos los registros llame TObjectList.Sort con funciones ordenadas necesarias como parámetro.

Todo esto depende de la cantidad de registros que esté ordenando.Si solo está clasificando menos de unos pocos cientos, entonces los otros métodos de clasificación funcionan bien; si va a clasificar más, entonces eche un vistazo al viejo y confiable Turbo Power. Herramientas del sistema proyecto.Hay un algoritmo de clasificación muy bueno incluido en la fuente.Uno que hace un muy buen trabajo clasificando millones de registros de manera eficiente.

Si va a utilizar el método tStringList para ordenar una lista de registros, asegúrese de que su número entero esté relleno hacia la derecha antes de insertarlo en la lista.Puedes usar el formato('%.10d',[rec.sortorder]) para alinear a la derecha con 10 dígitos, por ejemplo.

El algoritmo de clasificación rápida se utiliza a menudo cuando se requiere una clasificación rápida.Delphi lo está (o estaba) usando para List.Sort, por ejemplo.La Lista Delphi se puede utilizar para ordenar cualquier cosa, pero es un contenedor pesado, que se supone que debe verse como una serie de punteros en estructuras.Es pesado incluso si usamos trucos como Guy Gordon en este hilo (Poner índice o cualquier cosa en lugar de punteros, o poner directamente valores si son menores de 32 bits):necesitamos construir una lista y así sucesivamente...

En consecuencia, una alternativa para ordenar fácil y rápidamente una matriz de estructura podría ser utilizar función de tiempo de ejecución qsort C de msvcrt.dll.

Aquí hay una declaración que podría ser buena (Advertencia:código portátil sólo en Windows).

type TComparatorFunction = function(lpItem1: Pointer; lpItem2: Pointer): Integer; cdecl;
procedure qsort(base: Pointer; num: Cardinal; size: Cardinal; lpComparatorFunction: TComparatorFunction) cdecl; external 'msvcrt.dll';

Ejemplo completo aquí.

Tenga en cuenta que ordenar directamente la matriz de registros puede resultar lento si los registros son grandes.En ese caso, ordenar una matriz de punteros a los registros puede ser más rápido (de alguna manera, como el enfoque de Lista).

Con una matriz, usaría cualquiera de los dos quicksort o posiblemente heapsort, y simplemente cambie la comparación para usar TExample.SortOrder, la parte de intercambio seguirá actuando sobre la matriz y los punteros de intercambio.Si la matriz es muy grande, es posible que desee una estructura de lista vinculada si hay muchas inserciones y eliminaciones.

Rutinas basadas en C, hay varias aquí.http://www.yendor.com/programming/sort/

Otro sitio, pero tiene fuente pascal.http://www.dcc.uchile.cl/~rbaeza/handbook/sort_a.html

Utilice uno de los algoritmos de clasificación propuestos por Wikipedia.La función Intercambiar debería intercambiar elementos de la matriz utilizando una variable temporal del mismo tipo que los elementos de la matriz.Utilice una clasificación estable si desea que las entradas con el mismo valor entero de SortOrder permanezcan en el orden en que estaban en primer lugar.

TStringList tener un método de clasificación eficiente.
Si desea ordenar utilice un TStringList objeto con Sorted propiedad a Verdadero.

NOTA:Para mayor velocidad, agregue objetos en un orden no ordenado TStringList y al final cambie la propiedad a True.
NOTA:Para ordenar por campo entero, conviértalo a cadena.
NOTA:Si hay valores duplicados, este método no es válido.

Saludos.

Si tienes Delphi XE2 o más reciente, puedes probar:

var 
  someVar: array of TExample;
  list: TList<TExample>;
  sortedVar: array of TExample;
begin
  list := TList<TExample>.Create(someVar);
  try
    list.Sort;
    sortedVar := list.ToArray;
  finally
    list.Free;
  end;
end;

Creé un ejemplo muy simple que funciona correctamente si el campo de clasificación es una cadena.

Type
  THuman = Class
  Public
    Name: String;
    Age: Byte;
    Constructor Create(Name: String; Age: Integer);
  End;

Constructor THuman.Create(Name: String; Age: Integer);
Begin
  Self.Name:= Name;
  Self.Age:= Age;
End;

Procedure Test();
Var
 Human: THuman;
 Humans: Array Of THuman;
 List: TStringList;
Begin

 SetLength(Humans, 3);
 Humans[0]:= THuman.Create('David', 41);
 Humans[1]:= THuman.Create('Brian', 50);
 Humans[2]:= THuman.Create('Alex', 20);

 List:= TStringList.Create;
 List.AddObject(Humans[0].Name, TObject(Humans[0]));
 List.AddObject(Humans[1].Name, TObject(Humans[1]));
 List.AddObject(Humans[2].Name, TObject(Humans[2]));
 List.Sort;

 Human:= THuman(List.Objects[0]);
 Showmessage('The first person on the list is the human ' + Human.name + '!');

 List.Free;
End;
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top