С# - Разделение на трубе с экранированной трубой в данных?

У меня есть файл с разделителями каналов, который я хотел бы разделить (я использую С#). Например:

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