Move () для вставки / удаления элементов из динамического массива строк - PullRequest
10 голосов
/ 17 сентября 2010

Использование System.Move () для вставки / удаления элементов из массива строк не так просто, как вставка / удаление из других массивов простых типов данных.Проблема в том, что ... строка считается в Delphi.Использование Move () для типов данных с подсчетом ссылок требует более глубоких знаний о внутреннем поведении компилятора.

Может ли кто-нибудь здесь объяснить необходимые шаги для меня, или лучше с некоторыми фрагментами кода, или направить меня к хорошимссылка в интернете?

О, пожалуйста, не говорите мне использовать «ленивый, но медленный способ», то есть для цикла, я знаю это.

Ответы [ 7 ]

18 голосов
/ 17 сентября 2010

Ранее я демонстрировал, как удалять элементы из динамического массива:

В этой статье я начну со следующего кода:

type
  TXArray = array of X;

procedure DeleteX(var A: TXArray; const Index: Cardinal);
var
  ALength: Cardinal;
  i: Cardinal;
begin
  ALength := Length(A);
  Assert(ALength > 0);
  Assert(Index < ALength);
  for i := Index + 1 to ALength - 1 do
    A[i - 1] := A[i];
  SetLength(A, ALength - 1);
end;

Вы не можете ошибиться с этим кодом. Используйте любое значение для X, которое вы хотите; в вашем случае замените его на string. Если вы хотите стать более любопытным и использовать Move, то есть способ сделать это тоже.

procedure DeleteX(var A: TXArray; const Index: Cardinal);
var
  ALength: Cardinal;
  TailElements: Cardinal;
begin
  ALength := Length(A);
  Assert(ALength > 0);
  Assert(Index < ALength);
  Finalize(A[Index]);
  TailElements := ALength - Index;
  if TailElements > 0 then
    Move(A[Index + 1], A[Index], SizeOf(X) * TailElements);
  Initialize(A[ALength - 1]);
  SetLength(A, ALength - 1);
end;

Поскольку X равен string, вызов Finalize эквивалентен присвоению пустой строки этому элементу массива. Я использую Finalize в этом коде, потому что он будет работать для всех типов элементов массива, даже типов, которые включают записи, интерфейсы, строки и другие массивы.

Для вставки вы просто перемещаете вещи в противоположном направлении:

procedure InsertX(var A: TXArray; const Index: Cardinal; const Value: X);
var
  ALength: Cardinal;
  TailElements: Cardinal;
begin
  ALength := Length(A);
  Assert(Index <= ALength);
  SetLength(A, ALength + 1);
  Finalize(A[ALength]);
  TailElements := ALength - Index;
  if TailElements > 0 then begin
    Move(A[Index], A[Index + 1], SizeOf(X) * TailElements);
  Initialize(A[Index]);
  A[Index] := Value;
end;

Используйте Finalize, когда вы собираетесь сделать что-то, выходящее за пределы языка, например, использовать небезопасную процедуру Move для перезаписи переменной типа, управляемого компилятором. Используйте Initialize при повторном вводе определенной части языка. (Язык определяет, что происходит, когда массив увеличивается или уменьшается с SetLength, но не определяет, как копировать или удалять строки без использования оператора присваивания строки.)

2 голосов
/ 04 апреля 2015

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

procedure RemoveRecord(Index: integer);  
begin
 FRecords[Index]:= FRecords[High(FRecords)];  { Copy the last element over the 'deleted' element }
 SetLength(FRecords, Length(FRecords)-1);     { Cut the last element }
end;

{Я не проверял код на предмет его компиляции, но вы все равно поняли ...}

Редактирование огромного списка:
Если у вас есть ОГРОМНЫЙ список, который должен быть изменен пользователем, вы можете использовать методы, аналогичные описанным выше (нарушить порядок списка).Когда пользователь завершит редактирование (после нескольких удалений), вы даете ему кнопку «Список сортировки».Теперь он может выполнять длительную (сортировку) операцию.
Конечно, я предполагаю выше, что ваш список может быть отсортирован по определенному параметру.

2 голосов
/ 17 сентября 2010

Если бы я хотел вставить строку в середину списка строк, я бы использовал TStringList.Insert. (Он делает это быстро, используя System.Move.)

Есть ли какая-то конкретная причина, по которой вы используете массив вместо TStringList?

2 голосов
/ 17 сентября 2010

Чтобы вставить строку, просто добавьте строку (ленивый способ) в конец массива (который является массивом указателей), а затем используйте Move, чтобы изменить порядок элементов этого массива (из указатели).

1 голос
/ 17 сентября 2010

Вызовите UniqueString () для него, прежде чем связываться с ним.

http://docwiki.embarcadero.com/VCL/en/System.UniqueString

Тогда у вас есть строка с одной ссылкой.

Вероятность того, что удаление и вставка тоже есть, и я сомневаюсь, что вы будете быстрее.

0 голосов
/ 31 июля 2013

Просто хочу добавить это для всех людей, которые придут сюда в будущем.

Модифицируя код Роба, я придумал этот способ, который использует более новые конструкции типа TArray<T>.

type
  TArrayExt = class(TArray)
    class procedure Delete<T>(var A: TArray<T>; const Index: Cardinal; Count: Cardinal = 1);
  end;

implementation

class procedure TArrayExt.Delete<T>(var A: TArray<T>; const Index: Cardinal;
    Count: Cardinal = 1);
var
  ALength: Cardinal;
  i: Cardinal;
begin
  ALength := Length(A);
  Assert(ALength > 0);
  Assert(Count > 0);
  Assert(Count <= ALength - Index);
  Assert(Index < ALength);

  for i := Index + Count to ALength - 1 do
    A[i - Count] := A[i];

  SetLength(A, ALength - Count);
end;

Аналогичное можно сделать для вставки.

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

(Исправлено с учетом комментариев Роба ниже.)

0 голосов
/ 17 сентября 2010

Если вы используете System.Move для помещения элементов в массив строк, вы должны знать, что строки, которые были там до перемещения (и теперь перезаписаны), имели счетчик ссылок либо -1 для константных строк, либо > 0 для переменных строк. Постоянные строки не должны изменяться, но переменные строки должны обрабатываться соответствующим образом: вы должны вручную уменьшить их счетчик ссылок (прежде чем они будут перезаписаны!). Чтобы сделать это, вы должны попробовать что-то вроде этого:

Dec(PStrRec(IntPtr(SomeString)-12).refCnt);

Но если счетчик ссылок достиг нуля, вам также следует завершить ассоциированную память - что само Delphi делает намного лучше, если вы позволяете ему работать, это волшебство компилятора для строк. О, а также: если строки, которые вы копируете, поступают из того же массива, в который вы записываете, необходимое администрирование становится очень громоздким, очень быстро!

Так что, если каким-то образом можно избежать всей этой ручной уборки, я бы посоветовал Delphi справиться с этим самостоятельно.

...