Символы и строки (PascalABC.NET)

Материал из Информационная безопасностя
Перейти к навигации Перейти к поиску

Pascal ABC.NET выбор школьника - Часть 3

Символы (char)

Данные символьного типа имеют тип char и занимают в памяти два байта. Используется кодировка стандарта Unicode. В тексте программы символьная константа (так называемый литерал) всегда заключается в одинарные кавычки.

 1 var c1, p135, rz: char; // три переменные
 2 var ch1: char; // одна переменная
 3 var s: sequence of char; // последовательность символов;
 4 var ca: array[1..35] of char; // статический массив символов
 5 var ar: array of char; // динамический массив символов
 6 var m: array[,] of char; // матрица символов
 7 
 8 var a: char := 'a'; // тип указан явно
 9 var b:= 'b'; // автовыведение типа
10 var kt:= ('A', 'B', 'C'); // кортеж из трех символов

Перевод символа в его код и обратно

Символ в код

  • Ord(c) – код символа c в Unicode (тип word длиной 2 байта);
  • char.Code – то же, точечная нотация;

Код в символ

  • Chr(код) – символ с указанным кодом Unicode;
  • #код – символ с указанным кодом Unicode; принимает только литерал;

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

1 ##
2 for var i := 1040 to 1050 do
3   Writeln(i, ' ', chr(i));

Принадлежность символа к группе

Является ли символ буквой?

c.IsLetter

Расширение c.IsLetter возвращает True, если символ c принадлежит к группе букв и False в противном случае.

Является ли символ цифрой?

c.IsDigit

Расширение c.IsDigit возвращает True, если символ c принадлежит к группе цифр и False в противном случае.

Является ли символ пробельным?

Пробельные символы – термин, пришедший из типографской практики. Пробел – это пустое место, интервал между символами. Пробельными в типографском и издательском деле называют непечатаемые (и неотображаемые) символы. Символы с десятичными кодами 9..13 относятся к так называемым управляющим символам – раньше они управляли внешними устройствами, такими как механический принтер. Символ с кодом 32 – это пробел.
char.IsWhiteSpace(c)

Статический метод char.IsWhiteSpace(c) возвращает True, если символ c принадлежит к группе пробельных символов и False в противном случае.

Является ли символ знаком препинания?

char.IsPunctuation(c)

Статический метод char.IsPunctuation(c) возвращает True, если символ c принадлежит к группе знаков пунктуации (разделителям) и False в противном случае.

Принадлежит ли буква к верхнему регистру?

c.IsUpper

Расширение c.IsUpper возвращает True, если символ c принадлежит к буквенным символам верхнего регистра (прописным) и False в противном случае. Если символ не является буквой, всегда возвращается False.

Принадлежит ли буква к нижнему регистру?

c.IsLower

Расширение c.IsLower возвращает True, если символ c принадлежит к буквенным символам нижнего регистра (строчным) и False в противном случае. Если символ не является буквой, всегда возвращается False.

Принадлежит ли символ интервалу?

c in c1..c2

Конструкция c in c1..c2 вернет True, если символ c принадлежит группе символов от с1 до с2 включительно и False в противном случае.

Операции преобразования символов

Смена регистра буквенного символа

  • c.ToUpper() – расширение возвращает буквенный символ c, приведенный к верхнему регистру, если он принадлежит к нижнему регистру. В противном случае символ возвращается без изменения;
  • UpCase(c) – функция, делающая то же самое;
  • c.ToLower() – расширение возвращает буквенный символ c, приведенный к нижнему регистру, если он принадлежит к верхнему регистру. В противном случае символ возвращается без изменения;
  • LowCase(c) – функция, делающая то же самое

Преобразование цифрового символа в число

c.ToDigit

Расширение c.ToDigit возвращает буквенный символ c, преобразованный к изображаемому им целому неотрицательному однозначному числу типа integer. Если символ не является цифрой при выполнении программы будет выдано сообщение «Ошибка времени выполнения: not a Digit» и программа завершится аварийно.

Получение символа, соседнего с указанным

  • c.Pred – расширение возвращает буквенный символ, код которого в кодовой таблице предшествует коду символа c;
  • Pred(c) – функция, делающая то же самое;
  • c.Succ – расширение возвращает буквенный символ, код которого следует за кодом символа c;
  • Succ(c) – функция, делающая то же самое.

Смещение по символам кодовой таблицы

  • Dec(c) – процедура, заменяющая значение переменной, содержащей символ c на символ, предшествующий ему. Ведь это обычная

операция декремента, только для символа;

  • Dec(c, n) – процедура, заменяющая значение переменной c, содержащей символ c на символ, находящийся в кодовой таблице на n

позиций раньше. Тоже декремент;

  • Inc(c) – процедура, заменяющая значение переменной, содержащей символ c на символ, следующий за ним. Это инкремент.
  • Inc(c, n) – процедура, заменяющая значение переменной, содержащей символ c на символ, находящийся в кодовой таблице на n

позиций дальше. И это инкремент.

Ввод символов

Функции, осуществляющие ввод символьных данных с приглашением:

1 var c1 := ReadlnChar('ТекстПриглашения'); // ввод одного символа
2 var c2 := ReadlnChar2('ТекстПриглашения'); // ввод двух символов
3 var c3 := ReadlnChar3('ТекстПриглашения'); // ввод трех символов
4 var c4 := ReadlnChar4('ТекстПриглашения'); // ввод четырех символов

Вывод символов

С выводом все просто: используются уже знакомые процедуры Write, Writeln, Print, Println, а также расширения .Print и .Println. Имеется лишь oдна особенность вывода символов при помощи расширений: разделителем по умолчанию является не пробел, а пустой символ.

Строки

В PascalABC.NET строка – это последовательность символов практически неограниченной длины (на самом деле, строка не может занимать в памяти больше 2.1 Гбайт), принадлежащая некоторому алфавиту.

Символы в строке нумеруются от единицы (Эта особенность унаследована от языка Turbo Pascal) и чтобы обратиться к символу с номером k в строке s нужно написать s[k].

Чтобы символы строк нумеровались с 0 как и элементы динамических массивов необходимо в начале файла написать директиву компилятора.

{$ZeroBasedStrings}

Имеются два типа строк. Первый тип – строки длиной не превышающей 255 символов, унаследованные от языка Turbo Pascal. Они именуются короткими строками». При описании короткой строки после ключевого слова string в квадратных скобках указывается ее максимальная длина. Короткие строки оставлены в языке лишь для совместимости со старыми программами. Второй, основной тип строк – современные строки; при их описании длина не указывается. Мы будем рассматривать именно такие строки.

1 var st, s1, p18: string; // три строки
2 var s1: string; // одна строка
3 var sos: sequence of string; // последовательность строк
4 var ar: array of string; // динамический массив строк
5 var sh1: string[27]; // короткая строка, максимум 27 символов
6 // Описание строк можно соединять с инициализацией:
7 var s1: string := 'Это строка'; // тип указан явно
8 var s2 := '*** И это строка ***'; // автовыведение типа
9 var kt := ('Это', 'тоже', 'строка'); // кортеж из трех строк

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

Длина строки

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

  • s.Length – свойство, возвращает длину строки s
  • Length(s) – функция, делающая то же самое

Нумерация символов в строке от 0 или от 1

Если вы не используете директиву компилятора {$ZeroBasedStrings} для нумерации символов строк с 0, то важно иметь ввиду это различие.

Несмотря на то, что символы в строке нумеруются от единицы, при использовании любых методов класса string, к которому относятся строки, считается что символы в строке индексируются от нуля. В функциях и процедурах, не относящихся к этому классу, а также в срезах, символы считаются проиндексированными от единицы.

Запомнить это проще всего визуально. Если в коде программы строка s используется в записи вида string.Метод(s) или s.Метод, она считается индексированной от нуля. Если «точки» нет, то строка считается индексированной от единицы.

Например в этом коде находится и выводится индекс символа '5' в строке s. Поскольку s.IndexOf – метод класса string, считается, что индексы идут от нуля и выводится значение 4. Функция Pos не относится к классу string, поэтому выводится порядковый номер, равный 5.

1 ##
2 var s := '123456789';
3 Print(s.IndexOf('5'), Pos('5', s))
4 5

Ввод строк

Строки можно вводить как классическим оператором

Readln

так и доступными в современном Паскале функциями.

1 Begin
2   var s1 := ReadString('Введите строку');
3   var s2 := ReadString2('Введите 2 строки');
4   var s3 := ReadString3('Введите 3 строки');
5   var s4 := ReadString4('Введите 4 строки');
6 end.

Вывод строк

Write(ln) + Print(ln)

Выводить строки можно любыми средствами, которые использовались для вывода других типов данных: Print и Println, Write и Writeln, а также при помощи расширений .Print и .Println. Расширения рассматривают строку, как последовательность отдельных символов, но выводят эти символы не через пробел, как для других типов данных, а через пустой символ. Тем не менее, расширения .Print и .Println могут принимать в качестве параметра строку-разделитель, что иллюстрирует следующая простая программа.

1 ##
2 'Тестовая строка'.Println;
3 'Тестовая строка'.Println(' ');
4 'Тестовая строка'.Println('*');
5 'Тестовая строка'.Println(' = ');
Тестовая строка
Т е с т о в а я с т р о к а
Т*е*с*т*о*в*а*я* *с*т*р*о*к*а
Т = е = с = т = о = в = а = я = = с = т = р = о = к = а

Арифметические операции со строками

Сложение / Конкатенация

Операция сложения «+», примененная к строкам, объединяет их в общую строку. Если же одним из операндов является значение числового выражения, а вторым – строка, то числовое значение предварительно преобразуется к строковому виду, а потом выполняется объединение.

Умножение

В операции умножения «*» один операнд должен быть строкой, второй - целочисленным выражением. В результате строка соединяется сама с собой указанное значением выражения количество раз, давая новую строку. В одном операторе можно записывать и больше одного сложения и/или умножения.

1 ## Println('--- 25 + 3 * 8 =' + (25 + 3 * 8) + ' ---')
--- 25 + 3 * 8 = 49 ---

Можно «склеить» в строку и повторенный несколько раз отдельный символ, например

1 var s := 30 * '+';

Управление длинной строки

Строку можно усечь или удлинить, изменяя ее длину. Это можно сделать при помощи процедуры SetLength (помните, та самая, управляющая длиной массива). Удлиняемая строка дополняется справа пробелами до нужной длины.

1 begin
2  var P: string -> () := s -> Println('|' + s + '|');
3  var s := 'Маша ела кашу';
4  P(s); // '|' - чтобы видеть пробелы в начале и конце
5  SetLength(s, s.Length - 4);
6  P(s);
7  SetLength(s, s.Length + 6);
8  P(s)
9 end.
|Маша ела кашу|
|Маша ела |
|Маша ела       |

Сравнение строк

Содержимое строк, даже если их длина различна, можно сравнивать между собой. Строки сравниваются посимвольно слева направо до первого несовпадения кодов символов, либо до окончания одной или обеих строк. Для строк определены результаты всех шести операций сравнения: <, <=, =, <>, >, >=. Большим считается тот символ, код Unicode которого больше (говорят, что символы сравниваются лексикографически). Если все символы более короткой строки совпали с символами более длинной, то большей строкой считается более длинная строка.

1 ##
2 Println('Кошка' > 'Мышка'); // False, 'К' < 'М'
3 Println('12345' > '1234'); // True, строка 1 длиннее
4 Println('ЛЕН' > 'ЛЁН'); // True, 'Ё' в Unicode идет перед 'А'
5 Println('лен' > 'лён'); // False, 'ё' в Unicode после 'я'
6 Println('Папа' <= 'мама'); // True, 'П' < 'м'
7 Println('Пoдвох' = 'Подвох') // False, первая 'о' - латинская

Чтобы лучше ориентироваться при сравнении строк, надо рассмотреть таблицу Unicode. Предполагаю, что не каждый читатель захочет этим заниматься, поэтому привожу некоторые сведения, полезные при сравнении.

  • цифры '0'..'9' имеют десятичные коды #48..#56;
  • латинские буквы 'A'..'Z' имеют десятичные коды #65..#90;
  • латинские буквы 'a'..'z' имеют десятичные коды #97..#122;
  • буква 'Ё' имеет десятичный код #1025;
  • буквы кириллицы 'А'..'Я' (кроме Ё) имеют десятичные коды #1040..#1071;
  • буквы кириллицы 'а'..'я' (кроме ё) имеют десятичные коды #1072..#1103;
  • буква 'ё' имеет десятичный код #1105.

Выделение подстроки

Подстрока – это часть строки, полученная путем выборки некоторых ее символов, следующих подряд.

  • расширение s.Left(k) возвращает k левых символов строки s;
  • расширение s.Right(k) возвращает k правых символов строки s.

Для получения подстроки удобно использовать срезы. Легче запоминается, короче запись.

Срезы строк

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

1 ##
2 var s := 'Параграф';
3 s[:5].Println; // Пара
4 s[5:].Println; // граф
5 s[2:5].Println; // ара
6 s[3::2].Println; // рга
7 s[8::-2].Print // фраа

Имеется также расширение .Slice, позволяющее получать срезы.

Нумерация символов здесь ведется от нуля.
  • s.Slice(f, h) – возвращает срез строки s, начиная с позиции f и выбирая символы с шагом h;
  • s.Slice(f, h, k) – возвращает срез строки s длины не более k, начиная с позиции f и выбирая символы с шагом h;
1 ##
2 var s := 'Параграф';
3 s.Slice(0, 1, 4).Println; // Пара
4 s.Slice(4, 1).Println; // граф

Модификация строки

Смена регистра символов

Как и в случае с отдельными символами, в строке можно изменять регистр всех символов или их части. Используются расширения, возвращающие новую строку.

  • расширение s.ToLower возвращает строку s, приведенную к нижнему регистру;
  • расширение s.ToUpper возвращает строку s, приведенную к верхнему регистру.

Удаление символов в начале и конце

Наиболее популярным при обработке строк является удаление пробелов перед первым непробельным символом (левые, или лидирующие пробелы) и после последнего непробельного символа (правые, или завершающие пробелы).

  • s.TrimStart – метод, возвращающий исходную строку s с удаленными лидирующими пробелами;
  • s.TrimEnd – метод, возвращающий исходную строку s с удаленными завершающими пробелами;
  • s.Trim – метод, возвращающий исходную строку s с удаленными лидирующими и завершающими пробелами
1 ##
2 var P: procedure(s: string) := s -> Println('|' + s + '|');
3 var s := '   Маша   ела   кашу   ';
4 P(s); // '|' - чтобы видеть пробелы в начале и конце
5 P(s.TrimStart);
6 P(s.TrimEnd);
7 P(s.Trim);
|   Маша   ела   кашу   | 
|Маша   ела   кашу   | 
|   Маша   ела   кашу| 
|Маша   ела   кашу|

Кроме пробела имеются и другие непечатаемые (пробельные) символы, поэтому встречаются задачи и на удаление всех таких символов. Для этого нужно указать в качестве аргумента удаляемые символы, разделяя их запятыми или использовать массив символов. Не забывайте, что в этом случае пробельные символы тоже должны быть указаны, если они могут быть нежелательными.

1 ##
2 var s := ' .., + Это нужно! . , .,++ ';
3 s.Trim(' ', '.', ',', '+').Println;
4 var d := |' ', '.', ',', '+'|;
5 s.Trim(d).Print;
Это нужно!
Это нужно!

Удаление подстрок

Удаление срезами

Удалить подстроку в позициях символов [i; j] можно при помощи срезов. Для этого нужно взять символы c от начала строки и до номера i, не включая его и соединить с символами, номера которых начинаются после j и следуют до конца строки. Такую операцию для строки с именем s можно записать как

1 s := s[:i] + s[j + 1:];

Удаление функцией Delete

Можно воспользоваться процедурой Delete(s, from, k), удаляющей из строки s подстроку длиной k символов, начиная с позиции from. Если удаляемых символов окажется меньше k, будут удалены символы до конца строки. Эта процедура сохранена в целях совместимости с базовым Паскалем.

1 ##
2 var s := 'У Вани машина, у Наташи - мяч';
3 //        12345678901234567890123456789
4 //        -------                 -----
5 Delete(s, 8, 17);
6 s.Print;

Удаление методом Remove 1

Расширение s.Remove(from, k) возвращает строку, полученную удалением из строки s подстроки длиной k символов, начиная с позиции from. Это расширение класса string, поэтому индексирование ведется от нуля. Если удалить k символов окажется невозможно, выполнение программы завершится аварийно.

1 ##
2 var s := 'У Вани машина, у Наташи - мяч';
3 //        01234567890123456789012345678
4 //        -------                 -----
5 s.Remove(7, 17).Print

Удаление методом Remove 2

Расширение s.Remove(ss) умеет делать еще один вид удаления – возвращать строку, полученную путем удаления из строки s всех вхождений всех подстрок, определенных параметром ss. В качестве этого параметра можно задать массив подстрок, либо перечислить подстроки через запятую.

1 ##
2 var s := 'У Вани машина, у Наташи - мяч';
3 s.Remove('машина', 'мяч', '-').Print
4 // У Вани , у Наташи

Инверсия

Если в строке расположить все ее символы в обратном порядке, то полученную строку называют инвертированной относительно исходной или просто инверсией. Существуют также инверсии подстроки. PascalABC.NET предлагает несколько способов получения инверсии строки

  • s.Inverse – расширение, возвращающее инверсию строки s;
  • Инвертирование строки s с помощью среза s[::-1];
  • ReverseString(s, from, k) – функция, возвращающая строку s, в которой инвертирована подстрока длиной k, начиная с позиции from.
1 ##
2 var s := 'У всякого свой норов и обычай';
3 // 01234567890123456789012345678
4 s.Println;
5 ReverseString(s, 16, 5).Println;
6 s[16:21] := s[20:15:-1]; // реверс части строки срезами
7 s.Print;
У всякого свой норов и обычай
У всякого свой ворон и обычай
У всякого свой ворон и обычай

Вставка подстроки

Средств для вставки подстроки в строку всего два и функционально они несколько отличаются. Процедура Insert изменяет исходную строку, а метод Insert возвращает измененную строку в качестве результата.

  • Insert(ss, s, from) – процедура, вставляющая в строку s подстроку ss, начиная с позиции from. Указание несуществующей позиции приводит ко вставке подстроки перед первым (при from <1) или после последнего символа строки;
  • s.Insert(from, ss) – метод, возвращающий строку, полученную путем вставки подстроки ss в исходную строку s с позиции from. Нумерация позиций, как и подобает методу класса string, ведется от нуля. Указание несуществующей позиции приводит к аварийному завершению программы с выдачей сообщения «Ошибка времени выполнения: Заданный аргумент находится вне диапазона допустимых значений».
1 ##
2 var s := 'У меня есть конфета';
3 s.Println;
4 s.Insert(12, 'вкусная ').Println;
5 Insert('вкусная ', s, 13);
6 s.Print;
У меня есть конфета
У меня есть вкусная конфета
У меня есть вкусная конфета

Замена подстроки

Часто бывает нужно заменить в строке один контекст на другой. Например, поменять имя переменной в выражении. Метод s.Replace(s1, s2) возвращает строку, полученную из исходной строки s заменой всех вхождений подстроки s1 на подстроку s2.

1 ##
2 var s := 'Sin(2*x-1)*Sqr(Cos(x+5))+0.4*x**3';
3 s.Replace('x', 'УголНаклона').Print
Sin(2*УголНаклона-1)*Sqr(Cos(УголНаклона+5))+0.4*УголНаклона**3

Разновидность s.Replace(s1, s2, k) возвращает строку, полученную из исходной строки s заменой k первых вхождений подстроки s1 на подстроку s2.

Более сложные замены можно осуществить на основе расширения .RegexReplace, использующего регулярные выражения.

Проверки в строке

В процессе работы со строкой может возникать потребность выполнить некоторые проверки ее содержимого. PascalABC.NET предоставляет для этой цели достаточное количество средств и мы рассмотрим лишь часть из них. Результат проверки всегда имеет тип boolean и равен True в случае ее успешности.

  • s.StartWith(ss) проверяет, начинается ли строка s с подстроки ss;
  • s.EndWith(ss) проверяет, заканчивается ли строка s подстрокой ss;
  • s.Contains(ss) проверяет, содержит ли строка s подстроку ss;
  • s.InRange(s1, s2) проверяет, находится ли строка s между строками s1 и s2, т.е. соблюдается ли условие s1 ⩽ s ⩽ s2.

Разбиение строки на слова

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

s.ToWords(cc)

Расширение s.ToWords(cc) возвращает массив слов, полученных разбиением строки s; при этом в качестве разделителей слов могут использоваться один или более символов, перечисленных в сс через запятую, либо содержащихся в массиве символов сс. Параметр сс можно не указывать, тогда разделителями слов считаются пробельные символы.

Дана строка, содержашая слова, разделенные произвольным количеством пробелов. Вывести те из слов, у которых первая буква совпадает с последней.
1 ## ' баркас табурет олово собака магазин астра и'.ToWords.Where(t -> t[1] = t[^1]).Print;

Расширение .ToWords формирует массив, каждый элемент которого содержит слово, полученное из заданной строки. Элементы массива проходят фильтрацию по условию t[1] = t[^1]. Очередное слово t – это строка, поэтому можно обращаться к ее отдельным символам, как к элементам массива, индексированным от единицы. Тогда t[1] – первая буква в слове, а t[^1] – последняя (вспоминаем, что символ ^ перед значением индекса обозначает отсчет с конца).

Сцепление (слияние) строк

Слияние строк – операция, обратная разбиению строки. Несколько строк сливаются в одну общую. При этом в месте слияния может находиться символ-разделитель, подстрока из нескольких разделителей или не находиться ничего.

  • Concat(s1, s2, …) – функция, возвращающая строку, которая является сцеплением (конкатенацией) строк s1, s2, … без использования разделителей. Часто заменяется операцией «+»;
  • string.Join(ss, del) – статический метод, возвращающий строку, полученную сцеплением подстрок, находящихся в массиве ss. Подстрока del используется в качестве разделителя;
  • ss.JoinToString(del) – расширение, возвращающее строку, полученную сцеплением подстрок, находящихся в массиве или последовательности ss. Подстрока del используется в качестве разделителя; по умолчанию используется символ пробела.

Поиск в строке

Поиск в прямом направлении

Pos(ss, s);
Pos(ss, s, from);

Функция Pos(ss, s) возвращает номер позиции первого вхождения подстроки ss в строку s. Если подстрока не найдена, возвращается ноль. Функция Pos(ss, s, from) делает то же самое, но поиск начинается не от начала строки, а с позиции from. Это дань базовому Паскалю.

s.IndexOf(ss);
s.IndexOf(ss, from);

Метод s.IndexOf(ss) возвращает индекс первого вхождения подстроки ss в строку s. Если подстрока не найдена, возвращается -1. Метод s.IndexOf(ss, from) делает то же самое, начиная поиск с символа, имеющего индекс from. Метод s.IndexOf(ss, from, k) распространяет поиск только на первые k символов, начиная с позиции from.

s.IndexOfAny(cc)

Метод s.IndexOfAny(cc) возвращает индекс первого вхождения в строку s любого символа из массива cc. Если ни один из символов не найден, возвращает -1. Метод может быть полезен, например, при поиске знаков препинания или начала числа в строке.

1 ##
2 var s := 'Самое синее в мире Черное море мое';
3 // 1234567890123456789012345678901234
4 Pos('мо', s).Print; // первое вхождение
5 Pos('мо', s, Pos('мо', s) + 1).Println; // второе вхождение
6 s.IndexOf('мо').Print; // первое вхождение
7 s.IndexOf('мо', s.IndexOf('мо') + 1).Println; // второе вхождение
8 s.IndexOfAny(|'и', 'е'|).Print;
3 27
2 26
4 

Поиск в обратном направлении

LastPos(ss, s);
LastPos(ss, s, from);

Функция LastPos(ss, s) возвращает номер позиции последнего вхождения подстроки ss в строку s. Если подстрока не найдена, возвращается ноль. Функция LastPos(ss, s, from) делает то же самое, но поиск начинается с позиции from и ведется в обратном направлении. Фактически, значение from ограничивает поиск первыми from символами. Как и Pos, эта функция сохранена для обратной совместимости с базовым Паскалем.

s.LastIndexOf(ss);
s.LastIndexOf(ss, from);

Метод s.LastIndexOf(ss) возвращает индекс последнего вхождения подстроки ss в строку s. Если подстрока не найдена, возвращается -1. Метод s.LastIndexOf(ss, from) делает то же самое, начиная поиск с символа, имеющего индекс from. Метод s.LastIndexOf(ss, from, k) распространяет поиск только на последние k из from символов.

s.LastIndexOfAny(cc)

Метод s.LastIndexOfAny(cc) возвращает индекс последнего вхождения в строку s любого символа из массива cc. Если ни один из символов не найден, возвращает -1.

1 ##
2 var s := 'Самое синее в мире Черное море мое';
3 // 1234567890123456789012345678901234
4 LastPos('мо', s).Print; // последнее вхождение
5 LastPos('мо', s, LastPos('мо', s) - 1).Println; // предпоследнее
6 s.LastIndexOf('мо').Print; // последнее вхождение
7 s.LastIndexOf('мо', s.LastIndexOf('мо') - 1).Println;
8 s.LastIndexOfAny(|'и', 'е'|).Print
32 27
31 26
33

Быстрый поиск всех вхождений подстроки

s.IndicesOf(ss)

Метод s.IndicesOf(ss) возвращает последовательность индексов всех вхождений подстроки ss в строку s.

1 ##
2 var s := 'Самое синее в мире Черное море мое';
3 //        0123456789012345678901234567890123
4 s.IndicesOf('мо').Print;
2 26 31

По умолчанию подстроки не могут перекрываться. Если требуется выполнить поиск с учетом перекрытий подстрок, метод вызывается в виде s.IndicesOf(ss, True).

1 ##
2 var s := 'aabaabaaaabaabaaab';
3 //        012345678901234567
4 s.IndicesOf('aabaa', False).Println;
5 s.IndicesOf('aabaa', True).Print;
0 8
0 3 8 11

Этот метод основан на алгоритме Кнута – Морриса – Пратта (КМП-алгоритм) и имеет высокую скорость работы для строк очень большой длины. Для поиска всех вхождений подстроки без перекрытия в строках относительно небольшого размера эффективнее может оказаться поиск на основе регулярного выражения.

Регулярные выражения

Термин регулярное выражение (далее по тексту РВ) отражает свойство математических выражений, называемое регулярностью. Желающие разобраться в том, что такое регулярность, могут обратиться к специальной литературе. Часто можно также встретить английская аббревиатуру RegExp (Regular Expressions).

Внешне РВ представляет собой некоторую строку-шаблон, с которой сопоставляется обрабатываемый текст. Результатом сопоставления будут подстроки, удовлетворяющие РВ. На основании содержимого этих подстрок делается вывод о наличии или отсутствии или чего-либо в тексте, осуществляется выборка подстрок или их замена.

РВ состоит из метасимволов – специальных символьных комбинаций и литералов – всех прочих символов. Метасимволы объясняют, как интерпретировать литералы в РВ для выполнения поиска в заданной строке. При работе с РВ учитывается набор значений параметров, определяющих специфику поиска, например, нужно ли учитывать регистр символов, останавливаться после первого успешного поиска или продолжать его и т.д. Эти параметры могут задаваться как в самом РВ, так и в операторе языка, использующего РВ.

Некоторые метасимволы

Re1.png

Некоторые квантификаторы

Re2.png

Экранирование

Чтобы в шаблоне указать символ, который используется в РВ для служебных целей, перед ним записывают символ экранирования – обратную косую черту «\». К служебным относятся символы «+», «\», «*», «?», «|», «{», «[», «(», «)», «^», «$», «.», «#». Также, нужно экранировать обычную косую черту «/».

Пример шаблона с экранирующими символами:

\(\d{3}\) – трехзначное число в круглых скобках

Директивы нулевой длины

Это не реальные символы, а своего рода условия, выполнение которых проверяется.

/b – граница слова. Понимается как место, где соседствуют символы /w и /W
^ - начало строки
$ - конец строки

Примеры шаблонов

Любому целому числу без знака удовлетворяет шаблон

\d+

Любому целому числу, которое может иметь знак, удовлетворяет шаблон

[+-]?\d+

Дата в формате дд/мм/гггг может быть выделена при помощи шаблона

\d\d\/\d\d\/\d{4}

Номер автомобиля вида <буква><3 цифры><две буквы> можно найти по шаблону

\w\d{3}\w\w

Номер телефона в России вида +7(ddd)ddd-dd-dd отыщет шаблон вида

+7\(\d{3}\)\d{3}-\d\d-\d\d

Слово ищется посредством шаблона

\b\S+\b

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

\b(\S)\S*\1\b

Слова, содержащие только русские буквы, удовлетворяют шаблону

\b[А-Яа-яЁе]+\b

Наличие подстроки в строке

Расширение s.IsMatch(reg, opt) проверяет, удовлетворяет ли строка s регулярному выражению reg. С помощью opt можно задавать дополнительные опции.

В примере рассматривается строка «Роза увяла от мороза», в которой ищется подстрока «роза». Если учитывать регистр, то такая подстрока одна, без учета регистра их две. В последней строке опция RegexOptions.IgnoreCase означает требование игнорировать регистр букв.

1 ##
2 var s := 'Роза увяла от мороза';
3 s.IsMatch('роза').Println; // True
4 s.IsMatch('роза\s').Println; // False
5 s.IsMatch('Роза\s').Println; // True
6 s.IsMatch('роза\s', RegexOptions.IgnoreCase).Println; // True

Результаты поиска в элементах

Расширение s.Matches(reg, opt) возвращает последовательность элементов типа Match из строки s, соответствующих регулярному выражению reg. С помощью opt можно задавать дополнительные опции.

Если m – объект типа Match, три его свойства могут оказаться особенно полезными: m.Value – найденная подстрока, m.Length – ее длина, m.Index – индекс ее первого элемента.

Замена всех подстрок в строке

Расширение s.RegexReplace(reg, ss, opt) возвращает строку, полученную заменами в строке s всех найденных вхождений подстроки, удовлетворяющей регулярному выражению, подстрокой ss. С помощью opt можно задавать дополнительные опции.

1 ##
2 var s := 'Мама мыла раму, Маша ела кашу';
3 s.RegexReplace('М','Д').Println;
4 s:='23*x-Sin(x)';
5 s.RegexReplace('x','(x+1.5)').Println;
6 s.RegexReplace('\-Sin\(x\)','').Print
Дама мыла раму, Даша ела кашу
23*(x+1.5)-Sin((x+1.5))
23*x

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

Вторая форма расширения s.RegexReplace(reg, Match -> string, opt) отличается тем, что найденные подстроки заменяются их преобразованием, заданным лямбда-выражением.

1 ##
2 var s := 'Мама мыла раму';
3 s.RegexReplace('.а', m -> UpperCase(m.Value)).Println;
4 s.RegexReplace('.а', m -> m.Index.ToString).Println;
5 s := 'А роза упала на лапу Азора';
6 s.RegexReplace('\w+', m -> m.Length.ToString).Println;
7 s := ' тестовая строчка ';
8 s.RegexReplace('\s+', m -> m.Length.ToString).Print;
МАМА мыЛА РАму
02 мы7 10му
1 4 5 2 4 5
7тестовая2строчка3

Поиск первого вхождения подстроки

Расширение s.MatchValue(reg, opt) возвращает первую из подстрок в строке s, соответствующую регулярному выражению reg. С помощью opt можно задавать дополнительные опции. В отличии от расширения .IsMatch возвращает не логическое значение, показывающее успешность поиска, а подстроку – значение поля Value первого из элементов типа Match.

1 ##
2 var s := 'Роза увяла от мороза';
3 s.MatchValue('роза').Println;
4 s.MatchValue('роза\s').Println;
5 s.MatchValue('Роза\s').Println;
6 s.MatchValue('роза\s', RegexOptions.IgnoreCase).Println;
7 s.MatchValue('(?i)роза').Print // тоже без учета регистра
роза 
Роза
Роза
Роза

Поиск всех вхождений подстроки

Расширение s.MatchValues(reg, opt) возвращает последовательность подстрок в строке s, соответствующую регулярному выражению reg. С помощью opt можно задавать дополнительные опции. Практически, в качестве результата возвращается последовательность значений поля Value из всех найденных элементов Matches.

1 ##
2 var s := 'Роза увяла от мороза';
3 s.MatchValues('роза').Println;
4 s.MatchValues('роза\s').Println;
5 s.MatchValues('Роза\s').Println;
6 s.MatchValues('роза\s', RegexOptions.IgnoreCase).Println;
7 s.MatchValues('(?i)роза').Print
роза
Роза
Роза
Роза роза

Извлечение данных из строк

Выше рассматривалось разбиение строки на подстроки, в частности, на слова. При этом каждая подстрока получала такой же тип, как и исходная строка – string. Рассмотрим извлечение подстрок с преобразованием их типа.

Преобразование числа к строке

Числа преобразуют к строковому представлению для формирования строк вывода, для анализа цифр в числе. Это могут быть задачи нахождения числа цифр, поиска одинаковых цифр, получения перестановок цифр, для передачи данных в виде символьной строки и другие задачи. Метод n.ToString позволяет преобразовать к строке число любого типа.

Сколько раз встретится цифра 3 в записи целых чисел, принадлежащих отрезку [-4567; 24674]?
1 ## (-4567..24674).Select(d -> d.ToString.Count(c -> c = '3')).Sum.Print;

Преобразование строки к числу

Преобразовать число к строке несложно, а с обратным преобразованием дела обстоят совсем не так. Строка может целиком представлять одно число, а может содержать в подстроках представления нескольких чисел и даже не всегда одного типа, отделенных друг от друга каким-либо разделителем или группой разделителей. Если все подстроки одного типа, то строка может быть преобразована к массиву чисел. Наконец, строка может содержать некорректные представления одного и более чисел.

Преобразование строки к целому числу

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

Статический метод T.TryParse(s, n) пытается преобразовать строку s к целому числу n типа T. Если преобразование успешно, значение помещается в переменную n и возвращается True. В противном случае значение n не изменяется и возвращается False.

Организовать ввод целого числа с приглашением ко вводу и выдачей сообщения в случае, если при вводе были допущены ошибки.
 1 ##
 2 var n: integer;
 3 while True do
 4 begin
 5  var s := ReadlnString('Введите целое число:');
 6  if integer.TryParse(s, n) then
 7  break
 8  else
 9  Writeln('Неверный ввод, повторите');
10 end;
11 Print('Число введено корректно');
Введите целое число: 345345y345
Неверный ввод, повторите
Введите целое число: 3245345
Число введено корректно
Нахождение суммы всех цифр, встретившихся в строке. 
1 ## ReadString.MatchValues('\d').Select(t -> t.ToInteger).Sum.Print;

Преобразование строки к вещественному числу

Статический метод real.TryParse(s, n) пытается преобразовать строку s к целому числу n типа real. Если преобразование успешно, значение помещается в переменную n и возвращается True. В противном случае значение n не изменяется и возвращается False.

Расширение s.ToReal возвращает содержимое сроки s, преобразованное к значению типа real. В случае ошибки выполнение программы будет аварийно завершено.

Преобразование строки к массиву чисел

Расширения s.ToIntegers и s.ToReals возвращают динамический массив чисел целого или вещественного типа, полученный на основе содержимого строки s. Элементы в строке должны разделяться не менее чем одним пробельным символом. В случае ошибки выполнение программы завершается аварийно.

Дана строка в которой через один или более пробелов записаны натуральные целые числа, не превышающие 10 млн. Какой процент от их общего количества составляют четные числа?
1 ##
2 var s := ' 840005 1261 401 5060637 774 47216 5667899 8 ';
3 var a := s.ToIntegers; // получаем массив чисел
4 Write(a.Count(t -> t.IsEven) / a.Length * 100:0:1, '%');
37.5%

Преобразование строки в массив

Строку можно преобразовать в массив символов. Метод s.ToCharArray возвращает динамический массив типа char, полученный путем посимвольного разбиения исходной строки s. Обратное преобразование из массива или последовательности символов в строку выполняется при помощи расширения .JoinToString.

Пусть требуется выбрать из заданной строки все символы по одному разу, отсортировать их по возрастанию и заменить полученной строкой исходную.
1 ##
2 var s := 'наша тестовая строка';
3 s := s.ToCharArray.Distinct.Sorted.JoinToString;
4 s.Print
авекнорстшя

Форматирование данных для вывода

Для оформления выводимых данных в языке Паскаль традиционно используется процедуры Write и Writeln, в которых для выводимого элемента данных может указываться минимальная ширина поля вывода w, а для вещественных значений – еще и количество цифр в дробной части t. Разделителем служит символ «двоеточие» (:). Если параметр w не указан или имеет недостаточную для отображения выводимого значения величину, значение выводится со своей фактической длиной. Если для вещественного числа параметр t не указан, в дробной части выводится фактическое количество цифр, но при этом общее количество цифр не может превышать 15. Если в дробной части больше цифр, чем указано в t, выводится t цифр и при этом делается округление. При указании значения t, требующего в выводимом числе отобразить более 15 цифр, все цифры после пятнадцатой будут представлены нулями.

 1 ##
 2 var (a, b) := (-23.4723423290834, 16554);
 3 Writeln(a, ' ', b, ' ', a * b);
 4 Writeln(a, ' ', b, ' ', a * b:0:5);
 5 Writeln(a, ' ', b, ' ', a * b:20);
 6 Writeln(a, ' ', b, ' ', a * b:25:5);
 7 Writeln(a, ' ', b, ' ', a * b:0:20);
 8 Writeln('Тестовая строка', ' ', '*');
 9 Writeln('Тестовая строка':10, ' ', '*');
10 Writeln('Тестовая строка':30, ' ', '*');

Обратите внимание на использование нуля при задании ширины поля вывода w. Это типичный прием вывода вещественных значений в Паскале, когда неизвестно количество цифр в целой части числа. -23.4723423290834 16554 -388561.154915647 -23.4723423290834 16554 -388561.15492 -23.4723423290834 16554 -388561.154915647 -23.4723423290834 16554 -388561.15492 -23.4723423290834 16554 -388561.15491564700000000000 Тестовая строка * Тестовая строка *

              Тестовая строка *

Выравнивание строки пробелами

Чтобы строка s длиной L имела нужную ширину w, можно дополнить ее w-L пробелами слева или справа. Например, вот так:

1 s := ' ' * (w - s.Length) + s;
2 s := s + ' ' * (w - s.Length);

Писать подобным образом для каждого выводимого элемента неудобно. Поэтому в классе string введены два метода:

  • s.PadRight(w) возвращает строку s, дополненную пробелами справа до длины w
  • s.PadLeft(w) возвращает строку s, дополненную пробелами слева до длины w

Составное форматирование

Выравнивание строки пробелами решает проблему с выводом только символьных данных. Для произвольного типа данных есть общее решение – использование составного форматирования (composite format в терминологии Microsoft .NET Framework).

Составное форматирование использует в качестве исходных данных строку составного формата (шаблон, задающий правила преобразования данных) и список объектов, подлежащих форматированию. Этот список следует за строкой составного формата и отделяется от нее запятой. Элементы в списке нумеруются от нуля и разделяются запятыми. Строка составного формата может содержать произвольную комбинацию некоторого количества неизменяемого текста, представленного литералами, и элементов форматирования, каждый из которых заключается в фигурные скобки. Элемент форматирования является местозаполнителем (поле, в котором впоследствии будет размещено значение) и в простейшем случае содержит только отсчитываемый от нуля индекс – порядковый номер элемента, подлежащего форматированию, в списке.

1 ##
2 Format('Длина окружности диаметра {0} равна {1}', 2.16, 2.16 * Pi).Print;
Длина окружности диаметра 2.16 равна 6.78584013175395

В общем случае элемент форматирования имеет вид

{индекс, выравнивание : формат}

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

Целочисленные данные

Используется формат d или D (decimal), за которым может быть указано желаемое количество цифр.

1 ## Format('*{0,20:d}*{0,-15:D6}*{0:d1}*', -152).Print;
*                -152*-000152        *-152* 

Здесь трижды указан индекс 0, что позволило три раза использовать одно и то же значение -152 из списка. Числовое значение после d (D) определяет минимальное желаемое количество выводимых цифр, значение выравнивания перед двоеточием – общее количество позиций. Обратите внимание на появление незначащих нулей.

Вещественные данные с фиксированной точкой

Формат данных f или F определяет преобразование для отображения с фиксированной точкой (fixed), т.е. в виде целой и дробной части, разделенных точкой. По умолчанию в дробной части будут указаны две цифры с округлением значения.

1 ## Format('*{0,20:f}*{0,-15:f6}*{0:f1}*', -152.327).Print;
*             -152.33*-152.327000    *-152.3* 

Вещественные данные с плавающей точкой

Формат данных e или E определяет преобразование для отображения с плавающей точкой (exponential) – в виде одной цифры в целой части, нужного количества цифр в дробной части и показателя степени числа 10, отделенного буквой E. Этот формат обычно используется для величин, имеющих очень малые или очень большие значения. По умолчанию число знаков в дробной части равно шести.

1 ## Format('*{0,20:f}*{0,-15:f6}*{0:f1}*', -152.327).Print;
*             -152.33*-152.327000    *-152.3* 

Числовые данные в общем формате

Формат данных g или G определяет преобразование для данных любого числового типа. Наиболее подходящий формат (d, f, e) компилятор выбирает самостоятельно. Для отображения значений real с максимальной точностью используйте формат g17.

1 ##
2 Format('*{0,20:g}*{0,-15:G6}*{0:g1}*', -152).Println;
3 Format('*{0,20:g}*{0,-15:g6}*{0:g1}*', -152.327).Println;
4 Format('*{0,20:g}*{0,-15:G6}*{0:g1}*', -152.327e-31).Print;
*                -152*-152           *-2e+02* 
*            -152.327*-152.327       *-2e+02* 
*        -1.52327e-29*-1.52327E-29   *-2e-29* 

Формат для шестнадцатеричного представления

Формат x или X служит для преобразования целочисленных данных к их шестнадцатеричному представлению. По умолчанию отображается минимально необходимое количество цифр.

1 ##
2 Format('*{0,20:x}*{0,-15:X6}*{0:x1}*', 1023).Println;
3 Format('*{0,20:x}*{0,-15:X6}*{0:x1}*', -1023).Print;
*                 3ff*0003FF         *3ff* 
*            fffffc01*FFFFFC01       *fffffc01* 

Функция и статический метод Format

В приведенных выше примерах форматирование строк осуществлялось с использованием функции Format. Также можно использовать статическую функцию string.Format(cf, params), где cf – шаблон форматирования, params – список значений, подлежащих форматированию.

Процедура форматного вывода WriteFormat

По аналогии с процедурами Write и Writeln, позволяющими при выводе указывать ширину поля, в PascalABC.NET включены процедуры WriteFormat(сf, params) и WritelnFormat(сf, params), позволяющие осуществлять вывод с использованием составного форматирования. Список параметров params форматируется при помощи строки составного формата cf, а затем выводится. Процедура WritelnFormat затем осуществляет переход к новой строке вывода.

Интерполированные строки

Интерполированная строка – это объединение строки составного формата и списка объектов, подлежащих преобразованию в строковое представление. Она представляет собой литерал, содержащий интерполированные выражения, перед которым записан символ $. Интерполированное выражение очень похоже на элемент форматирования строки составного формата: оно так же заключается в фигурные скобки и содержит описание формата данных, только вместо индекса подлежащего форматированию выражения указывается само это выражение. Интерполированные строки обычно короче и нагляднее строк составного форматирования. Впрочем, сравните сами:

1 ##
2 Format('Длина окружности диаметра {0} равна {1}', 2.16, 2.16 * Pi)
3 $'Длина окружности диаметра {2.16} равна {2.16 * Pi}'
4 Format('*{0,20:d}*{0,-15:D6}*{0:d1}*', -152)
5 $'*{-152,20:d}*{-152,-15:D6}*{-152:d1}*'
6 Format('*{0,20:f}*{0,-15:f6}*{0:f1}*', -152.327)
7 $'*{-152.327,20:f}*{-152.327,-15:f6}*{-152.327:f1}*'
8 Format('Сумма квадратов катетов a={0} и b={1} равна {2}',a, b, a * a + b * b)
9 $'Сумма квадратов катетов a={a} и b={b} равна {a * a + b * b}'

Интерполированная строка – это строка типа string и с ней можно работать обычным образом.