Лучший способ изменить размер TStringList?

Я часто обнаруживаю, что мне нужно «изменить размер» a a TStringList, чтобы он содержал ровно N элементов, либо добавляя в список дополнительные пустые строки, либо удаляя ненужные.

В контейнере C++ STL я мог бы использовать метод resize, но поскольку его не существует, я обычно делаю что-то подобное (внимание: псевдокод!).

list.beginUpdate;

while list.Count < requiredSize do
begin
   list.add('');
end;

while list.Count > requiredSize do
begin
   list.delete(list.count-1);
end;

list.endUpdate;

Есть ли более простой способ сделать это, который я упустил из виду?


person Roddy    schedule 13.01.2014    source источник
comment
почему бы не использовать TList<String> вместо этого?   -  person Arioch 'The    schedule 13.01.2014
comment
Я не могу вспомнить, когда мне приходилось изменять размер списка строк, чтобы он содержал ровно N элементов, поэтому мне интересно узнать о ваших вариантах использования? Возможно, другие структуры данных лучше подходят для того, что вам нужно.   -  person Lieven Keersmaekers    schedule 13.01.2014
comment
Зачем нужно изменять размер списка? Это важный вопрос, потому что он оказывает значительное влияние на лучший ответ. Например. Изменение размера, потому что другой код ожидает ровно N элементов, сильно отличается от изменения размера, чтобы избежать избыточных накладных расходов памяти для очень больших списков, когда вы точно знаете, сколько строк вы хотите сохранить.   -  person Disillusioned    schedule 13.01.2014
comment
@CraigYoung Изменение размера Потому что у меня есть визуальный компонент (например, TValueListEditor), который должен отображать X элементов.   -  person Roddy    schedule 13.01.2014


Ответы (4)


Судя по реализации TStringList.Assign, лучшего способа сделать это не существует. В основном они вызывают Clear и добавляют строки одну за другой.

Конечно, вы должны поместить свой код в служебный метод:

procedure ResizeStringList(List : TStrings; ANewSize: Integer);
begin
...
end;

Или вы можете использовать помощник класса, чтобы ваш метод выглядел как часть самого TStringList.

person jpfollenius    schedule 13.01.2014

Метод в вашем вопросе - лучшее, что вы можете сделать. Вы можете сделать его чище, если используете помощник класса. Например:

type
  TStringsHelper = class helper for TStrings
    procedure SetCount(Value: Integer);
  end;

procedure TStringsHelper.SetCount(Value: Integer);
begin
  BeginUpdate;
  try
    while Count<Value do
      Add('');
    while Count>Value do
      Delete(Count-1);
  finally
    EndUpdate;
  end;
end;

И тогда вы можете написать:

List.SetCount(requiredSize);
person David Heffernan    schedule 13.01.2014
comment
Как насчет свойства TStringList.Capacity? - person Ville Krumlinde; 13.01.2014
comment
@VilleKrumlinde Это контролирует емкость, а не количество. - person David Heffernan; 13.01.2014
comment
@DavidHeffernan да, но добавление if Value > Count then Capacity = Value перед циклом Add может повысить производительность. - person Roddy; 13.01.2014
comment
@ Родди Сомнительно, что это будет иметь какое-либо заметное значение. Код списка строк уже увеличивает емкость большими кусками. Посмотрите на TStringList.Grow. - person David Heffernan; 13.01.2014

Свойство Capacity почти идеально, потому что оно выделяет правильное количество записей во внутреннем массиве. Однако у него есть неприятные недостатки:

  • Вновь выделенная память не инициализируется.
  • Количество элементов Strings.Count не обновляется.

Поскольку архитектура компонентов Delphi относится к базовому типу TStrings, вы можете предоставить свой конкретный подкласс, который может поддерживать более эффективную функциональность изменения размера. Например. рассмотрим следующую реализацию TList.SetCount.

procedure TList.SetCount(NewCount: Integer);
var
  I: Integer;
begin
  if (NewCount < 0) or (NewCount > MaxListSize) then
    Error(@SListCountError, NewCount);
  if NewCount > FCapacity then
    SetCapacity(NewCount);
  if NewCount > FCount then
    FillChar(FList^[FCount], (NewCount - FCount) * SizeOf(Pointer), 0)
  else
    for I := FCount - 1 downto NewCount do
      Delete(I);
  FCount := NewCount;
end;

После обновления емкости, если есть вновь выделенная память, она инициализируется с помощью FillChar. Это намного эффективнее, чем добавлять/удалять элементы по одному.

Таким образом, вы можете либо предоставить свою собственную независимую конкретную реализацию подкласса TStrings, либо просто сделать копию TStringList Delphi, которая включает соответствующий метод SetCount.

Тем не менее, я считаю маловероятным, что этот раздел кода будет страдать от каких-либо проблем с производительностью, поэтому будет достаточно вашего собственного решения, заключенного в соответствующие служебные методы. Ответ Дэвида тоже хорош, хотя лично я не считаю функцию "помощник класса" такой уж полезной. «Старый способ» реализации хелперов классов гораздо более универсален.

person Disillusioned    schedule 13.01.2014
comment
Я бы сказал, что «досадным недостатком» является то, что Count недоступно для записи. Несмотря на то, что они связаны, Емкость представляет собой совершенно другое понятие, чем Счет. - person Roddy; 13.01.2014
comment
@ Родди Правда. В конце концов, тесно связанные TList позволили Count быть записываемыми. На самом деле нет никаких причин делать один доступным для записи, а другой нет. - person Disillusioned; 13.01.2014
comment
@DavidHeffernan Ты ошибаешься. (Возможно, вы просто неправильно понимаете, о чем я говорю.) Единственное, что делает Grow, — это эффективно масштабирует выделение памяти для внутреннего списка по мере увеличения количества строк. Однако он ничего не делает для очистки вновь выделенной памяти, поэтому пустые строки необходимо добавлять в цикле. Каждая новая строка также назначает связанную ссылку на объект (со значением nil). Это очень неэффективно по сравнению с FillChar по вновь выделенной памяти. - person Disillusioned; 14.01.2014
comment
Grow реализуется вызовом SetCapacity. Это реализуется вызовом SetLength. Это, в свою очередь, инициализирует новую память одним вызовом FillChar. - person David Heffernan; 21.01.2014

person    schedule
comment
Согласно моему ответу (stackoverflow.com/a/21091387/224704) есть две проблемы с настройкой емкости. - person Disillusioned; 13.01.2014