Решение:
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.
Интересно :)
По-моему, нынешний 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 - Рома конечно же прав.
Отправить комментарий