#c_sharp #регулярные_выражения
Задача В C#-программе получить из текстового файла телефонные коды городов и их названия с помощью регулярного выражения. Для простоты в данной задаче будем брать только названия городов, состоящие из одного слова без дефиса. Логично будет, если в файле данных сначала шло бы название города, а потом код, но я по определённым причинам сделал наоборот. В идеале, если в файле данных строка будет начинаться с кода города, а потом через один пробел будет идти его название, но идеология юзабилити требует адаптации к криворуким пользователям, потому регулярное выражение должно допускать пробелы до кода, лишние пробелы после него, а также лишние пробелы после названия города. Недопустимо лишь отсутствие пробела между кодом и городом. Файл данных 39032 Абакан 39042 Саяногорск 39031 Черногорск 39036Копьево 42722 Анадыр ь 81831145 Березник 81 856 Карпогоры Составление регулярного выражения ## На Regex Storm .NET с регулярным выражением ^\s*(\d{4,5})\s+(\w)+\s*$ и опцией Multiline я получил 3 совпадения (первые три города), а остальные 4 паттерну не соответствуют: Копьево - Отсутствует пробел между кодом и именем Анадырь - Имеется ошибочный пробел в имени (повторюсь, что в этом вопросе мы рассматриваем только имена собственные, состоящие из одного слова). Березинск - Слишком много цифер в коде (несоответствие стандарту) Карпогоры - Пробел в номере Программная реализация Как я узнал, в C# режим Multiline является режимом по умолчанию. С точки зрения логики, в паттерне нужно указать начало и конец строки. Тем не менее с паттерном string pattern = @"^\s*(\d{4,5})\s*(\w+)\s$"; в программе я не получил ни одного совпадения. С паттерном @"^\s*(\d{4,5})\s*(\w+)\s" (без указания конца строки) я получил единственное совпадение: пустой код города и имя первого города. Наконец с паттерном @"\s*(\d{4,5})\s*(\w+)\s" (без указания начала и конца строки) я получил невалидные данные: Код первого города - пустой. Копьево, Анадырь - получено несмотря на невалидность Березинск: Получен код города 81831; следующие три цифры зачтены как название города. Как следует исправить код, чтобы совпадения были те же, что и на Regex Storm? class Program { static void Main(string[] args) { // Указание пути к файлу данных String currentProjectPath = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..\\..\\")).ToString(); String dataFolderPath = currentProjectPath + "/Data/"; String dataFileNameWithExtension = "Data.txt"; String fullPathToDataFile = dataFolderPath + dataFileNameWithExtension; // Чтение файла string[] lines = System.IO.File.ReadAllLines(fullPathToDataFile); string fileContains = ""; foreach (string line in lines) { fileContains = fileContains + line + "\n"; } // Применение регулярного выражения string pattern = @"\s*(\d{4,5})\s*(\w+)\s"; MatchCollection matches = Regex.Matches(fileContains, pattern); foreach (Match match in matches) { Console.WriteLine("Код города:"+ match.Groups[1].Value); Console.WriteLine("Имя города:" + match.Groups[2].Value); } } } Если для ответа на данный вопрос Вам нужно поэкспериментировать с кодом, то в целях экономии Вашего времени я подготовил проект для Visual Studio (ссылка на Яндекс Диск; возможно станет недоступной после получения ответа на вопрос).
Ответы
Ответ 1
Ваша изначальная регулярка неверна, она возвращает код города и последовательность по одной букве: Правильная регулярка: ^\s*(\d{4,5})\s+(\w+)\s*$: Рабочий код: var lines = @"39032 Абакан 39042 Саяногорск 39031 Черногорск 39036Копьево 42722 Анадыр ь 81831145 Березник 81 856 Карпогоры"; string pattern = @"^\s*(\d{4,5})\s+(\w+)\s*$"; MatchCollection matches = Regex.Matches(lines, pattern, RegexOptions.Multiline); foreach (Match match in matches) { Console.WriteLine("Код города:" + match.Groups[1].Value); Console.WriteLine("Имя города:" + match.Groups[2].Value); } Обратите внимание, на параметр RegexOptions.Multiline, он: Изменяет ^ и $ так, чтобы они соответствовали началу/концу строки текста, а не началу/концу всей строки регулярного выражения PS: На сколько я понял .NET Regex Tester как раз работает на движке регулярных выражений .NET, поэтому раз уж вы включили там галочку Multiline, то должны и в своем коде указать соответствующий флагОтвет 2
var lines = @"39032 Абакан 39042 Саяногорск 39031 Черногорск 39036Копьево 42722 Анадыр ь 81831145 Березник 81 856 Карпогоры"; lines = lines.Replace(" ", ""); string pattern = @"(\d{4,5})\d*(\w+)"; MatchCollection matches = Regex.Matches(lines, pattern); foreach (Match match in matches) { Console.WriteLine("Код города:" + match.Groups[1].Value); Console.WriteLine("Имя города:" + match.Groups[2].Value); } Код города:39032 Имя города:Абакан Код города:39042 Имя города:Саяногорск Код города:39031 Имя города:Черногорск Код города:39036 Имя города:Копьево Код города:42722 Имя города:Анадырь Код города:81831 Имя города:Березник Код города:81856 Имя города:КарпогорыОтвет 3
Ваша изначальная регулярка, ^\s*(\d{4,5})\s+(\w)+\s*$, верна, и ничуть не хуже, чем ^\s*(\d{4,5})\s+(\w+)\s*$. Да, из нее неудобно достать результат, но это вполне возможно кодом вида: Console.WriteLine("Имя города:" + String.Join("", match.Groups[2].Captures.Cast().Select(c=>c.Value))); Дело в только в том, что доставать результат парсинга регекса, опираясь на индексы групп - не слишком надежный способ. Надежный способ - именованные группы. Тогда все три выражения дадут вам одинаковый результат, вне зависимисти от того, как вы поставите скобки: ^\s*(?'code'\d{4,5})\s+(?'city'(\w)+)\s*$ ^\s*(?'code'\d{4,5})\s+(?'city'\w+)\s*$ ^\s*(?'code'\d{4,5})\s+(?'city'(\w+))\s*$ Имена групп можно обрамлять в <>, если одиночные кавычки режут глаз: ^\s*(? \d{4,5})\s+(?
\w+)\s*$ Пример кода с вашей изначальной регуляркой + именами групп var lines = @"39032 Абакан 39042 Саяногорск 39031 Черногорск 39036Копьево 42722 Анадыр ь 81831145 Березник 81 856 Карпогоры"; string pattern = @"^\s*(?'code'\d{4,5})\s+(?'city'(\w)+)\s*$"; MatchCollection matches = Regex.Matches(lines, pattern, RegexOptions.Multiline); foreach (Match match in matches) { Console.WriteLine("Код города:" + match.Groups["code"].Value); Console.WriteLine("Имя города:" + match.Groups["city"].Value); } При этом неименованные группы можно вообще убрать из результатов, задав флаг RegexOptions.ExplicitCapture
Комментариев нет:
Отправить комментарий