Лучший способ сортировки массива

29

Скажем, у меня есть массив записей, которые я хочу сортировать, основываясь на одном из полей в записи. Какой лучший способ достичь этого?

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

var SomeVar : array of TExample;
  • 0
    Просто прошел это упражнение и нашел лучший способ написать свой собственный код. Я не думаю, что какой-либо из ответов следует рекомендовать как лучший .
  • 0
    Дело принято. Может быть, вы могли бы добавить ответ с вашим решением проблемы?
Показать ещё 2 комментария
Теги:
arrays
sorting

10 ответов

30
Лучший ответ

Вы можете добавить указатели к элементам массива в TList, затем вызвать TList.Sort с помощью функции сравнения и, наконец, создать новый массив и скопировать значения из TList в желаемом порядке.

Однако, если вы используете следующую версию D2009, есть новая библиотека коллекций, которая может сортировать массивы. Для пользовательских заказов сортировки требуется дополнительная реализация IComparer<TExample>. Здесь это действие для вашего конкретного случая:

TArray.Sort<TExample>(SomeVar , TDelegatedComparer<TExample>.Construct(
  function(const Left, Right: TExample): Integer
  begin
    Result := TComparer<Integer>.Default.Compare(Left.SortOrder, Right.SortOrder);
  end));
  • 1
    Обратите внимание, что в D2009 есть несколько проблем в компиляторе с Generics, поэтому использование библиотеки коллекций никогда не является хорошим вариантом (из-за ложных внутренних ошибок компилятора и ошибок codegen). В Delphi XE эти проблемы были решены по большей части, хотя использование универсальных версий повлечет за собой снижение производительности (и вы можете потерять некоторую ясность / возможность отладки кода).
  • 1
    +1 Очень хороший ответ. Благодарю.
Показать ещё 4 комментария
12

(Я знаю, что это год спустя, но все же полезный материал.)

Предложение Skamradt для целочисленных значений pad предполагает, что вы собираетесь сортировать, используя сравнение строк. Это было бы медленным. Формат вызова() для каждой вставки медленнее. Вместо этого вы хотите выполнить целочисленное сравнение.

Вы начинаете с типа записи:

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

Вы не указали, как были сохранены записи, или как вы хотели получить к ним доступ после сортировки. Поэтому предположим, что вы поместили их в динамический массив:

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;

Теперь вы хотите отсортировать этот массив по целочисленному значению SortOrder. Если вы хотите получить TStringList (так что вы можете использовать метод ts.Find), вы должны добавить каждую строку в список и добавить SortOrder в качестве указателя. Затем выполните сортировку по указателю:

var  tsExamples: TStringList;         //declare it somewhere (global or local)
...
  tsExamples := tStringList.create;   //allocate it somewhere (and free it later!)
...
  tsExamples.Clear;                   //now let 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;

Обратите внимание на трюк приведения Integer SortOrder в указатель TObject, который хранится в свойстве TStringList.Object. (Это зависит от того, что Integer и Pointer имеют одинаковый размер.) Где-то мы должны определить функцию для сравнения указателей TObject:

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

Теперь мы можем отсортировать tsList в .Object, вызывая .CustomSort вместо .Sort(который будет сортировать по строковому значению.)

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

Теперь TStringList сортируется, поэтому вы можете перебирать его с 0 на .Count-1 и читать строки в отсортированном порядке.

Но предположим, что вам не нужен TStringList, просто массив в отсортированном порядке. Или записи содержат больше данных, чем только одна строка в этом примере, и порядок сортировки является более сложным. Вы можете пропустить шаг добавления каждой строки и просто добавить индекс массива как элементы в TList. Делайте все выше, но используйте TList вместо 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;

Теперь, когда Mlist отсортирован, используйте его в качестве таблицы поиска для доступа к массиву в отсортированном порядке:

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

Поскольку я итерации по TList, мы возвращаем индексы массивов в отсортированном порядке. Нам просто нужно отбросить их на целые числа, так как TList считает, что они указатели.

Мне нравится делать это таким образом, но вы могли бы также поместить реальные указатели на элементы массива в TList, добавив вместо него индекс Address элемента массива. Затем, чтобы использовать их, вы должны использовать их в качестве указателей на записи TExample. Это то, о чем говорили Барри Келли и CoolMagic в своих ответах.

  • 1
    похоже, это всего лишь один день спустя :)
4

Если ваша сортировка сортируется по строке, используйте отсортированные TStringList и добавьте запись TString.AddObject(string, Pointer(int_val)).

Но если нужно отсортировать по целочисленному полю и строке - используйте TObjectList и после добавления всех записей вызовите TObjectList.Sort с необходимыми отсортированными функциями в качестве параметра.

2

Алгоритм быстрой сортировки часто используется, когда требуется быстрая сортировка. Например, Delphi (или был) использует его для List.Sort, например. Список Delphi можно использовать для сортировки, но это супертяжелый контейнер, который должен быть похож на массив указателей на структуры. Это тяжеловес, даже если мы используем трюки, такие как Гай Гордон в этой теме (помещая индекс или что-нибудь вместо указателей или помещая непосредственно значения, если они меньше 32 бит): нам нужно построить список и так далее...

Следовательно, альтернативой легкому и быстрому сортировке массива структуры может быть использование функции qsort C runtime из файла msvcrt.dll.

Вот объявление, которое может быть хорошим (предупреждение: код переносится только на окна).

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

Полный пример здесь.

Обратите внимание, что сортировка массива записей может быть медленной, если записи большие. В этом случае сортировка массива указателей на записи может быть быстрее (как-то вроде подхода к списку).

2

Все зависит от количества сортируемых записей. Если вы сортируете менее нескольких сотен, то другие методы сортировки работают нормально, если вы собираетесь сортировать больше, а затем хорошо посмотрите на старый надежный Turbo Power SysTools. В источнике есть очень хороший алгоритм сортировки. Тот, который делает очень хорошую работу, сортирует миллионы записей эффективным образом.

Если вы собираетесь использовать метод сортировки списка записей tStringList, убедитесь, что ваше целое заполнено справа, прежде чем вставлять его в список. Вы можете использовать формат ('%. 10d', [rec.sortorder]) для выравнивания по правому краю до 10 цифр, например.

2

С массивом я бы использовал либо quicksort, либо, возможно, heapsort, и просто изменил сравнение, чтобы использовать TExample.SortOrder, часть подкачки все равно будет действовать только на массив и указатели подкачки. Если массив очень большой, вам может понадобиться структура связанного списка, если есть много вставки и удаления.

Процедуры на основе C, здесь несколько http://www.yendor.com/programming/sort/

Другой сайт, но имеет источник pascal http://www.dcc.uchile.cl/~rbaeza/handbook/sort_a.html

1

TStringList имеют эффективный метод сортировки.
Если вы хотите Сортировка, используйте объект TStringList с свойством Sorted в значение True.

ПРИМЕЧАНИЕ. Для большей скорости добавьте объекты в не отсортированном TStringList и в конце измените свойство на True.
ПРИМЕЧАНИЕ. Для сортировки по целочисленному полю, преобразование в строку.
ПРИМЕЧАНИЕ. Если имеются повторяющиеся значения, этот метод не является допустимым.

С уважением.

1

Используйте один из алоритов сортировки, предлагаемый Wikipedia. Функция Swap должна обмениваться элементами массива с использованием временной переменной того же типа, что и элементы массива. Используйте стабильную сортировку, если вы хотите, чтобы записи с тем же значением целочисленного значения SortOrder оставались в том порядке, в котором они были в первую очередь.

0

Если у вас есть Delphi XE2 или новее, вы можете попробовать:

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;
0

Я создал очень простой пример, который работает корректно, если поле sort является строкой.

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;

Ещё вопросы

Сообщество Overcoder
Наверх
Меню