C # - Расщепление на трубе с побегом трубы в данных? - PullRequest
11 голосов
/ 28 апреля 2011

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

This|is|a|test

Однако некоторые данные могут содержать канал. Если это так, он будет экранирован обратной косой чертой:

This|is|a|pip\|ed|test (this is a pip|ed test)

Мне интересно, есть ли регулярное выражение или какой-либо другой метод, чтобы разделить это на только "чистые" трубы (то есть трубы, которые не имеют обратной косой черты перед ними). Мой текущий метод состоит в том, чтобы заменить экранированные каналы пользовательским фрагментом текста, разделить их на каналы, а затем заменить мой пользовательский текст на канал. Не очень элегантно, и я не могу не думать, что есть лучший способ. Спасибо за любую помощь.

Ответы [ 6 ]

7 голосов
/ 28 апреля 2011

Просто используйте String.IndexOf(), чтобы найти следующую трубу. Если предыдущий символ не является обратной косой чертой, используйте String.Substring() для извлечения слова. В качестве альтернативы вы можете использовать String.IndexOfAny(), чтобы найти следующее вхождение трубы или обратной косой черты.

Я делаю много таких разборов, и это действительно довольно просто. При правильном подходе мой подход также будет ускоряться.

EDIT

На самом деле, может быть, что-то вроде этого. Было бы интересно посмотреть, как это сравнивает по производительности с решением 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;
}
3 голосов
/ 28 апреля 2011

Это нужно сделать:

string test = @"This|is|a|pip\|ed|test (this is a pip|ed test)";
string[] parts = Regex.Split(test, @"(?<!(?<!\\)*\\)\|");

Регулярное выражение в основном гласит: разделить на каналы, которым не предшествует escape-символ. Я не должен брать за это никакой заслуги, я просто похитил регулярное выражение из этого поста и упростил его.

EDIT

С точки зрения производительности, по сравнению с методом ручного синтаксического анализа, представленным в этой теме, я обнаружил, что эта реализация Regex в 3-5 раз медленнее, чем реализация Джонатона Вуда с использованием более длинной тестовой строки, предоставленной OP.

С учетом сказанного, если вы не создадите или не добавите слова в List<string> и не вернете void, метод Джона будет работать примерно в 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 мс)

Результаты были такими же, используя расширенное регулярное выражение из поста, на который я ссылался.

2 голосов
/ 15 декабря 2016

Я сталкивался с подобным сценарием, для меня был установлен счетчик количества труб (не труб с "\ |").Вот как я справился.

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("¬", "\\|");
}
*/
1 голос
/ 28 апреля 2011

Вот еще одно решение.

Одна из самых красивых вещей в программировании - это несколько способов решения одной и той же проблемы:

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;
}
0 голосов
/ 28 апреля 2011

Вы можете сделать это с помощью регулярного выражения.Как только вы решите использовать обратную косую черту в качестве своего 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
0 голосов
/ 28 апреля 2011

Решение Кори довольно хорошее.Но я предпочитаю не работать с Regex, тогда вы можете просто сделать что-то в поиске "\ |"и замените его каким-нибудь другим символом, затем выполните разделение, затем замените его снова на "\ |".

Другой вариант - выполнить разбиение, затем изучить все строки и, если последний символ является \, соединить его со следующей строкой.

Конечно, все это игнорирует то, чтопроисходит, если вам нужен экранированный обратный слеш перед каналом .. как "\\ |".

В целом, я склоняюсь к регулярному выражению, хотя.

Честно говоря, я предпочитаю использовать FileHelpers потому что, хотя это не разделенный запятыми, это в основном то же самое.И у них есть замечательная история о , почему вы не должны писать это сами .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...