С# - Разделение на трубе с экранированной трубой в данных?
У меня есть файл с разделителями каналов, который я хотел бы разделить (я использую С#). Например:
This|is|a|test
Однако некоторые данные могут содержать в себе трубу. Если это произойдет, он будет экранирован с помощью обратного слэша:
This|is|a|pip\|ed|test (this is a pip|ed test)
Мне интересно, есть ли regexp или какой-либо другой метод, чтобы разделить это на просто "чистые" трубы (то есть трубы, у которых нет обратной косой черты перед ними). Мой текущий метод заключается в том, чтобы заменить экранированные каналы на пользовательский бит текста, разделить на трубы, а затем заменить мой пользовательский текст на канал. Не очень элегантный, и я не могу не думать, что там лучший способ. Спасибо за любую помощь.
Ответы
Ответ 1
Просто используйте String.IndexOf()
, чтобы найти следующий канал. Если предыдущий символ не является обратным слэшем, используйте String.Substring()
для извлечения слова. В качестве альтернативы вы можете использовать String.IndexOfAny()
, чтобы найти следующее вхождение в трубку или обратную косую черту.
Я много разбираюсь в этом, и это действительно довольно прямолинейно. Принимая мой подход, если все сделано правильно, также будет работать быстрее.
ИЗМЕНИТЬ
На самом деле, может быть, что-то вроде этого. Было бы интересно посмотреть, как это сравнивается по производительности с решением RegEx.
public List<string> ParseWords(string s)
{
List<string> words = new List<string>();
int pos = 0;
while (pos < s.Length)
{
// Get word start
int start = pos;
// Get word end
pos = s.IndexOf('|', pos);
while (pos > 0 && s[pos - 1] == '\\')
{
pos++;
pos = s.IndexOf('|', pos);
}
// Adjust for pipe not found
if (pos < 0)
pos = s.Length;
// Extract this word
words.Add(s.Substring(start, pos - start));
// Skip over pipe
if (pos < s.Length)
pos++;
}
return words;
}
Ответ 2
Это должно сделать это:
string test = @"This|is|a|pip\|ed|test (this is a pip|ed test)";
string[] parts = Regex.Split(test, @"(?<!(?<!\\)*\\)\|");
Регулярное выражение в основном говорит: split на трубах, которым не предшествует escape-символ. Я не должен признавать это, хотя я просто захватил регулярное выражение из этого сообщения и упростил его.
ИЗМЕНИТЬ
Что касается производительности, по сравнению с методом ручного анализа, представленным в этом потоке, я обнаружил, что эта реализация Regex в 3 - 5 раз медленнее реализации Jonathon Wood с использованием более длинной тестовой строки, предоставленной OP.
С учетом сказанного, если вы не создаете или не добавляете слова в List<string>
и не возвращаете void вместо этого, метод Jon приходит примерно в 5 раз быстрее, чем метод Regex.Split()
(0,01 мс против 0,002 мс) для чисто разбиения строки. Если вы добавите накладные расходы на управление и возврат List<string>
, это было примерно в 3,6 раза быстрее (0,01 мс против 0,00275 мс), усредненное по нескольким наборам миллионов итераций. Я не использовал статический Regex.Split() для этого теста, вместо этого я создал новый экземпляр Regex с выражением выше вне моего тестового цикла, а затем вызвал его метод Split.
UPDATE
Использование статической функции Regex.Split() на самом деле намного быстрее, чем повторное использование экземпляра выражения. С этой реализацией использование регулярного выражения только примерно в 1,6 раза медленнее реализации Джона (0,0043 мс против 0,00275 мс)
Результаты были одинаковыми с использованием расширенного регулярного выражения из ссылки, связанной с.
Ответ 3
Я столкнулся с похожим сценарием: для меня было установлено количество номеров труб (не труб с "\ |" ). Вот как я справился.
string sPipeSplit = "This|is|a|pip\\|ed|test (this is a pip|ed test)";
string sTempString = sPipeSplit.Replace("\\|", "¬"); //replace \| with non printable character
string[] sSplitString = sTempString.Split('|');
//string sFirstString = sSplitString[0].Replace("¬", "\\|"); //If you have fixed number of fields and you are copying to other field use replace while copying to other field.
/* Or you could use a loop to replace everything at once
foreach (string si in sSplitString)
{
si.Replace("¬", "\\|");
}
*/
Ответ 4
Вот еще одно решение.
Одна из самых красивых вещей в программировании - это несколько способов решения одной и той же проблемы:
string text = @"This|is|a|pip\|ed|test"; //The original text
string parsed = ""; //Where you will store the parsed string
bool flag = false;
foreach (var x in text.Split('|')) {
bool endsWithArroba = x.EndsWith(@"\");
parsed += flag ? "|" + x + " " : endsWithArroba ? x.Substring(0, x.Length-1) : x + " ";
flag = endsWithArroba;
}
Ответ 5
Решение Cory довольно хорошее. Но, я предпочитаю не работать с Regex, тогда вы можете просто сделать что-то, ища "\ |" и заменив его каким-либо другим персонажем, затем сделайте свой раскол, а затем замените его на "\ |".
Другой вариант заключается в том, чтобы выполнить разделение, затем проверить все строки и, если последний символ является \, а затем соединить его со следующей строкой.
Конечно, все это игнорирует то, что происходит, если вам нужен экранированный обратный слэш перед каналом. Например, "\\ |".
В целом, я склоняюсь к регулярному выражению.
Честно говоря, я предпочитаю использовать FileHelpers, потому что, хотя это не делит запятую, это в основном то же самое. И у них есть отличная история о почему вы не должны писать этот материал сами.
Ответ 6
Вы можете сделать это с помощью регулярного выражения. После того, как вы решите использовать обратную косую черту в качестве escape-символа, у вас есть два случая исключения:
- Выход из трубы:
\|
- Сбрасывание обратной косой черты, которую вы хотите интерпретировать буквально.
Оба они могут быть выполнены в одном и том же регулярном выражении. Сбежавшие обратные косые черты всегда будут иметь два символа \
. Последовательные, сбегающие обратные косые черты всегда будут четными числами символов \
. Если вы обнаружите нечетную последовательность \
перед трубой, это означает, что у вас есть несколько сбрасываемых обратных косых черт, за которыми следует экранированный канал. Поэтому вы хотите использовать что-то вроде этого:
/^(?:((?:[^|\\]|(?:\\{2})|\\\|)+)(?:\||$))*/
Сбивать с толку, возможно, но это должно сработать. Объяснение:
^ #The start of a line
(?:...
[^|\\] #A character other than | or \ OR
(?:\\{2})* #An even number of \ characters OR
\\\| #A literal \ followed by a literal |
...)+ #Repeat the preceding at least once
(?:$|\|) #Either a literal | or the end of a line