вторник, 10 марта 2009 г.

Работа со строками

2. Работа со строками.

2.1. Поиск подстроки в строке.

Обычно программист, перешедший с Pascal на Delphi, сам пишет функцию поиска подстроки в строке. Выглядит она примерно так:
Function FindSubStrInStr(Const Str,SubStr:string):Boolean;
Var
  i,j:integer;
Begin
  Result:=false;
  For i:=1 to Length(str) do
  begin
    If (Str[i]=SubStr[1]) and (Length(substr)<=Length(Str)-(i-1)) then
    begin
      Result:=true;
      For j:=2 to Length(SubStr) do
      begin
        If SubStr[j]<>Str[i+j-1] then
        begin
          Result:=false;
          Break;
        End;
      End;
    End;
    If Result Then
      Break;
  End;
End;

Я написал и отладил эту функцию где-то за 3 минуты, но я уже, когда писал что-то подобное и имел представление что мне нужно. А теперь давайте обратимся к стандартному модулю System. В нем есть замечательная функция Pos.

Функция Pos:

Параметры:
1. Подстрока, тип string.
2. Строка, тип string.

Возвращаемое значение:
Тип integer. Функция возвращает индекс первого символа подстроки в строке. Если подстрока не найдена – 0.

И что же у нас получается? Программист, не знающий о функции Pos потратит минуты 3, пока будет писать свою. Программист, знающий о существовании этой функции потратит на ту же задачу секунд десять. Однако описание этой функции все-таки иногда встречается в книгах.

2.2. Подсчет вхождений подстроки в строку.

Теперь давайте рассмотрим вторую похожую задачу. Нужно подсчитать количество вхождение подстроки в строку. Перепишем нашу функцию:

Function CountSubStrInStr(Const Str, SubStr:string):Integer;
Var
  i,j:integer;
  find:Boolean;
Begin
  Result:=0;
  For i:=1 to length(str) do
  begin
    Find:=false;
    If (Str[i]=SubStr[1]) and (Length(substr)<=Length(Str)-(i-1)) then
    begin
      Find:=true;
      For j:=2 to Length(SubStr) do
      begin
        If SubStr[j]<>Str[i+j-1] then
        begin
          Find:=false;
          Break;
        End;
      End;
    End;
    If Find Then
      Inc(Result);
  End;
End;

На эту функцию я так же потратил где-то три минуты. А можно подключить к проекту модуль StrUtils в котором есть функция PosEx.

Функция PosEx:

Параметры:
1. Подстрока, тип string.
2. Строка, тип string.
3. Смещение (индекс символа, с которого начинается поиск), тип integer;

Возвращаемое значение:
Тип integer. Функция возвращает индекс первого символа подстроки в строке. Если подстрока не найдена – 0.

Теперь функция CountSubStrInStr будет выглядеть так:

Function CountSubStrInStr(Const Str, SubStr:string):Integer;
var
  i:integer;
begin
  Result:=0; //Значение по-умолчанию 0 (подстрок не найдено).
  i:=1; //Начинаем поиск с первого символа.
  repeat
    i:=PosEx(SubStr,Str,i); //Присваиваем i результат работы PosEx.
    if i<>0 then //Если подстрока найдена…
    begin
      Inc(Result); //…увеличиваем результат на единицу…
      i:=i+Length(SubStr); //…и увеличиваем значение i на длину подстроки.
    end;
  until i=0; //Выходим из цикла, когда подстрок больше (вообще) не найдено.
end;

По-моему, смориться куда лучше, да и скорость выполнения значительно выше.

2.3 Разбить предложение на слова.

Это задача является одной из моих любимых. Помниться еще на первом курсе на её решение на Pascal потратил минут 40. Потом как-то, пришлось решать заново уже на Delphi, и минут за 10 накидал такую процедуру:

Procedure DivideString(const Str:string; StrList:TStrings);
Type
  Mnoj=set of char;
Var
  i:integer;
  subs:string;
Сonst
  Mn:Mnoj=['А'..'Я', 'а'..'я', '0'..'9', '-', #39];
Begin
  subs:='';
  for i:=1 to length(str) do
  begin
  if not (str[i] in Mn) then
    Continue;
    subs:=subs+str[i];
    if (not (str[i+1] in Mn)) or (i=length(str)) then
    begin
      StrList.Add(subs);
      subs:='';
    end;
  end;
End;

Она вполне работоспособна и отлично справляется со своими обязанностями, разбивая строку за один проход (а ведь были предложения сначала разбить предложение на слова, используя в качестве разделителей только пробелы, а потом еще и знаки препинания в другом цикле отсекать). Но вместо того, чтобы тратить 10 минут, можно потрать одну, и использовать в коде функцию ExtractStrings.

Функция ExtractStrings:

Параметры:
1. Множество символов используемых в качестве делителей, тип TSysCharSet.
2. Множество символов, которые будут игнорироваться, только если они находятся в начале строки, тип TSysCharSet.
3. Строка, тип PChar.
4. Список строк, в который будут добавлены получившиеся в результате разбиения слова, тип TStrings.

Возвращаемое значение:
Тип integer. Функция возвращает количество получившихся слов в результате разбиения строки.

Единственный минус, найденный мной в работе этой функции, это не возможность обработать символ «’» в сочетании с русскими буквами. То есть если мы введет строку: «Computed solution d’Alembert’s force», - то получим вполне ожидаемые четыре слова. А если: «Найденное решение и есть д'Аламберова сила», - функция просто откажется разбивать строку после «’» вернув оставшееся как одно слово. То есть мы получим пять слов вместо шести.

воскресенье, 15 февраля 2009 г.

Рефакторинг.

Сама среда разработки дает нам в руки множество возможностей, очень облегчающих жизнь. Все мы знаем про то, что после точки Delphi предложит набор свойств и методов, доступных в данном классе, а вот про то, что если нажать Ctrl+Пробел, то среда предложит набор функций и констант, доступных во всех подключенных модулях, плюс все переменные, свойства и методы данного модуля – знают немногие.
Многим не нравится то, что в Pascal (соответственно и в Delphi) переменные объявляется в специальной секции Var и часто, чтобы объявить новую переменную приходиться прокручивать много кода. В последних версиях Delphi эта «проблема» решена: достаточно написать var и нажать Tab. Среда создаст строку: LVar: Integer; Пишем имя будущей переменной, нажимаем Tab, задаем тип и нажимаем Tab еще раз. Среда сама переместить переменную в секцию var. Так же эта возможность (как и другие сокращения) доступны через Ctrl+Пробел. Можно же сделать иначе: написать имя будущей переменной прямо в коде функции, навести на неё курсор и нажать Shist+Ctrl+V. Появиться окно Declare Variable. В поле Name будет имя новой переменной, которое нельзя изменить. В поле Type – тип будущей переменной, если среда определила его неверно, то его можно изменить. Если включить флажок Array, то среда создаст динамический массив указанного типа, с глубиной вложенностью указанной в поле Dimensions. Если включить флажок Set Value, то можно будет указать значение переменной, присваемое ей при инициализации. Так же новую переменную можно сделать полем класса. Для этого надо нажать Shift+Ctrl+D. Изменяемые поля в окне Declare New Field те же что и Declare Variable.
Еще одна замечательная возможность среды – Extract Method. Например, при написании какой-то функции выходит так, что часть кода будет использоваться где-то еще. Можно при написании другой функции скопировать эту часть кода, объявит те же переменные, присвоить им значения, в общем, подгонять код под другие условия. А можно использовать это код как отдельную функцию. Но вырезать уже написанные строки, создавать новую функцию с необходимыми параметрами, и вставлять в неё код – будет дольше, чем выделить написанное, и нажать Shift+Ctrl+M. Появиться новое окно Extract Method. В окно New method name вводим имя нового метода, а в окне Sample extracted code будет представлен будущий код. Причем все переменные которые используются только внутри выделенного участка перенесутся в секцию Var нового метода, а все переменные которые используются и в другом коде будут переданы в новый метод в качестве параметров. Нажмем ok, и выделенные строки перенесутся в новый метод, а на их месте появиться его вызов. Конечно, не всегда новый метод создается безупречно, и приходится что-то правит вручную, однако эта возможность действительно очень удобна.
Так же стоит обратить внимание на Sync Edit Mode. Предположим, что в уже написанном и отлаженном модуле, содержащем приличное количество строк, нужно изменить имя глобальной переменной. Сделать это быстро, но потом придется потратить немало времени, переименовывая эту переменную везде, где они встречается. Воспользоваться вслепую функцией «Замена» тоже удается не всегда, например, когда надо переименовать переменную «i» или «s», так как это буквы наверняка входят в имена других переменных и методов. Конечно, можно возложить работу на компилятор – попытаться скомпилировать модуль, а потом пройтись по всем ошибкам исправляя имя переменной. Но самый быстрый и, на мой взгляд, верный способ решить данную задачу – это выделить текст всего модуля комбинацией Ctrl+A и нажав Shift+Ctrl+J, изменить имя переменной только там где оно объявлено. Среда сама переименует её везде, где она используется. Замечу, что так можно изменять не только имена переменных, но и методов и свойств классов, имена самих классов и имена функций и процедур. Так же можно не выделить текст всего модуля, а только конкретный участок. Изменить же имя только переменной можно еще одним способом: навести курсор на переменную которую надо переименовать и нажать Shift+Ctrl+E. В появившемся окне Rename variable поле New name задаем новое имя. Если оставить галочку View reference before refactoring, то среда сначала покажет все места, где переменная будет переименована, перед подтверждением программистом.
Продолжим здесь: Работа со строками

Секреты Delphi или переход с Pascal

Обычно, начинающие программисты, переходя с языка Pascal на Delphi, перетаскивают свои старые функции и прочие участки часто встречающегося кода без изменений, не зная, сколько всего уже реализовано в VCL. Связанно это, прежде всего, с тем, что чаще всего при преподавании языка Pascal в учебных заведениях рассматривают только модули CRT и Graph, содержащие только базовые функции, а современные технические писатели, описывающие Delphi, рассматривают только малую часть всех возможностей данного языка. Вот и выходит, что код нагромождается лишними, часто неоптимизированными функциями.
Так было и со мной, когда после года изучения языка Pascal в вузе, я решил сам освоит Delphi. Купил несколько книжек по данному языку, разобрался с тем, как создавать формы, вводить и выводить данные, стал писать простенькие программы. Но чем больше я углублялся в Delphi, тем чаще стал замечать, что большинство написанного мною кода уже реализовано в стандартных модулях. Изучая исходный код VCL, я открыл для себя много замечательных функций и типов, которые здорово облегчили бы мне работу над многими программами. Сейчас, разрабатывая уже достаточно сложные программы, прежде чем написать какую-нибудь функцию, которая делает более-менее стандартные действия, я проверяю - нет ли уже её реализации в VCL и, процентах в тридцати, такая функция оказывается.
Так вот, совершенно недавно, открыв для себя замечательным модуль ConvUtils (о нем мы еще поговорим), я решил написать данную статью, дабы рассказать новичкам, стремящимся изучить замечательный язык программирования Delphi, о скрытых функциях и возможностях, которые почти нигде не описаны и сразу не бросаются в глаза.
Итак, начнем: Рефакторинг.

О чем этот блог?

Да собственно о том же, о чем и говорит его название. Определенные мысли могут возникать спонтанно и не всегда запоминаются, а так: что-то придумал, вспомнил что-то о чем хотел написать и сразу вынес текущую мысль на все общее обозрение.
Например, я уже давно пытаюсь написать статью "Секреты Delphi или переход с Pascal", но все никак: то не хватает времени, то нахожусь там, где нет доступа к компьютеру. А бывает что и засяду наконец за неё, а о чем хотел написать уже и не вспомню (вернее не могу вспомнить как, хотя до этого в голове был стройный текст).
Вот я и решил завести данный блог. Сижу, решаю какую-нибудь задачу и вдруг раз, и нашел интересное, как мне кажется, решение. Останется только занести его в блог, и оно уже никуда не денется. В общем скоро будет первая мысль!