Версия для печати
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум на Исходниках.RU > Все языки: Статьи, заготовки в FAQ > Функции Split, Join, GetElement


Автор: Fr0sT 01.11.12, 09:58
Функции Split, Join, GetElement

Апдейт этой темы - т.к. отредактировать тот пост нет возможности, а исходная тема удалена.

Набор функций для работы со строками, содержащими списки элементов с разделителями. Аналог одноименных функций из JS, PHP. Разделитель может быть любой длины.

Проверено в Delphi 2009-10, но должно работать и в более старых версиях

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    uses StrUtils; // для PosEx
     
    const
      // Разделитель по умолчанию для Split, Join, GetElement
      DefListDelim = ';';
     
    ...
     
    type
      // Алиас типа вместо нового объявления - в целях совместимости.
      // Можно было бы юзать TArray<string>, но он глючит при использовании в параметрах
      // (i := Low(arr) - пишет Incompatible types)
      TStrArray = Types.TStringDynArray;
     
    ...
     
      function Split(const Str: string; Delim: string = DefListDelim; AllowEmpty: Boolean = True): TStrArray;
      function GetElement(const Str: string; ElemIdx: Integer; Delim: string = DefListDelim): string;
      function Join(const Arr: array of string; Delim: string = DefListDelim; AllowEmpty: Boolean = True): string;
     
    ...
     
    // Расщепление строки Str на массив элементов.
    //   Str - исходная строка
    //   Delim - разделитель элементов в строке (может насчитывать сколько угодно символов)
    //   AllowEmpty - добавлять ли в массив пустые элементы ('elem1;elem2;;elem3').
    //     Потребуется, если исходная строка представляет собой неоднородный список
    //     с фиксированными индексами элементов
    // Пример:
    //   HTMLlines := Split(HTMLPageSource, #13#10, False)
    //   BananaProperties := Split('banana;yellow;;Africa', ';', True)
    function Split(const Str: string; Delim: string; AllowEmpty: Boolean): TStrArray;
    var CurrDelim, NextDelim, CurrIdx: Integer;
    begin
      if Str = '' then begin SetLength(Result, 0); Exit; end;
      CurrDelim := 1; CurrIdx := 0; SetLength(Result, 16);
      repeat
        if CurrIdx = Length(Result) then
          SetLength(Result, CurrIdx + 16);                // проверяем наполненность массива и расширяем при необходимости
        NextDelim := PosEx(Delim, Str, CurrDelim);        // позиция следующего разделителя
        if NextDelim = 0 then NextDelim := Length(Str)+1; // строка кончилась
        Result[CurrIdx] := Copy(Str, CurrDelim, NextDelim - CurrDelim);
        CurrDelim := NextDelim + Length(Delim);
        // если элемент непуст или же пустые допустимы - увеличиваем индекс
        if (Result[CurrIdx] <> '') or AllowEmpty
          then Inc(CurrIdx)
          else Continue;
      until CurrDelim > Length(Str);
      SetLength(Result, CurrIdx);                      // обрезаем массив
    end;
     
    // Получение 0-based элемента из строки с разделителями
    //   Str - исходная строка
    //   ElemIdx - индекс элемента, от 0 до ...
    //   Delim - разделитель элементов в строке (может насчитывать сколько угодно символов)
    // Пример:
    //   value := GetElement('GeneralSettings.SomeIniOption = 123', 1, ' = ');
    function GetElement(const Str: string; ElemIdx: Integer; Delim: string): string;
    var CurrDelim, NextDelim, Idx: Integer;
    begin
      Result := ''; CurrDelim := 1;
      // ElemIdx раз ищем символ разделителя в строке
      for Idx := 1 to ElemIdx do
      begin
        CurrDelim := PosEx(Delim, Str, CurrDelim);
        if CurrDelim = 0 then Exit;
        Inc(CurrDelim, Length(Delim)); // теперь здесь индекс первого символа следующего элемента
      end;
      NextDelim := PosEx(Delim, Str, CurrDelim); // ищем конечный разделитель
      if NextDelim = 0 then NextDelim := Length(Str) + 1;
      Result := Copy(Str, CurrDelim, NextDelim-CurrDelim);
    end;
     
    // Объединение всех строк из массива в одну с разделителями (обратно Split)
    //   Arr - массив строк
    //   Delim - разделитель элементов в строке (может насчитывать сколько угодно символов,
    //     (!) в том числе и ни одного (!) )
    //   AllowEmpty - добавлять ли в строку пустые элементы
    //     Потребуется, если итоговая строка должна представлять собой неоднородный список
    //     с фиксированными индексами элементов
    // Пример:
    //   FruitList := Join(['apples', 'bananas', 'grape'], '; ')
    //   BananaProperties := Join([Banana.Name, Banana.Color, '', Banana.Country], ';', True)
    //   ReJoin := Join(Split('one;two;three'), '|');
    function Join(const Arr: array of string; Delim: string; AllowEmpty: Boolean): string;
    var i: Integer;
    begin
      Result := '';
      for i := Low(Arr) to High(Arr) do
      begin
        if (Arr[i] = '') and not AllowEmpty then Continue;
        if Result = ''
          then Result := Arr[i]
          else Result := Result + Delim + Arr[i];
      end;
    end;


12.11
* Тип TStrArray теперь является алиасом Types.TStringDynArray по совету jack128. Более стильный TArray<string>, к сожалению, глючит в качестве параметра.
* Добавил Join с параметром типа array of string - код полностью аналогичен, но позволяет задавать параметр-массив прямо в коде без использования вспомогательной StrArray

17.12
* Убраны StrArray и Join с параметром TStrArray: Join с параметром array of string работает и с переменными типа TStrArray

Автор: --Ins-- 01.11.12, 18:28
Fr0sT, а чем не устроила стандартная ExtractStrings? И зачем выходные данные в виде массива? TStrings всяко удобнее, особенно тем, что повсеместно используется в VCL - можно сразу подставлять в него TMemo.Lines или TComboBox.Items и так длаее

Автор: Fr0sT 02.11.12, 06:54
--Ins--, тем, что TStrings надо удалять.

Автор: --Ins-- 02.11.12, 08:45
Цитата Fr0sT @
--Ins--, тем, что TStrings надо удалять.


Ну, сомнительный недостаток, имхо. По мне так массивы - крайне неудобная вещь, практически нигде их не использую, заменяя контейнерными классами. Особенно с учетом того, что в интерфейсах VCL именно они и используются, что позволяет их использовать "как есть", а вот массивы почти всегда нужно вручную обрабатывать и преобразовывать к какому-либо виду. Не говоря уже о том, что контейнерные классы по сравнению с массивами поддерживают ряд дополнительных возможностей

Автор: Fr0sT 02.11.12, 09:46
Жалко, что предыдущей темы не осталось - там было в точности то же самое, причем с твоим же участием :)

Мне лично удобно, очень часто юзаю эти функции для кратковременных задач.

Автор: --Ins-- 02.11.12, 10:14
Цитата Fr0sT @
Жалко, что предыдущей темы не осталось - там было в точности то же самое, причем с твоим же участием


Серьезно? :D Я уже и не помню. Ну тогда ладно

Автор: jack128 11.11.12, 17:07
Цитата Fr0sT @
type
TStrArray = array of string;

за такое - растрел на месте полагается.
Либо TArray<string> должен быть если >=Delphi2009 юзаешь, либо Types.TStringDynArray если раньше.

Автор: Fr0sT 12.11.12, 06:48
Не стреляйте, дяденька! :)
Лучше аргументируй столь категоричное высказывание.

Автор: jack128 12.11.12, 07:21
Цитата Fr0sT @
Лучше аргументируй столь категоричное высказывание.

А чего тут аргументировать?? Вот есть три модуля

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    unit Unit1;
     
    type
      TStrArray1 = array of string;
     
    funciton f1(): TStrArray1;

...

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    unit Unit2;
     
    type
      TStrArray2 = array of string;
     
    function f2(arr: TStrArray2): TStrArray2;


<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    unit Unit3;
     
    type
      TStrArray3 = array of string;
     
    function f3(arr: TStrArray3): TStrArray3;


разработчик каждого нового модуля считает себя самым умными и объявляет новый тип для массива строк.

в результате я вместо того, чтобы просто написать f3(f2(f1))

вынужден либо unsafe type cast'ом заниматься, либо писать пачку преобразователей TStrArray1 -> TStrArray2 -> TStrArray3.

Автор: Fr0sT 12.11.12, 10:26
* Тип TStrArray теперь является алиасом Types.TStringDynArray по совету jack128. Более стильный TArray<string>, к сожалению, глючит в качестве параметра.
* Добавил Join с параметром типа array of string - код полностью аналогичен, но позволяет задавать параметр-массив прямо в коде без использования вспомогательной StrArray

Автор: jack128 12.11.12, 12:33
TStringBuilder в D2009 есть?? Если есть, то в Join - он так и просится.

Автор: Fr0sT 12.11.12, 12:44
jack128, хм, а что, в нём нет такой функции? Интересная идея, погляжу

Автор: leo 12.11.12, 15:37
Цитата Fr0sT @
* Добавил Join с параметром типа array of string - код полностью аналогичен, но позволяет задавать параметр-массив прямо в коде без использования вспомогательной StrArray

В таком случае вариант с параметром Arr:TStrArray является вроде как излишним\масло-масляным, т.к. дин.массив можно передавать вместо открытого массива

Автор: --Ins-- 12.11.12, 20:29
Цитата leo @
В таком случае вариант с параметром Arr:TStrArray является вроде как излишним\масло-масляным, т.к. дин.массив можно передавать вместо открытого массива


Только вроде как в рантайм преобразование будет, если память не подводит, так что можно второй вариант оставить

Автор: leo 13.11.12, 16:55
Цитата --Ins-- @
Только вроде как в рантайм преобразование будет

Нет, в обоих случаях массив передается непосредственно по указателю, и разница лишь в том, что при Arr:TStringArray ничего доп-но не передается и High(Arr) определяется внутри функции по теневому заголовку Arr (вызовом _DinArrayHigh), а в случае с open array _DinArrayHigh(Arr) вызывается до вызова функции и передается в нее в виде доп.теневого параметра, а внутри функции High(Arr) просто берет это значение из параметра (в данном случае из регистра EDX).

Автор: Fr0sT 14.11.12, 06:38
Цитата leo @
Нет, в обоих случаях массив передается непосредственно по указателю, и разница лишь в том, что при Arr:TStringArray ничего доп-но не передается и High(Arr) определяется внутри функции по теневому заголовку Arr (вызовом _DinArrayHigh), а в случае с open array _DinArrayHigh(Arr) вызывается до вызова функции и передается в нее в виде доп.теневого параметра, а внутри функции High(Arr) просто берет это значение из параметра (в данном случае из регистра EDX)

О! А я всё кумекал, в чем разница и почему нельзя просто сделать одну функцию для обоих случаев. Спасибо!

Автор: leo 17.11.12, 10:48
Цитата Fr0sT @
О! А я всё кумекал, в чем разница и почему нельзя просто сделать одну функцию для обоих случаев

В качестве open array параметра, можно передавать не только [...] - конструктор массива "на лету", но и любой массив, совместимый по типу элементов (динамический, статический или выделенный в куче по GetMem), а также одну переменную типа элемента массива (которая рассматривается как массив длины 1). Причем для статических и выделенных по GetMem массивов, во-первых, не обязательна индексация от 0 (хотя внутри функции open array по любому индексируется с 0), во-вторых, можно передавать не весь массив, а произвольное число первых элементов с помощью псевдофункции Slice(массив,Count), которая на этапе компиляции просто заменяет значение передаваемого теневого параметра High(Arr) на Count.
Ну и разумеется, во всех этих случаях при объявлении параметра-массива как const в функцию просто передается указатель на исходный массив и значение High=Count-1

Автор: Rouse_ 17.12.12, 14:17
Fr0sT в итоге что, в первом посте у тебя финальная версия кода?
Если нет - то приведи в порядок, перед НГ будем причесывать раздел и переносить все в FAQ, поэтому желательно чтобы все было готово.

Автор: Fr0sT 17.12.12, 14:35
Rouse_, готово.

Powered by Invision Power Board (https://www.invisionboard.com)
© Invision Power Services (https://www.invisionpower.com)