Вводная часть
Здравствуйте. Гуляя по просторам интернета, не раз встречал задания вроде такого
Дана строка, содержащая текст на русском языке. Подсчитать количество слов, начинающихся и заканчивающихся на одну и ту же букву
Все бы очень просто, но люди начинают пытаться воспользоваться встроенными методами, вроде strtok и string.Split(). Но они вечно забывают, что существуют небольшие нюансы использования подобных функций/методов…
Симптоматика
Больной не понимает, что использование разделителей не самая хорошая идея, в лучшем случае, он создает массив разделителей, и пихает его во все вышеописанные методы, в худшем — он в каждом случае пишет отдельно что-то вроде
var a = str.Split(new[] {",","."}, StringSplitOptions.RemoveEmptyEntries)
соответственно при необходимости добавления еще одного символа требуется по всему коду лазить и исправлять эти Split’ы.
Лечение
Просьба использовать больше 50 символов-разделителей, тогда до больного начинает доходить необходимость в смене алгоритма.
Лекарство
В качестве лекарства мной был написан вот такой метод расширения:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace StrSplit { public static class Program { public static IEnumerable<string> MySplit(this string s) { var sb = new StringBuilder(); foreach (char c in s) if (char.IsLetter(c)) sb.Append(c); else { yield return sb.ToString(); sb.Length = 0; } } }
потом, посмотрев на встроенный StringSplit, решил добавить опцию, аналогичную StringSplitOptions.RemoveEmptyEntries
Итоговый вид метода
private static IEnumerable<string> MySplit(this string s, bool removeEmpty) { var sb = new StringBuilder(); foreach (char c in s) if (char.IsLetter(c)) sb.Append(c); else { if (sb.Length != 0 || !removeEmpty) yield return sb.ToString(); sb.Length = 0; } }
Принцип действия
В принципе, все элементарно, создается объект StringBuilder (особый вид string в C#, позволяющий безболезненно суммировать большое кол-во строк, особенности реализации на конкретном языке, поэтому не будем останавливаться на этом), после этого он заполняется буквами до тех пор, пока не встретится не-буква, то есть в этом и проявляется суть метода, он работает с любыми разделителями, и нет необходимости исправлять программу в случае изменения каких-либо внешних факторов, достаточно указать только полезную информацию (в данном случае — буквы), и все. Для большей универсальности в качестве еще одного параметра можно передавать делегат (указатель) на функцию, которая будет проводить сравнение (то есть какое условие определяет, полезная буква или нет, добавлять её в вывод или удалить). Ну если встретился не-символ тогда проверяем: если строка пустая и мы не хотим получать пустую строку (параметр removeEmpty == true), тогда просто обнуляем текущую строку иначе возвращаем полученный результат.
Небольшой аналог на C
typedef struct mystruct //Структура для хранения строк { char *data; //Тут храним строку int length; //Тут храним длину строки struct mystruct *next; //Тут храним указатель на следующий элемент списка } StringListNode; //Объявляем эту структуру под псевдонимом StringListNode int inline Contains(char *s, char c) { int i; for(i = 0; s[i]; i++) //Пока не встретился символ окончания строки if(s[i] == c) //Если символы совпадают (то есть в строке есть такой символ) return 1; //Возвращаем истину return 0; //Если дошли до конца строки и нам ни разу не встретилась похожая буква, значит их в строке нет, возвращаем ноль } StringListNode* AddToList(char *s, int length) { StringListNode *p = (StringListNode*)malloc(sizeof(StringListNode)); //Выделяем память под новый элемент списка p->length = length; //Сохраняем в этом элементе длину строки p->data = (char*)malloc(sizeof(char)*(length+1)); //Выделяем память в элементе под строку (кол-во выделяемой памяти = кол-во символов строки + 1 (для '\0'), умноженное на место, занимаемое одной буквой strcpy(p->data,s); //Помещаем в выделенную на предыдущем шаге область памяти строку (то есть сохраняем строку в элементе) return p; //Возвращаем элемент } StringListNode *StringSplit (char *s, char delims[]) { StringListNode *head, *tail, *p; //Указатель на голову списка, хвост списка и на новый элемент int i,j; //Счетчики char result[255]; //Буфер for(i = j = head = tail = 0; s[i]; i++) //Обнуляем все переменные, идем, пока s[i] != NULL, в короткой записи пока s[i] if (Contains(delims,s[i])) //Если это разделитель то { if (j == 0) continue; //Если полезной информации нет (длина строки, содержащей информацию, равна нулю) пропускаем result[j++] = '\0'; //Иначе дописываем в конец ноль p = AddToList(result,j); //Создаем новый элемент списка, куда помещается строка и информация о её длине if (head) //Если у нас список непустой { tail->next = p; //То в конец добавляем новый элемент tail = tail->next; //А этот новый элемент теперь сам является последним в списке } else tail = head = p; //Иначе это единственный элемент списка, поэтому голова и хвост указывают на него j = 0; //Строку обработали, обнулили счетчик текущей строки } else result[j++] = s[i]; //Иначе это не разделитель, а буква, значит добавляем в строку if (j) //Если после выхода из цикла у нас осталась полезная информация (длина строки, содержащей информацию, не равна нулю), то также добавляем её в список { result[j++] = '\0'; tail->next = AddToList(result,j); tail = tail->next; } tail->next = NULL; //После последнего элемента никаких элементов нет, поэтому обнуляем поле next хвостового элемента return head; //Возвращаем список }
Код максимально подробно прокомментирован. Обычно, комментирование каждой строки является плохим тоном, так как загромождает программу ненужным текстом, но мое мнение таково, что в данном случае это оправданно. Функция Contains уже имеется в том или ином виде в string.h, но когда этот код писался, я об этом еще не знал (я же тоже учусь на своих ошибках). Почему не исправлено, см.ниже.
P.S.
Кто-то может начать кидаться тухлыми помидорами и кричать, что регэкспы (регулярные выражения, выражаясь русским языком) все это разрулят за 5 секунд: да, конечно, но где тогда интерес?
P.P.S.
Не претендую на абсолютную правильность вышеописанных кодов, так как я считаю, что важна не реализация, а алгоритмическая модель данной задачи, но надеюсь, что читатель сам при необходимости сможет его исправить/дополнить. Удачи вам в ваших исследованиях!
ссылка на оригинал статьи http://habrahabr.ru/post/164915/
Добавить комментарий