Рацеев С.М. Программирование на языке Си
.pdfПример 8. Дана некоторая строка s. Определить сколько различных символов встречается в строке s, не рассматривая символ
‘\0’.
Опять же для большей скорости решения данной задачи применим дополнительный массив flag, как и в двух предыдущих задачах.
int Count(char *s)
{
int i, n, flag[256] = {0}; for(i = 0; s[i]; i++)
flag[s[i]] = 1;
for(i = n = 0; i < 256; i++) if (flag[i])
n++; return n;
}
Пример 9. Дана некоторая строка s. Проверить, все ли символы данной строки попарно различны.
int Check(char *s)
{
int i, flag = 1, count[256] = {0}; for(i = 0; s[i] && flag; i++)
{
count[s[i]]++;
if (count[s[i]] > 1) flag = 0;
}
return flag;
}
Заметим, что возможности языка Си позволяют записать функцию Count() в более компактной и оптимальной форме:
int Check(char *s)
{
131
int i, count[256] = {0};
for(i = 0; s[i] && ++count[s[i]] < 2; i++)
;
return s[i] == '\0';
}
Пример 10. Дана некоторая строка s. Выделить в данной строке все целые неотрицательные числа и найти их сумму.
Для решения данной задачи для разнообразия используем динамическую строку digit, куда будем записывать очередное выделенное число из строки s.
#include <stdio.h> #include <stdlib.h> #include <string.h> #define N 1024
/* функция возвращает сумму целых чисел из строки s */ int Sum(char *s)
{
int i, j, sum;
char *digit = NULL; sum = 0;
/* пропускаем все символы, не являющиеся цифрами: */ for(i = 0; s[i] && !isdigit(s[i]); i++)
i++; while (s[i])
{
j = i; /* запоминаем позицию начала очередного числа */ /* пробегаем по всем подряд идущим цифрам, входящим в
состав очередного числа:
*/
while (s[i] && isdigit(s[i])) i++;
/* выделяем память для динамической строки digit: */ digit = (char *)malloc((i - j + 1) * sizeof(char));
strncpy(digit, &s[j], i - j); /* копируем цифры в строку digit */ digit[i - j] = '\0';
132
sum += atoi(digit); /* прибавляем очередное число к sum*/ free(digit); /* освобождаем память */
digit = NULL;
/* пропускаем все символы, не являющиеся цифрами: */ while (s[i] && !isdigit(s[i]))
i++;
}
return sum;
}
int main( )
{
char s[N]; fgets(s, N, stdin);
printf("sum = %d\n", Sum(s)); return 0;
}
Пример 11. Пусть SET – некоторое множество символов из таблицы символов ASCII, не содержащее символ ‘\0’. Требуется в строке s удалить все символы, принадлежащие множеству SET.
Пусть, например, SET = {‘A’, ‘B’, ‘a’, ‘b’, ‘!’}. Приведем очень эффективный алгоритм решения данной задачи. Пусть int flag[256] – логический массив, каждый индекс которого соответствует определенному символу из таблицы символов ASCII. Все элементы массива flag, индексы которых соответствуют элементам множества SET, определим единицами, а все остальные элементы – 0. Для написания алгоритма учтем пример 5 пункта 5.2 и замечание к примеру 4 данного пункта.
#define SET "ABab!"
void Del(char *s)
{
int i, j, flag[256] = {0};
/* Все элементы массива flag, индексы которых соответствуют элементам множества SET, определяем единицами:
133
*/
for(i = 0; SET[i]; i++) flag[SET[i]] = 1;
/* Пробегаем все символы строки, не принадлежащие множеству SET:
*/
for(j = 0; s[j] && !flag[s[j]]; j++)
;
/* Переставляем на новые позиции все символы в строке s, не принадлежащие множеству SET:
*/
for(i = j; s[i]; i++) if (!flag[s[i]])
s[j++] = s[i]; s[j] = '\0';
}
Пример 12. Пусть s и t – некоторые строки. Требуется написать функцию, которая определяет количество символов, одновременно входящих в обе данные строки без учета символа ‘\0’.
Сложность приведенного ниже алгоритма равна length(s)+length(t).
int Count(char *s, char *t)
{
int i, n, flag[256] = {0}; for(i = n = 0; s[i]; i++)
flag[s[i]] = 1; for(i = 0; t[i]; i++) if (flag[t[i]])
n++; return n;
}
Пример 13. Пусть имеются строки SET_A, SET_B и s. Требуется проверить, чередуются ли в строке s элементы, принадлежащие строкам SET_A и SET_B. Например, если SET_A – строка, со-
134
стоящая из латинских букв, а SET_B – строка, состоящая из цифр, то требуется проверить, чередуются ли в строке s буквы и цифры. Или если SET_A – строка, состоящая из гласных латинских букв, SET_B – строка, состоящая из согласных латинских букв, то требуется проверить, чередуются ли в строке s гласные и согласные латинские буквы.
Рассмотрим очень быстрый и оптимальный алгоритм решения данной задачи, не использующий функцию strchr(), которая, как было отмечено выше, будет тормозить работу алгоритма. Заведем два целочисленных массива flag_a и flag_b, каждый из которых состоит из 256 элементов (количество элементов из таблицы символов ASCII), и пусть i-ый элемент массива flag_a (flag_b) соответствует i-ому элементу из таблицы символов. Напротив каждого элемента в массиве flag_a, которые входят в строку SET_A, выставим флаг, равный 1, а все остальные элементы в массиве flag_a определим нулевыми значениями:
int flag_a[256] = {0}; for(i = 0; SET_A[i]; i++)
flag_a[SET_A[i]] = 1;
Точно так же поступим и с массивом flag_b. После этого очень легко и, самое главное, очень быстро можно проверить, принадлежит ли один из символов s[i-1] и s[i] строки s строке SET_A, а другой – строке SET_B:
if (flag_a[s[i-1]] && flag_b[s[i]] || flag_b[s[i-1]] && flag_a[s[i]])
Алгоритм решения данной задачи теперь будет иметь такой вид:
#define SET_A "ABCD" #define SET_B "xyz"
int Check(char *s)
{
int i, flag_a[256] = {0}, flag_b[256] ={0}; if (s[0] == 0)
return 1;
135
for(i = 0; SET_A[i]; i++) flag_a[SET_A[i]] = 1; for(i = 0; SET_B[i]; i++) flag_b[SET_B[i]] = 1;
i = 1;
while(s[i] && (flag_a[s[i-1]] && flag_b[s[i]] || flag_b[s[i-1]] && flag_a[s[i]])) i++;
return s[i] == '\0';
}
Рассмотрим также частный случай данной задачи. Пусть требуется проверить, чередуются ли в строке s гласные и согласные латинские буквы. В этом случае, конечно, можно в строку SET_A записать все гласные латинские буквы, а в строку SET_B – согласные и применить функцию Check(), но все же в этом случае алгоритм можно упростить и немного ускорить.
Пусть VOWEL – строка, содержащая все гласные латинские буквы, а flag[256] – массив, в котором каждой гласной букве соответствует единица, а всем остальным символам – 0. Поэтому если символы s[i-1] и s[i] являются латинскими буквами и одна из них гласная, а другая согласная, то flag[s[i-1]]+flag[s[i]]==1. Используем это свойство в следующем алгоритме:
#define VOWEL "AEIOUYaeiouy"
int Check(char *s)
{
int i, flag[256] = {0}; if (s[0] == 0)
return 1;
if (!isalpha(s[0])) return 0;
for(i = 0; VOWEL[i]; i++) flag[VOWEL[i]] = 1;
i = 1;
while(s[i] && isalpha(s[i]) && (flag[s[i-1]] + flag[s[i]] == 1)) i++;
return s[i] == '\0';
136
}
Пример 14. Пусть s – некоторая строка, c – некоторый символ. Требуется в строке s каждую серию подряд идущих символов c заменить одним символом c.
void Zamena(char *s, char c)
{
int i, j, n; i = j = 0; while(s[i])
{
n = 0; /* количество подряд идущих символов c */ while (s[i] && s[i] == c)
{
i++;
n++;
}
if (n > 0) s[j++] = c;
while (s[i] && s[i] != c) s[j++] = s[i++];
}
s[j] = '\0';
}
int main( )
{
char s[1024]; fgets(s, 1024, stdin); Zamena(s, 'a'); puts(s);
return 0;
}
Пример 15. В строке s заменить каждую серию подряд идущих одинаковых символов на один символ данной серии. Например, строку s=”aabbbccd” необходимо преобразовать в s=”abcd”.
137
void Zamena(char *s)
{
int i, j;
if (s[0] == '\0') return;
for (i = j = 1; s[i]; i++) if (s[i] != s[i-1]) s[j++] = s[i];
s[j] = '\0';
}
Пример 16. Из строки s удалить все слова, имеющие в своем составе хотя бы одну цифру.
#include <stdio.h> #include <string.h> #include <ctype.h> #define DEL " .,:;\n\t" #define N 1024
/* проверка, присутствует ли в массиве s хотя бы одна цифра: */ int Check(char *s, int len)
{
int i;
for(i = 0; i < len && !isdigit(s[i]); i++)
;
return i >= len;
}
int main( )
{
char s[N]; int i, j, k, len;
fgets(s, N, stdin); i = k = 0;
while (s[i])
{
138
while (s[i] && strchr(DEL, s[i])) s[k++] = s[i++];
j = i;
while (s[i] && !strchr(DEL, s[i])) i++;
len = i - j;
if (Check(s + j, len))
{
strncpy(s + k, s + j, len); k += len;
}
}
s[k] = '\0'; puts(s); return 0;
}
8.7. Разбиение строки на лексемы
Рассмотрим задачу выделения всех слов в строке-предложе- нии. Обозначим через sentence исходную строку-предложение, в которой требуется выделить все слова, а через word – очередное выделенное слово в строке sentence.
Возможен случай, когда переменная sentence является массивом символов, например:
char sentence[1024];
Также возможен случай, когда переменная sentence является указателем на строку:
char *sentence;
То же самое можно сказать и о переменной word. Данные случаи исходят из той или иной задачи работы со строками.
Случай 1. Рассмотрим такой случай: пусть переменная sentence является массивом символов и некоторые ее элементы, например, символы-разделители разрешается заменить на символ '\0', а переменная word является указателем на строку. Составим программу, которая будет заменять первый символ из каждой се-
139
рии подряд идущих символов-разделителей на '\0', а указатель word будет каждый раз содержать адрес первого символа очередной лексемы в предложении sentence.
Для эффективной реализации данного алгоритма введем логический массив int flag[256], в котором порядковые номера (индексы) элементов соответствуют кодам символов из таблицы символов ASCII. Элементы данного массива инициализируем следующим образом: если символ с кодом i является символомразделителем, то полагаем flag[i] = 1, иначе flag[i] = 0. И так для всех символов из таблицы символов ASCII (i = 0, 1, …, 255):
int flag[256] = {0};
for (i = 0; DELIMITERS[i]; i++) flag[DELIMITERS[i]] = 1;
После данного цикла массив flag будет иметь такой вид:
Теперь проверка, является ли i-й символ строки sentence символомразделителем, будет выглядеть следующим образом:
if (flag[sentence[i]])
Ниже приводится очень эффективный алгоритм выделения слов из строки sentence. Заметим, что сложность данного алгоритма равна m+n, где m – длина строки DELIMITERS, n – длина строки sentence, при этом данный метод разбиения строки на слова работает в разы быстрее чем функция strtok().
#include <stdio.h>
#define DELIMITERS " .,:;?!\n\t" /* символы-разделители */ #define N 1024
int main( )
{
char sentence[N]; /* исходная строка */
char *word; /* адрес начала очередного слова в предложении */
140