Как парсить строку в c
Разделение строк с помощью метода String.Split на C#
Метод String.Split создает массив подстрок, разбивая входную строку по одному или нескольким разделителям. Этот метод зачастую является самым простым способом разделить строку по границам слов. Он также используется для разбиения строк по другим конкретным символам или строкам.
Примеры C# в этой статье выполняются во встроенном средстве выполнения кода и на площадке Try.NET. Нажмите на кнопку Выполнить, чтобы выполнить пример в интерактивном окне. После выполнения кода вы можете изменить его и выполнить измененный код, снова нажав на кнопку Выполнить. Либо в интерактивном окне выполняется измененный код, либо, если компиляция завершается с ошибкой, в интерактивном окне отображаются все сообщения об ошибках компилятора C#.
Следующий код разбивает обычную фразу на массив строк для каждого слова.
Каждый экземпляр знака разделения создает значение в возвращаемом массиве. Последовательные знаки разделения создают пустую строку в виде значения в возвращаемом массиве. В следующем примере показано создание пустой строки с использованием символа пробела в качестве разделителя.
Такое поведение упрощает работу с такими форматами, как файл данных с разделителями-запятыми (CSV), которые представляют табличные данные. Идущие подряд запятые представляют пустой столбец.
Чтобы исключить из возвращаемого массива все пустые строки, можно передать необязательный параметр StringSplitOptions.RemoveEmptyEntries. Для более сложной обработки возвращенной коллекции можно использовать LINQ, чтобы управлять результирующей последовательностью.
String.Split может использовать несколько знаков разделения. В приведенном ниже примере в качестве знаков разделения используются пробелы, запятые, точки, двоеточия и символы табуляции, которые передаются в Split в массиве. Цикл в конце кода отображает каждое из слов в возвращенном массиве.
Последовательные экземпляры любого разделителя создают пустую строку в выходном массиве:
Про LL-парсинг: Подход к синтаксическому анализу через концепцию нарезания строки
Приветствую уважаемое сообщество!
Повторение — мать учения, а разбираться в синтаксическом анализе — очень полезный навык для любого программиста, поэтому хочу еще раз поднять эту тему и поговорить в этот раз про анализ методом рекурсивного спуска (LL), обойдясь без лишних формализмов (к ним потом всегда можно будет вернуться).
Как пишет великий Д. Строгов, «понять — значит упростить». Поэтому, чтобы понять концепцию синтаксического разбора методом рекурсивного спуска (оно же LL-парсинг), упростим задачу насколько можно и вручную напишем синтаксический анализатор формата, похожего на JSON, но более простого (при желании можно будет потом его расширить до анализатора полноценного JSON, если захочется поупражняться). Напишем его, взяв за основу идею нарезания строки.
В классических книгах и курсах по конструированию компиляторов обычно начинают объяснять тему синтаксического разбора и интерпретации, выделяя несколько фаз:
Я сейчас хочу использовать немного другой подход к этой же самой концепции (LL-разбора) и показать, как можно построить LL-анализатор, взяв за основу идею нарезания строки: от исходной строки в процессе разбора отрезаются фрагменты, она становится меньше, а затем разбору подвергается оставшаяся часть строки. В итоге мы придем к той же самой концепции рекурсивного спуска, но немного другим путем, чем это обычно делается. Возможно, этот путь окажется удобнее для понимания сути идеи. А если и нет, то это все равно возможность посмотреть на рекурсивный спуск с другого ракурса.
Начнем с более простой задачи: есть строка с разделителями, и хочется написать итерацию по ее значениям. Что-то вроде:
Как это можно сделать? Стандартный способ — преобразовать строку с разделителями в массив или список с помощью String.split (в Java), или names.split(«,») (в javascript), и сделать итерацию уже по массиву. Но представим, что преобразование в массив использовать не хотим или не можем (например, ну вдруг если мы программируем на языке программирования AVAJ++, в котором нету структуры данных “массив”). Можно еще сканировать строку и отслеживать разделители, но этот способ я использовать тоже не буду, потому что он делает код цикла итерации громоздким и, главное, это идет вразрез с концепцией, которую я хочу показать. Поэтому мы будем относиться к строке с разделителями таким же образом, как относятся к спискам в функциональном программировании. А там для них всегда определяют функции head (получить первый элемент списка) и tail (получить остаток списка). Начиная еще с первых диалектов лиспа, где эти функции назывались совершенно ужасно и неинтуитивно: car и cdr (car = content of address register, cdr = content of decrement register. Преданья старины глубокой, да, эхехех.).
Наша строка — это строка с разделителями. Подсветим разделители фиолетовым:
А элементы списка подсветим желтым:
Будем считать, что наша строка мутабельна (ее можно изменять) и напишем функцию:
Ее сигнатура, например, может быть такой:
На вход функции даем список (в виде строки с разделителями) и, собственно, значение разделителя. На выходе функция выдает первый элемент списка (отрезок строки до первого разделителя), остаток списка и признак, удалось ли вернуть первый элемент. При этом остаток списка помещается в ту же переменную, где был исходный список.
В результате мы получили возможность написать вот так:
Выводит, как и ожидалось:
ivanov
petrov
sidorov
Мы обошлись без конвертации в ArrayList, но зато испортили переменную names, и в ней теперь пустая строка. Выглядит пока не очень полезно, как будто поменяли шило на мыло. Но давайте пойдем дальше. Там мы увидим, зачем это было надо и куда это нас приведет.
Давайте теперь разбирать что-нибудь поинтереснее: список из пар “ключ-значение”. Это тоже очень частая задача.
Тоже ожидаемо. И то же самое можно достичь с помощью String.split, без нарезания строк.
Но допустим, что теперь нам захотелось усложнить наш формат и от плоского key-value перейти к формату, допускающему вложенность, напоминающему JSON. Теперь мы хотим читать что-то такое:
По какому разделителю делать split? Если по запятой, то в одном из токенов у нас окажется строка
Явно не то, что нам нужно. Поэтому надо обратить внимание на структуру той строки, которую мы хотим разобрать.
Она начинается с фигурной скобки и заканчивается фигурной скобкой (парной к ней, что важно). Внутри этих скобок находится список пар ‘ключ’:’значение’, каждая пара отделена от следующей пары запятой. Ключ и значение разделяется двоеточием. Ключ — это строка из букв, заключенная в апострофы. Значение может быть строкой символов, заключенных в апострофы, а может быть такой же структурой, начинающейся и заканчивающейся парными фигурными скобками. Назовем такую структуру словом “объект”, как и принято называть ее в JSON.
Только что мы в неформальной форме описали грамматику нашего, напоминающего JSON, формата. Обычно грамматики описываются наоборот, в формальной форме, и для их записи применяется BNF-нотация или ее вариации. Но сейчас я обойдусь без этого, и мы просто посмотрим, как можно “нарезать” эту строку, чтобы ее по правилам этой грамматики разобрать.
В самом деле, наш “объект” начинается с открывающей фигурной скобки и заканчивается парной ей закрывающей. Что может делать функция, разбирающая такой формат? Скорее всего, следующее:
Также теперь нам надо обратить внимание, что у нас появилось понятие “парная скобка” в дополнение к понятию “разделитель”. Если для нарезания строки до следующего разделителя (двоеточие между ключом и значением, запятая между парами “ключ: значение”) нам было достаточно функции cutNextToken, то теперь, когда в качестве значения у нас может выступать не только строка, но и объект, нам понадобится функция “отрезать до следующей парной скобки”. Примерно такая:
Эта функция отрезает от строки фрагмент от открывающей скобки до парной ей закрывающей, учитывая вложенные скобки, если они есть. Конечно, можно не ограничиваться скобками, а использовать подобную функцию для отрезания разных блочных структур, допускающих вложенность: операторных блоков begin..end, if..endif, for..endfor и аналогичных им.
Нарисуем графически, что будет происходить со строкой. Бирюзовый цвет — это значит мы сканируем строку вперед на символ, выделенный бирюзовым, чтобы определить, что нам делать дальше. Фиолетовый — это “что отрезать, это когда мы отрезаем от строки фрагменты, выделенные фиолетовым, и то, что осталось, продолжаем разбирать дальше.
Для сравнения, вывод программы (текст программы приведен в приложении), разбирающей эту строку:
Демонстрация разбора JSON-подобной структуры
Мы в любой момент знаем, что мы ожидаем найти в нашей входной строке. Если мы вошли в функцию parseJsonObject, то мы ожидаем, что нам туда передали объект, и можем это проверить по наличию открывающей и закрывающей скобки в начале и в конце. Если мы вошли в функцию parseJsonPairList, то мы ожидаем там список пар “ключ: значение”, и после того, как мы “откусили” ключ (до разделителя “:”), мы ожидаем, что следующее, что мы “откусываем” — это значение. Мы можем посмотреть на первый символ значения, и сделать вывод о его типе (если апостроф — то значение имеет тип “строка”, если открывающая фигурная скобка — то значение имеет тип “объект”).
Таким образом, отрезая от строки фрагменты, мы можем выполнить ее синтаксический разбор методом нисходящего анализа (рекурсивного спуска). А когда мы можем выполнить синтаксический разбор, то мы можем разбирать нужный нам формат. Или придумать свой, удобный нам формат и разбирать его. Или придумать DSL (Domain Specific Language) для нашей конкретной области и сконструировать интерпретатор для него. И сконструировать правильно, без вымученных решений на регекспах или самопальных state-машинах, которые возникают у программистов, которые пытаются решить какую-нибудь задачу, требующую синтаксического разбора, но не вполне владеют материалом.
Вот. Поздравляю всех с наступившим летом и желаю добра, любви и функциональных парсеров 🙂
Для дальнейшего чтения:
Идеологическое: пара длинных, но стоящих прочтения статей Стива Йегге (англ.):
You either learn compilers and start writing your own DSLs, or your get yourself a better language
The first big phase of the compilation pipeline is parsing
Type casts, narrowing and widening conversions, friend functions to bypass the standard class protections, stuffing minilanguages into strings and parsing them out by hand, there are dozens of ways to bypass the type systems in Java and C++, and programmers use them all the time, because (little do they know) they’re actually trying to build software, not hardware.
Техническое: две статьи, посвященных синтаксическому анализу, про отличие LL и LR-подходов (англ.):
LL and LR Parsing Demystified
LL and LR in Context: Why Parsing Tools Are Hard
И еще глубже в тему: как написать интерпретатор Лиспа на C++
Lisp interpreter in 90 lines of C++
Разбор (разделение) строки в C++ с помощью разделителя строк (стандартный C++) [дубликат]
этот вопрос уже есть ответ здесь:
я разбираю строку в C++, используя следующее:
разбор с одним разделителем символов в порядке. Но что делать, если я хочу использовать строку в качестве ограничитель данных.
пример: я хочу разделить:
С > = как разделитель, так что я могу получить Скотт и Тигр.
11 ответов:
можно использовать std::string::find() функция, чтобы найти позицию вашего разделителя строк, а затем использовать std::string::substr() чтобы получить маркер.
The find(const string& str, size_t pos = 0) функция возвращает позицию первого вхождения str в строке, или npos если строка не найдена.
если у вас есть несколько разделителей, после извлечения одного токена вы можете удалить его (включая разделитель), чтобы продолжить последующие извлечения (если вы хотите сохранить исходную строку, просто используйте s = s.substr(pos + delimiter.length()); ):
таким образом, вы можете легко цикл, чтобы получить каждый маркер.
Пример
этот метод использует std::string::find без изменения исходной строки путем запоминания начала и конца предыдущего маркера подстроки.
вы можете использовать следующую функцию, чтобы разделить строку:
функции strtok позволяет передавать несколько символов в качестве разделителей. Бьюсь об заклад, если вы передали в «>=» ваш пример строка будет разделена правильно (хотя > и = считаются отдельными разделителями).
редактировать, если вы не хотите использовать c_str() для преобразования из String в char*, вы можете использовать substr и find_first_of для маркировки.
этот код разбивает строки из текста и добавляет все в вектор.
вот тот, который работает для вашего случая.
вот мой взгляд на это. Он обрабатывает граничные случаи и принимает необязательный параметр для удаления пустых записей из результатов.
строка разделитель
Разделить строку на основе строки-разделителя. Например, разбиение строки «adsf-+qwret-+nvfkbdsj-+orthdfjgh-+dfjrleih» на основе строки разделителя «-+» на выходе будетдля одного символа-разделителя
Если вы не хотите изменять строку (как в ответе Винченцо Pii)и хотите вывести последний токен, а также, вы можете использовать этот подход:
P. S: работает только в том случае, если длины строк после разбиения равны
Парсинг строки в си
Как максимально эффективно вытащить данные из строки? Имеется на входе:
Хочу на выходе получить:
Изначально планировал тремя ссканфами обойтись(на название параметра, на первцю пару чисел, на вторую), но не понимаю как из первого входа вытащить две строки(str 1 и str 2). Если кто-то поможет кодом, буду очень благодарен
4 ответа 4
Что-то типа такого устроит.
Скорей всего последняя правка ответа.
В общем вот алгоритм:
Ну и дальше я просто все остальные функции выложу:
Если хотите, чтобы я объяснил код, то отпишите в комментарии. Удачи 🙂
Как обычно и бывает, более-менее подробный разбор с анализом ошибок и попытками продолжить работу после них выливается в достаточно длинную программу.
Хотя, основная функция разбора довольно компактна и легко перестаиваема под небольшие изменения формата данных.
Эта функция вот так вызывается в цикле для обработки всех фрагментов данных в одной строке
При выходе из цикла по получению в r.str_error кода NO_FR_BEGIN (т.е. при поиске начала фрагмента данных найден неверный символ-разделитель) можно попробовать найти начало фрагмента, вызвав str = strchr(str, ‘<'); (я не стал загромождать пример (и так, довольно "увесистый") подобными способами восстановления обработки после найденных ошибок).
Как обычно, компилируем, запускаем и смотрим результат
UPDATE
Если снизить требования к допустимым символам-разделителям между фрагментами, открывающей фрагмент < и началом имени, символам составляющим имя и т.п., то код, конечно, можно немного сократить.
Для разнообразия я изменил возвращаемый тип на указатель. В этом случае использование функции может выглядеть вот так:
[ C ] парсинг строки
есть файл, в котором содержутся данные в виде:
как правильно распарсить строку и вытащить value из кавычек в соответствуищий массив char для данной переменной(которые в оперативной памяти, а не в файле)
я делаю следующим образом:
2. вычитываю из файла строки, которые начинаются с «params_»
#define BUFF_SIZE 32
while( fgets(str, BUFF_SIZE, pFILE) )
if( strstr( &str[0], «params_» ) == &str[0] )
в цикле нахожу строки, которые мне нужны.
мне нужно распарсить саму строку, т.е. «вытащить» имя переменной и ее значение без кавычек.
прошу помочь, спасибо!
Остается лишь отбрасывание кавычек добавить
зачем строить свои велопарсеры?
но как то сложно, может попроще как то можно, не разбивая строку, без strtok()
Из пушки по воробью.
\«%s\» — разве так нельзя?
Особенно раздел BUGS.
В Глибе есть функционал по парсингу ini-файлов.
> \«%s\» — разве так нельзя?
тебя тоже в мане забанили?)
мне не для дела, иначе прочитал бы ман, а просто интересно было
Что-то не кажется мне безопасным sscanf использвоать.
franchukroman> Из пушки по воробью.
ну если ТС пишет одноразовую наколенную поделку, то да, перебор, а если с заделом на будущее?
Даже с заделом на будущее, усложнять без реальной нужды, вероятно, не следует.
Если в будущем все усложнится и появится серьезная потребность в flex, никто не мешает переделать.
не работает такая конструкция
покажи строку, которую парсишь.
а, да. невнимательно читал стартовый пост. замени «param_%d…» на «params_%d»:
А разве он так не будет постоянно самую первую запись возвращать?
не принципиально, params или param
sscanf не считывает то, что между кавычек
попробуйте errno проверить
> А разве он так не будет постоянно самую первую запись возвращать?
sscanf же не изменяет указатель и не возвращает указатель. А просто находит самое первое совпадение.
Т.е. это будет работать только для первого параметра в строке!
а если первым будет не param_N, а слово переменной длины(т.е. название самой переменной будет переменной длины) и нужно вытащить имя переменной и ее значение?
не подскажите как sscanf будет выглядеть?
Замени const char *line = «param_1=\„10000\“;\n»; на
Я про конкретные конструкции ничо не говорил. (Или вы ответили чутка не тому ;))
Что-то не кажется мне безопасным sscanf использвоать.
дык, так это у тебя одна строка будет. \n добавь
> Замени const char *line = «param_1=\„10000\“;\n»; на …
ты наркоман? кончишь торчать, перечитай стартовый пост.
arsi, преогромнеейшее спасибо.
разбери строку по регулярным выражениям (man regex)
Т.е. ты предлагаешь считывать построчно каким-нибудь getline, а потом sscanf’ом обрабатывать?
> Т.е. ты предлагаешь считывать построчно каким-нибудь getline, а потом sscanf’ом обрабатывать?
если файл может содержать «неформатные» строки, типа комментов, то да, конечно:
иначе можно использовать только fscanf(3).
о господя размер буфера под строку у нормальных функций указывается с учетом терминатора а это какое то убожество
о господя размер буфера под строку у нормальных функций указывается с учетом терминатора а это какое то убожество
strtok ужасно багоопасен но тот кто вороружен предупрежден ))) тема такая она (strtok) юзает механизм TLS (thread local storage) для сохранения между вызовами положения указателя для каждого потока имеется свой такой указатель но и это не спасает например вы пишите алгоритм в цикде вызываете strtok и между вызовами strtok вызываете какую нимбудь системную функцию так вот если она (а вы это знать не можете) тоже юзает strtok то она похеерит ваш указатель привязанный к текущему потоку когда вы вернетесь из системного вызова и вызовете strtok она начнет глючить потому что указатель похерен
strtok можно использовать только если между вызовами (например вы в цикле что то парсите) нет вызовов незнакомых функций которые потенциально тоже могут юзать strtok
наткнулся на это при попытке написать алгоритм для сохранения кукисов под вендой (в цикле вызывалаксь strtok а между вызовами был еще один цикл тоже юзающий strtok так вот при выходе из внутреннего цикла внешний оказывался неработоспособен) strtok гиблое дело и работающее со специфическими ограничениями в итоге я бросил писать алгоритм из за нее пошел другим путем






