Существует ли быстрая стандартная программа GetToken для Delphi?

Если вы используете всплывающие окна Bootstrap, вам не хватает свойства элемента, чтобы заставить работать поповер HTML. Добавьте это свойство data-html="true" к элементу span с помощью всплывающей подсказки, и, если оно все еще не отображается должным образом, запустите его на DOM ready:

$(function () {
    $('[data-toggle="tooltip"]').tooltip();
})
21
задан Community 23 May 2017 в 11:54
поделиться

7 ответов

Ваша новая функция (та, что с PChar) должна объявлять «Delim» как Char , а не как String . В вашей текущей реализации компилятор должен преобразовать PLine ^ char в строку, чтобы сравнить ее с «Delim». И это происходит в замкнутом цикле, что приводит к огромному снижению производительности.

function GetTok(const Line: string; const Delim: Char{<<==}; const TokenNum: Byte): string;
{ LK Feb 12, 2007 - This function has been optimized as best as possible }
{ LK Nov 7, 2009 - Reoptimized using PChars instead of calls to Pos and PosEx }
{ See; http://stackoverflow.com/questions/1694001/is-there-a-fast-gettoken-routine-for-delphi }
var
 I: integer;
 PLine, PStart: PChar;
begin
  PLine := PChar(Line);
  PStart := PLine;
  inc(PLine);
  for I := 1 to TokenNum do begin
    while (PLine^ <> #0) and (PLine^ <> Delim) do
      inc(PLine);
    if I = TokenNum then begin
      SetString(Result, PStart, PLine - PStart);
      break;
    end;
    if PLine^ = #0 then begin
      Result := '';
      break;
    end;
    inc(PLine);
    PStart := PLine;
  end;
end; { GetTok }
11
ответ дан 29 November 2019 в 20:51
поделиться

Это функция, которой я пользовался в своей личной библиотеке в течение достаточно долгого времени и которой я активно пользуюсь. Я считаю, что это самая актуальная версия. В прошлом у меня было несколько версий, оптимизированных по разным причинам. Этот пытается учесть строки в кавычках, но если этот код будет удален, это сделает функцию немного быстрее.

На самом деле у меня есть ряд других подпрограмм, CountSections и ParseSectionPOS являются парой примеров.

К сожалению, эта процедура основана только на ANSI / PCHAR. Хотя я не думаю, что было бы трудно перевести его в Unicode. Может быть, я уже сделал это ... Я должен проверить это.

Примечание. Эта подпрограмма равна 1 на основе индексации ParseNum.

function ParseSection(ParseLine: string; ParseNum: Integer; ParseSep: Char; QuotedStrChar:char = #0) : string;
var
   wStart, wEnd : integer;
   wIndex : integer;
   wLen : integer;
   wQuotedString : boolean;
begin
   result := '';
   wQuotedString := false;
   if not (ParseLine = '') then
   begin
      wIndex := 1;
      wStart := 1;
      wEnd := 1;
      wLen := Length(ParseLine);
      while wEnd <= wLen do
      begin
         if (QuotedStrChar <> #0) and (ParseLine[wEnd] = QuotedStrChar) then
            wQuotedString := not wQuotedString;

         if not wQuotedString and (ParseLine[wEnd] = ParseSep) then
         begin
            if wIndex=ParseNum then
               break
            else
            begin
               inc(wIndex);
               wStart := wEnd+1;
            end;
         end;
         inc(wEnd);
      end;

      result := copy(ParseLine, wStart, wEnd-wStart);
      if (length(result) > 0) and (QuotedStrChar <> #0) and (result[1] = QuotedStrChar) then
         result := AnsiDequotedStr(result, QuotedStrChar);
   end;
end; { ParseSection }
1
ответ дан 29 November 2019 в 20:51
поделиться

Имеет большое значение, каким должен быть "Делим". Если ожидается, что это будет одиночный символ, вам будет гораздо лучше переходить от одного символа к другому, в идеале с помощью PChar, и специально тестировать.

Если это длинная строка, поисковые запросы Бойера-Мура и подобные им имеют набор -up для пропуска таблиц, и лучший способ - создать таблицы один раз и повторно использовать их для каждой последующей находки. Это означает, что вам нужно состояние между вызовами, и эту функцию лучше использовать как метод объекта.

Возможно, вас заинтересует ответ, который я дал на вопрос некоторое время назад, о самом быстром способе разобрать строку в Delphi. (Но я вижу, что это вы задали вопрос! Тем не менее, решая вашу проблему, я бы выслушал, как я описал синтаксический анализ, , а не с использованием PosEx, как вы, в зависимости от того, как обычно выглядит Delim.)


ОБНОВЛЕНИЕ : Хорошо, я потратил около 40 минут, глядя на это. Если вы знаете, что разделителем будет символ, вам всегда будет лучше со второй версией (то есть сканированием PChar), но вы должны передать Delim как символ. Во время написания вы конвертируете выражение PLine ^ типа Char в строку для сравнения с Delim. Это будет очень медленно; даже индексация в строке с Delim [1] также будет несколько медленной.

Однако, в зависимости от того, насколько велики ваши строки и сколько частей с разделителями вы хотите вытащить, вы можете Лучше использовать возобновляемый подход, чем пропускать ненужные разделенные части внутри процедуры токенизации. Если вы вызываете GetTok с последовательно увеличивающимися индексами, как вы это делаете сейчас в своем мини-тесте, вы получите производительность O (n * n), где n - количество разделенных разделов. Это можно превратить в O (n), если вы сохраните состояние сканирования и восстановите его для следующей итерации, или упакуйте все извлеченные элементы в массив.

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

// Do all tokenization up front.
function GetTok4(const Line: string; const Delim: Char): TArray<string>;
var
  cp, start: PChar;
  count: Integer;
begin
  // Count sections
  count := 1;
  cp := PChar(Line);
  start := cp;
  while True do
  begin
    if cp^ <> #0 then
    begin
      if cp^ <> Delim then
        Inc(cp)
      else
      begin
        Inc(cp);
        Inc(count);
      end;
    end
    else
    begin
      Inc(count);
      Break;
    end;
  end;

  SetLength(Result, count);
  cp := start;
  count := 0;

  while True do
  begin
    if cp^ <> #0 then
    begin
      if cp^ <> Delim then
        Inc(cp)
      else
      begin
        SetString(Result[count], start, cp - start);
        Inc(cp);
        Inc(count);
      end;
    end
    else
    begin
      SetString(Result[count], start, cp - start);
      Break;
    end;
  end;
end;

Вот возобновляемый подход. Однако загрузка и сохранение текущей позиции и символа-разделителя имеют определенную стоимость:

type
  TTokenizer = record
  private
    FSource: string;
    FCurrPos: PChar;
    FDelim: Char;
  public
    procedure Reset(const ASource: string; ADelim: Char); inline;
    function GetToken(out AResult: string): Boolean; inline;
  end;

procedure TTokenizer.Reset(const ASource: string; ADelim: Char);
begin
  FSource := ASource; // keep reference alive
  FCurrPos := PChar(FSource);
  FDelim := ADelim;
end;

function TTokenizer.GetToken(out AResult: string): Boolean;
var
  cp, start: PChar;
  delim: Char;
begin
  // copy members to locals for better optimization
  cp := FCurrPos;
  delim := FDelim;

  if cp^ = #0 then
  begin
    AResult := '';
    Exit(False);
  end;

  start := cp;
  while (cp^ <> #0) and (cp^ <> Delim) do
    Inc(cp);

  SetString(AResult, start, cp - start);
  if cp^ = Delim then
    Inc(cp);
  FCurrPos := cp;
  Result := True;
end;

Вот полная программа, которую я использовал для тестирования.

Вот результаты:

*** count=3, Length(src)=200
GetTok1: 595 ms
GetTok2: 547 ms
GetTok3: 2366 ms
GetTok4: 407 ms
GetTokBK: 226 ms
*** count=6, Length(src)=350
GetTok1: 1587 ms
GetTok2: 1502 ms
GetTok3: 6890 ms
GetTok4: 679 ms
GetTokBK: 334 ms
*** count=9, Length(src)=500
GetTok1: 3055 ms
GetTok2: 2912 ms
GetTok3: 13766 ms
GetTok4: 947 ms
GetTokBK: 446 ms
*** count=12, Length(src)=650
GetTok1: 4997 ms
GetTok2: 4803 ms
GetTok3: 23021 ms
GetTok4: 1213 ms
GetTokBK: 543 ms
*** count=15, Length(src)=800
GetTok1: 7417 ms
GetTok2: 7173 ms
GetTok3: 34644 ms
GetTok4: 1480 ms
GetTokBK: 653 ms

В зависимости от характеристик ваших данных,

12
ответ дан 29 November 2019 в 20:51
поделиться

Delphi компилируется в ОЧЕНЬ эффективный код; по моему опыту, на ассемблере было очень сложно сделать что-то лучше.

Я думаю, вам следует просто указать PChar (они все еще существуют, не так ли? Я расстался с Delphi около 4.0) в начале строки и увеличивайте его, считая "|", пока не найдете n-1 из них. Я подозреваю, что это будет быстрее, чем повторный вызов PosEx.

Обратите внимание на эту позицию, затем увеличивайте указатель еще немного, пока не дойдете до следующего канала. Вытащите свою подстроку. Готово.

Я только догадываюсь, но я не удивлюсь, если это будет близко к самому быстрому решению этой проблемы.

РЕДАКТИРОВАТЬ: Вот что я имел в виду. Этот код, увы, не скомпилирован и не протестирован, но он должен продемонстрировать то, что я имел в виду.

В частности, Delim обрабатывается как один символ, что, как я считаю, имеет огромное значение, если он будет соответствовать требованиям, а символ в PLine тестируется только один раз. Наконец, больше нет сравнения с TokenNum; Я считаю, что для подсчета разделителей быстрее уменьшить счетчик до 0.

function GetTok(const Line: string; const Delim: string; const TokenNum: Byte): string;
var 
  Del: Char;
  PLine, PStart: PChar;
  Nth, I, P0, P9: Integer;
begin
  Del := Delim[1];
  Nth := TokenNum + 1;
  P0 := 1;
  P9 := Line.length + 1;
  PLine := PChar(line);
  for I := 1 to P9 do begin
    if PLine^ = Del then begin
      if Nth = 0 then begin
        P9 := I;
        break;
      end;
      Dec(Nth);
      if Nth = 0 then P0 := I + 1
    end;
    Inc(PLine);
  end;
  if (Nth <= 1) or (TokenNum = 1) then
    Result := Copy(Line, P0, P9 - P0);
  else
    Result := '' 
end;
9
ответ дан 29 November 2019 в 20:51
поделиться

Использование ассемблера было бы микрооптимизацией. Оптимизация алгоритма дает гораздо больший выигрыш. Не выполнять работу лучше, чем выполнять работу максимально быстро, каждый раз.

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

Но в целом Я согласен с ответом Карла (+1), использование PChar для сканирования, вероятно, будет быстрее, чем ваш текущий код.

2
ответ дан 29 November 2019 в 20:51
поделиться

В вашем коде, я думаю, это единственная строка, которую можно оптимизировать:

Result := copy(Line, P+1, MaxInt)

Если вы вычислите новую длину там, она может стать немного быстрее, но не на 10%, которые вы ищите.

Ваш алгоритм токенизации кажется вполне нормальным. Для оптимизации я бы пропустил его через профилировщик (например, AQTime от AutomatedQA) с репрезентативным подмножеством ваших производственных данных. Это укажет вам на самое слабое место.

Единственная функция RTL, которая подходит близко, - это функция в модуле классов:

procedure TStrings.SetDelimitedText(const Value: string);

Она токенизирует, но использует как QuoteChar , так и Delimiter ], но вы используете только разделитель.

Он использует функцию SetString в системном модуле, что является довольно быстрым способом установки содержимого строки на основе PChar / PAnsiChar / PUnicodeChar и length.

Это тоже может помочь вам; с другой стороны, Копирование тоже действительно быстрое.

1
ответ дан 29 November 2019 в 20:51
поделиться

Я не тот человек, который всегда обвиняет алгоритм, но если я посмотрю на первую часть источника, проблема в том, что для строки N вы снова выполняете POS / Posexes для строки 1..n-1.

Это означает, что для N элементов вы выполняете sum (n, n-1, n-2 ... 1) POSes (= + / - 0,5 * N ^ 2), а требуется только N.

Если вы просто кэшируете позицию последнего найденного результата, например, в записи, переданной с помощью параметра VAR, вы можете получить много.

типа
TLastPosition = запись elementnr: целое число; // номер последнего токена elementpos: целое число; // индекс символа последнего совпадения конец;

и затем что-то

if tokennum = (lastposition.elementnr + 1), то начать newpos: = Posex (разделитель, строка, lastposition.elementpos); end;

К сожалению, сейчас у меня нет времени писать это, но я надеюсь, что вы поняли идею

1
ответ дан 29 November 2019 в 20:51
поделиться
Другие вопросы по тегам:

Похожие вопросы: