понедельник, 4 августа 2014 г.

Простой текстовый итератор. Пример использования record в Delphi

Задача: осуществить перебор строковых значений в исходной строке, разделённых символом-разделителем, с учётом символа-кавычки.
Решение:
unit dnStringIterator;

interface

type
  TStringValuesIterator = record
  type
    TCallBack = reference to procedure (const AValue: string);
    TOptions = set of (ioTrimValues, ioDequoteValues);
  private
    FSourceString: string;
    FDelimiter, FQuoteChar: Char;
    FCheckQuotes: Boolean;
    FOptions: TOptions;
    FOffset, FLength: Integer;
    FCurrent: string;
  public
    constructor Init(const ASourceString: string; ADelimiter: Char; AQuoteChar: Char = #0; AOptions: TOptions = []);
    function GetEnumerator: TStringValuesIterator;
    function MoveNext: Boolean;
    procedure Run(ACallBack: TCallBack); inline;
    property Current: string read FCurrent;
  end;

implementation

uses
  SysUtils;

{ TStringValuesIterator }

constructor TStringValuesIterator.Init(const ASourceString: string; ADelimiter, AQuoteChar: Char; AOptions: TOptions);
begin
  FSourceString := ASourceString;
  FDelimiter := ADelimiter;
  FQuoteChar := AQuoteChar;
  FOffset := 1;
  FLength := Length(FSourceString);
  FOptions := AOptions;
  FCurrent := '';

  FCheckQuotes := FQuoteChar <> #0;

  // нельзя, чтобы символ разделитель совпадал с символом-кавычкой:
  Assert(not FCheckQuotes or (FDelimiter <> FQuoteChar));
  // нельзя использовать ioDeqouteValues, если не указан QuoteChar
  Assert(FCheckQuotes or not (ioDequoteValues in FOptions));
end;

function TStringValuesIterator.GetEnumerator: TStringValuesIterator;
begin
  Result := Self;
end;

function TStringValuesIterator.MoveNext: Boolean;
var
  IsInQuote: Boolean;
  CurPos: Integer;
  Ch: Char;
begin
  Result := (FLength > 0) and (FOffset <= (FLength + 1));
  if not Result then
    Exit;

  IsInQuote := False;
  for CurPos := FOffset to FLength do
  begin
    Ch := FSourceString[CurPos];
    if FCheckQuotes and (Ch = FQuoteChar) then
      IsInQuote := not IsInQuote
    else if not IsInQuote and (Ch = FDelimiter) then
      Break;
  end;
  FCurrent := Copy(FSourceString, FOffset, CurPos - FOffset);
  FOffset := CurPos + 1;
  if ioTrimValues in FOptions then
    FCurrent := Trim(FCurrent);
  if ioDequoteValues in FOptions then
    FCurrent := AnsiDequotedStr(FCurrent, FQuoteChar);
end;

procedure TStringValuesIterator.Run(ACallBack: TCallBack);
begin
  while MoveNext do
    ACallBack(Current);
end;

end.

Примеры использования

С явным объявлением дополнительной переменной:
var
  svi: TStringValuesIterator;
begin
  svi.Init(TestString, ',');
  while svi.MoveNext do
    Memo1.Lines.Add(svi.Current);
end;
Без явного объявления дополнительной переменной, используя with:
begin
  with TStringValuesIterator.Init(TestString, ',') do
    while MoveNext do
      Memo1.Lines.Add(Current);
end;
Используя анонимную процедуру:
begin
  TStringValuesIterator.Init(TestString, ',').Run(
    procedure (const AValue: string)
    begin
      Memo1.Lines.Add(AValue);
    end
  );
end;
Используя for-in синтаксис:
var
  Tmp: string;
begin
  for Tmp in TStringValuesIterator.Init(TestString, ',') do
    Memo1.Lines.Add(Tmp);
end;

7 коммент.:

Александр Люлин комментирует...

Не люблю копирования строк. Предпочитаю - перемещать указатель.

Николай Зверев комментирует...

А я наоборот, в последнее время ухожу от использования указателя с циклом вида while P^ <> #0 do inc(P) в пользу итерации циклом for.

Roman Yankovsky комментирует...

Интересно :)
По-моему, нынешний record - это самый недооцененный тип. Можно интересные вещи делать, но пока непривычно.

Я немного поигрался. Сделал вот такой итератор, по-моему, он чуть более органичен: http://pastebin.com/T6ms332V
(выложил на пастебин, ибо тут в комментариях код превратиться в кашу скорее всего)

Николай Зверев комментирует...

О да, про for in я совсем забыл, так и не ввёл его в свою практику. Спасибо Рома! Сейчас "докручу" исходник.

Александр Люлин комментирует...

"А я наоборот, в последнее время ухожу от использования указателя с циклом вида while P^ <> #0 do inc(P) в пользу итерации циклом for."
-- так одно другому не мешает :-)

Александр Люлин комментирует...

Из моих древностей кстати - http://18delphi.blogspot.ru/search/label/%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%20%D0%BC%D0%B0%D0%BB%D1%8F%D1%80%D0%B0

ну так.. на всякий случай

Александр Люлин комментирует...

Ну и вот - http://18delphi.blogspot.ru/2013/11/blog-post_956.html

там по-моему - полезная ссылка

Так что про for in - Рома конечно же прав.

Отправить комментарий

.

.